使用Three.js与WebGL构建网页版Minecraft的完整技术指南

📅 2026/7/18 12:26:51
使用Three.js与WebGL构建网页版Minecraft的完整技术指南
最近在开发网页游戏时发现很多开发者对实现网页版Minecraft这样的3D沙盒游戏很感兴趣但往往在技术选型和实现细节上遇到困难。本文将完整介绍如何使用现代Web技术构建一个简易的网页版Minecraft涵盖从基础环境搭建到核心功能实现的完整流程。1. 项目概述与技术选型1.1 网页版Minecraft的核心需求网页版Minecraft需要实现几个核心功能3D场景渲染、方块放置与破坏、玩家移动控制、简单物理引擎等。与传统客户端游戏不同网页版需要充分利用浏览器能力同时保证性能和兼容性。从技术角度看主要挑战包括3D图形渲染性能优化实时用户交互处理内存管理和资源加载跨浏览器兼容性1.2 技术栈选择理由基于当前Web技术生态我们选择以下技术栈Three.js作为3D渲染引擎Three.js是当前最成熟的WebGL封装库提供了完整的3D图形管线支持几何体、材质、光照、相机等核心概念大大降低了WebGL的使用门槛。JavaScript/TypeScript作为开发语言现代JavaScript具备良好的类型支持和模块化能力配合Webpack或Vite等构建工具可以高效开发。WebGL 2.0作为图形API相比WebGL 1.0WebGL 2.0提供了更多高级特性如实例化渲染、变换反馈等特别适合Minecraft这种需要渲染大量相似方块的场景。2. 开发环境准备2.1 基础环境配置首先确保你的开发环境包含以下工具Node.js 16.0或更高版本现代浏览器Chrome 90、Firefox 88、Safari 14代码编辑器VS Code推荐本地Web服务器用于开发调试2.2 项目初始化创建项目目录结构mkdir web-minecraft cd web-minecraft npm init -y安装核心依赖npm install three npm install --save-dev types/three npm install --save-dev webpack webpack-cli webpack-dev-server npm install --save-dev typescript ts-loader创建基础项目结构web-minecraft/ ├── src/ │ ├── core/ │ ├── world/ │ ├── player/ │ ├── blocks/ │ └── utils/ ├── public/ │ └── index.html ├── package.json └── tsconfig.json2.3 TypeScript配置创建tsconfig.json文件{ compilerOptions: { target: ES2020, module: ESNext, lib: [DOM, DOM.Iterable, ES6], allowJs: true, skipLibCheck: true, esModuleInterop: true, allowSyntheticDefaultImports: true, strict: true, forceConsistentCasingInFileNames: true, moduleResolution: node, resolveJsonModule: true, isolatedModules: true, noEmit: true, jsx: react-jsx }, include: [src] }3. 核心架构设计3.1 游戏引擎架构网页版Minecraft需要设计一个模块化的游戏引擎架构// src/core/GameEngine.ts class GameEngine { private scene: THREE.Scene; private camera: THREE.PerspectiveCamera; private renderer: THREE.WebGLRenderer; private clock: THREE.Clock; private world: World; private player: Player; constructor() { this.initRenderer(); this.initScene(); this.initCamera(); this.initWorld(); this.initPlayer(); this.initEventListeners(); } private initRenderer(): void { this.renderer new THREE.WebGLRenderer({ antialias: true, alpha: true }); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); document.body.appendChild(this.renderer.domElement); } private initScene(): void { this.scene new THREE.Scene(); this.scene.background new THREE.Color(0x87CEEB); // 天空蓝 // 环境光 const ambientLight new THREE.AmbientLight(0x404040, 0.6); this.scene.add(ambientLight); // 方向光太阳 const directionalLight new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(50, 100, 50); this.scene.add(directionalLight); } private initCamera(): void { this.camera new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); this.camera.position.set(0, 10, 0); } // 其他初始化方法... }3.2 世界生成系统世界生成是Minecraft的核心功能需要实现地形生成、区块管理等// src/world/WorldGenerator.ts class WorldGenerator { private noise: NoiseFunction2D; private chunkSize: number 16; private worldHeight: number 128; constructor(seed: number 12345) { this.noise createNoise2D(() seed); } generateChunk(chunkX: number, chunkZ: number): ChunkData { const blocks: Block[][][] []; for (let x 0; x this.chunkSize; x) { blocks[x] []; for (let z 0; z this.chunkSize; z) { blocks[x][z] []; const worldX chunkX * this.chunkSize x; const worldZ chunkZ * this.chunkSize z; // 使用噪声函数生成地形高度 const height this.generateHeight(worldX, worldZ); for (let y 0; y this.worldHeight; y) { if (y height - 3) { blocks[x][z][y] new StoneBlock(); // 石头层 } else if (y height) { blocks[x][z][y] new DirtBlock(); // 泥土层 } else if (y height) { blocks[x][z][y] new GrassBlock(); // 草方块层 } else { blocks[x][z][y] new AirBlock(); // 空气 } } } } return new ChunkData(chunkX, chunkZ, blocks); } private generateHeight(x: number, z: number): number { // 使用多倍频噪声生成自然地形 const scale 0.01; let height this.noise(x * scale, z * scale) * 20; height this.noise(x * scale * 2, z * scale * 2) * 10; height this.noise(x * scale * 4, z * scale * 4) * 5; return Math.floor(height 50); // 基准高度50 } }4. 方块系统实现4.1 基础方块类设计方块是Minecraft世界的基本构建单元需要设计灵活的继承体系// src/blocks/Block.ts abstract class Block { public readonly type: BlockType; public readonly transparent: boolean; public readonly solid: boolean; constructor(type: BlockType, transparent: boolean false, solid: boolean true) { this.type type; this.transparent transparent; this.solid solid; } abstract createMesh(x: number, y: number, z: number): THREE.Mesh; abstract getTexturePaths(): { [face: string]: string }; } // 具体方块实现 class GrassBlock extends Block { constructor() { super(BlockType.GRASS); } createMesh(x: number, y: number, z: number): THREE.Mesh { const geometry new THREE.BoxGeometry(1, 1, 1); const materials this.createMaterials(); return new THREE.Mesh(geometry, materials); } private createMaterials(): THREE.Material[] { return [ new THREE.MeshBasicMaterial({ color: 0x7CFC00 }), // 右 - 绿色 new THREE.MeshBasicMaterial({ color: 0x7CFC00 }), // 左 - 绿色 new THREE.MeshBasicMaterial({ color: 0x90EE90 }), // 上 - 浅绿 new THREE.MeshBasicMaterial({ color: 0x8B4513 }), // 下 - 棕色 new THREE.MeshBasicMaterial({ color: 0x7CFC00 }), // 前 - 绿色 new THREE.MeshBasicMaterial({ color: 0x7CFC00 }) // 后 - 绿色 ]; } getTexturePaths(): { [face: string]: string } { return { top: textures/grass_top.png, bottom: textures/dirt.png, sides: textures/grass_side.png }; } }4.2 方块注册表为了方便管理所有方块类型需要实现一个方块注册表// src/blocks/BlockRegistry.ts class BlockRegistry { private static instance: BlockRegistry; private blocks: MapBlockType, Block new Map(); private constructor() { this.registerBlocks(); } public static getInstance(): BlockRegistry { if (!BlockRegistry.instance) { BlockRegistry.instance new BlockRegistry(); } return BlockRegistry.instance; } private registerBlocks(): void { this.blocks.set(BlockType.AIR, new AirBlock()); this.blocks.set(BlockType.STONE, new StoneBlock()); this.blocks.set(BlockType.GRASS, new GrassBlock()); this.blocks.set(BlockType.DIRT, new DirtBlock()); this.blocks.set(BlockType.WOOD, new WoodBlock()); this.blocks.set(BlockType.LEAVES, new LeavesBlock()); } public getBlock(type: BlockType): Block { const block this.blocks.get(type); if (!block) { throw new Error(Block type ${type} not registered); } return block; } }5. 玩家控制系统5.1 第一人称摄像机实现Minecraft风格的第一人称摄像机控制// src/player/FirstPersonCamera.ts class FirstPersonCamera { private camera: THREE.PerspectiveCamera; private velocity: THREE.Vector3; private onGround: boolean; private moveSpeed: number; private jumpPower: number; constructor(camera: THREE.PerspectiveCamera) { this.camera camera; this.velocity new THREE.Vector3(); this.onGround false; this.moveSpeed 0.1; this.jumpPower 0.15; this.setupEventListeners(); } private setupEventListeners(): void { const keys: { [key: string]: boolean } {}; document.addEventListener(keydown, (event) { keys[event.code] true; }); document.addEventListener(keyup, (event) { keys[event.code] false; }); // 鼠标控制视角 document.addEventListener(mousemove, (event) { if (document.pointerLockElement document.body) { this.rotateCamera(event.movementX, event.movementY); } }); // 进入指针锁定 document.addEventListener(click, () { if (document.pointerLockElement ! document.body) { document.body.requestPointerLock(); } }); } private rotateCamera(deltaX: number, deltaY: number): void { const sensitivity 0.002; // 左右旋转Y轴 this.camera.rotation.y - deltaX * sensitivity; // 上下俯仰X轴限制角度 this.camera.rotation.x - deltaY * sensitivity; this.camera.rotation.x Math.max( -Math.PI / 2, Math.min(Math.PI / 2, this.camera.rotation.x) ); } public update(world: World): void { this.handleMovement(); this.applyGravity(); this.handleCollisions(world); } private handleMovement(): void { const direction new THREE.Vector3(); const forward new THREE.Vector3(0, 0, -1); const right new THREE.Vector3(1, 0, 0); // 应用摄像机旋转 forward.applyEuler(this.camera.rotation); right.applyEuler(this.camera.rotation); // 前后移动 if (keys[KeyW]) direction.add(forward); if (keys[KeyS]) direction.sub(forward); // 左右移动 if (keys[KeyA]) direction.sub(right); if (keys[KeyD]) direction.add(right); // 标准化并应用速度 if (direction.length() 0) { direction.normalize(); this.velocity.x direction.x * this.moveSpeed; this.velocity.z direction.z * this.moveSpeed; } else { this.velocity.x 0; this.velocity.z 0; } // 跳跃 if (keys[Space] this.onGround) { this.velocity.y this.jumpPower; this.onGround false; } } }5.2 物理系统实现简单的物理引擎来处理重力和碰撞// src/physics/PhysicsEngine.ts class PhysicsEngine { private gravity: number -0.02; private terminalVelocity: number -1.0; applyGravity(velocity: THREE.Vector3, onGround: boolean): void { if (!onGround) { velocity.y this.gravity; velocity.y Math.max(velocity.y, this.terminalVelocity); } } checkCollision( position: THREE.Vector3, velocity: THREE.Vector3, world: World ): CollisionResult { const result: CollisionResult { collided: false, normal: new THREE.Vector3(), newPosition: position.clone().add(velocity) }; // 简化的AABB碰撞检测 const playerBox this.getPlayerBoundingBox(result.newPosition); // 检查周围方块 for (let x Math.floor(playerBox.min.x); x Math.floor(playerBox.max.x); x) { for (let y Math.floor(playerBox.min.y); y Math.floor(playerBox.max.y); y) { for (let z Math.floor(playerBox.min.z); z Math.floor(playerBox.max.z); z) { const block world.getBlock(x, y, z); if (block block.solid) { const blockBox new THREE.Box3( new THREE.Vector3(x, y, z), new THREE.Vector3(x 1, y 1, z 1) ); if (playerBox.intersectsBox(blockBox)) { result.collided true; this.resolveCollision(result, playerBox, blockBox); } } } } } return result; } private getPlayerBoundingBox(position: THREE.Vector3): THREE.Box3 { // 玩家碰撞箱比实际玩家稍小 const width 0.6; const height 1.8; return new THREE.Box3( new THREE.Vector3( position.x - width / 2, position.y, position.z - width / 2 ), new THREE.Vector3( position.x width / 2, position.y height, position.z width / 2 ) ); } }6. 区块管理系统6.1 区块加载与卸载为了优化性能需要实现区块的动态加载和卸载// src/world/ChunkManager.ts class ChunkManager { private chunks: Mapstring, Chunk new Map(); private renderDistance: number 8; private loadingChunks: Setstring new Set(); constructor(private worldGenerator: WorldGenerator) {} update(playerPosition: THREE.Vector3): void { const playerChunkX Math.floor(playerPosition.x / 16); const playerChunkZ Math.floor(playerPosition.z / 16); // 计算需要加载的区块范围 const chunksToLoad: [number, number][] []; for (let x playerChunkX - this.renderDistance; x playerChunkX this.renderDistance; x) { for (let z playerChunkZ - this.renderDistance; z playerChunkZ this.renderDistance; z) { chunksToLoad.push([x, z]); } } // 加载新区块 this.loadChunks(chunksToLoad); // 卸载远处区块 this.unloadDistantChunks(playerChunkX, playerChunkZ); } private loadChunks(chunksToLoad: [number, number][]): void { for (const [chunkX, chunkZ] of chunksToLoad) { const chunkKey this.getChunkKey(chunkX, chunkZ); if (!this.chunks.has(chunkKey) !this.loadingChunks.has(chunkKey)) { this.loadingChunks.add(chunkKey); // 使用Web Worker进行异步区块生成 this.generateChunkAsync(chunkX, chunkZ).then(chunk { this.chunks.set(chunkKey, chunk); this.loadingChunks.delete(chunkKey); }); } } } private async generateChunkAsync(chunkX: number, chunkZ: number): PromiseChunk { // 在实际项目中这里可以使用Web Worker return new Promise((resolve) { setTimeout(() { const chunkData this.worldGenerator.generateChunk(chunkX, chunkZ); resolve(new Chunk(chunkData)); }, 0); }); } private getChunkKey(x: number, z: number): string { return ${x},${z}; } }6.2 网格优化为了提升渲染性能需要将区块内的方块合并为更少的网格// src/world/Chunk.ts class Chunk { private mesh: THREE.Mesh; private chunkData: ChunkData; constructor(chunkData: ChunkData) { this.chunkData chunkData; this.mesh this.createMesh(); } private createMesh(): THREE.Mesh { const geometry new THREE.BufferGeometry(); const material new THREE.MeshLambertMaterial({ vertexColors: true }); const positions: number[] []; const normals: number[] []; const colors: number[] []; const indices: number[] []; let vertexIndex 0; for (let x 0; x 16; x) { for (let y 0; y 128; y) { for (let z 0; z 16; z) { const block this.chunkData.getBlock(x, y, z); if (block block.type ! BlockType.AIR) { this.addBlockToMesh(x, y, z, block, positions, normals, colors, indices, vertexIndex); vertexIndex 24; // 每个方块最多24个顶点6个面×4个顶点 } } } } geometry.setAttribute(position, new THREE.Float32BufferAttribute(positions, 3)); geometry.setAttribute(normal, new THREE.Float32BufferAttribute(normals, 3)); geometry.setAttribute(color, new THREE.Float32BufferAttribute(colors, 3)); geometry.setIndex(indices); return new THREE.Mesh(geometry, material); } private addBlockToMesh( x: number, y: number, z: number, block: Block, positions: number[], normals: number[], colors: number[], indices: number[], startIndex: number ): void { // 只渲染可见的面Greedy Meshing优化 // 这里简化实现实际项目中应该实现面剔除 const faces [ { // 前面 vertices: [ [x, y, z 1], [x 1, y, z 1], [x 1, y 1, z 1], [x, y 1, z 1] ], normal: [0, 0, 1] }, // 其他面... ]; for (const face of faces) { if (this.shouldRenderFace(x, y, z, face.normal)) { this.addFaceToMesh(face, block, positions, normals, colors, indices, startIndex); startIndex 4; } } } }7. 用户交互系统7.1 方块放置与破坏实现鼠标交互来放置和破坏方块// src/interaction/BlockInteraction.ts class BlockInteraction { private raycaster: THREE.Raycaster; private mouse: THREE.Vector2; private maxDistance: number 10; constructor(private camera: THREE.Camera, private scene: THREE.Scene, private world: World) { this.raycaster new THREE.Raycaster(); this.mouse new THREE.Vector2(); this.setupMouseListeners(); } private setupMouseListeners(): void { document.addEventListener(mousedown, (event) { if (event.button 0) { // 左键 - 破坏方块 this.breakBlock(); } else if (event.button 2) { // 右键 - 放置方块 this.placeBlock(); } }); document.addEventListener(contextmenu, (event) { event.preventDefault(); // 阻止右键菜单 }); } private breakBlock(): void { const targetBlock this.getTargetBlock(); if (targetBlock) { this.world.setBlock(targetBlock.position.x, targetBlock.position.y, targetBlock.position.z, null); this.world.updateChunkMesh( Math.floor(targetBlock.position.x / 16), Math.floor(targetBlock.position.z / 16) ); } } private placeBlock(): void { const targetBlock this.getTargetBlock(); if (targetBlock) { // 在目标方块相邻位置放置新方块 const placePosition targetBlock.position.clone() .add(targetBlock.normal); this.world.setBlock(placePosition.x, placePosition.y, placePosition.z, new StoneBlock()); this.world.updateChunkMesh( Math.floor(placePosition.x / 16), Math.floor(placePosition.z / 16) ); } } private getTargetBlock(): { position: THREE.Vector3; normal: THREE.Vector3 } | null { this.raycaster.setFromCamera(this.mouse, this.camera); // 检测与方块的相交 const intersects this.raycaster.intersectObjects(this.world.getBlockMeshes()); if (intersects.length 0) { const intersect intersects[0]; return { position: intersect.point.floor(), normal: intersect.face!.normal }; } return null; } }7.2 准星与UI添加游戏UI元素!-- public/index.html -- !DOCTYPE html html langzh head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title网页版Minecraft/title style body { margin: 0; padding: 0; overflow: hidden; font-family: Arial, sans-serif; } #crosshair { position: absolute; top: 50%; left: 50%; width: 20px; height: 20px; transform: translate(-50%, -50%); pointer-events: none; } #crosshair::before, #crosshair::after { content: ; position: absolute; background: white; } #crosshair::before { width: 2px; height: 20px; left: 9px; top: 0; } #crosshair::after { width: 20px; height: 2px; left: 0; top: 9px; } #hud { position: absolute; top: 10px; left: 10px; color: white; font-size: 14px; text-shadow: 1px 1px 2px black; } /style /head body div idcrosshair/div div idhud divFPS: span idfps0/span/div div位置: span idposition0, 0, 0/span/div div区块: span idchunks0/span/div /div script srcbundle.js/script /body /html8. 性能优化策略8.1 渲染优化技术网页版Minecraft需要大量优化才能保证流畅运行视锥体剔除只渲染摄像机可见范围内的区块// src/rendering/FrustumCulling.ts class FrustumCulling { private frustum new THREE.Frustum(); private cameraView new THREE.Matrix4(); update(camera: THREE.Camera): void { this.cameraView.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); this.frustum.setFromProjectionMatrix(this.cameraView); } isChunkInFrustum(chunk: Chunk): boolean { const chunkBounds chunk.getBoundingBox(); return this.frustum.intersectsBox(chunkBounds); } }LOD层次细节根据距离使用不同细节级别的网格// src/rendering/LODSystem.ts class LODSystem { private lodDistances [16, 32, 64]; // 不同LOD级别的距离阈值 getLODLevel(distance: number): number { for (let i 0; i this.lodDistances.length; i) { if (distance this.lodDistances[i]) { return i; } } return this.lodDistances.length; } createLODMeshes(chunkData: ChunkData): THREE.LOD { const lod new THREE.LOD(); for (let level 0; level this.lodDistances.length; level) { const mesh this.createMeshForLOD(chunkData, level); const distance level 0 ? 0 : this.lodDistances[level - 1]; lod.addLevel(mesh, distance); } return lod; } }8.2 内存管理对象池模式重用网格和几何体对象// src/utils/ObjectPool.ts class ObjectPoolT { private pool: T[] []; private createFn: () T; private resetFn: (obj: T) void; constructor(createFn: () T, resetFn: (obj: T) void, initialSize: number 10) { this.createFn createFn; this.resetFn resetFn; for (let i 0; i initialSize; i) { this.pool.push(createFn()); } } acquire(): T { if (this.pool.length 0) { return this.pool.pop()!; } return this.createFn(); } release(obj: T): void { this.resetFn(obj); this.pool.push(obj); } } // 几何体对象池 const geometryPool new ObjectPool( () new THREE.BufferGeometry(), (geometry) geometry.dispose() );9. 高级特性实现9.1 光影系统实现基础的光照和阴影// src/rendering/LightingSystem.ts class LightingSystem { private shadowMap: THREE.WebGLRenderTarget; private shadowCamera: THREE.OrthographicCamera; constructor() { this.shadowMap new THREE.WebGLRenderTarget(2048, 2048, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat }); this.shadowCamera new THREE.OrthographicCamera(-50, 50, 50, -50, 0.1, 100); } updateSunPosition(timeOfDay: number): void { const angle (timeOfDay / 24) * Math.PI * 2; const sunX Math.cos(angle) * 100; const sunY Math.sin(angle) * 100; const sunZ 50; // 更新光源位置 directionalLight.position.set(sunX, sunY, sunZ); this.shadowCamera.position.set(sunX, sunY, sunZ); this.shadowCamera.lookAt(0, 0, 0); } renderShadows(renderer: THREE.WebGLRenderer, scene: THREE.Scene): void { // 第一次渲染生成阴影贴图 renderer.setRenderTarget(this.shadowMap); renderer.render(scene, this.shadowCamera); renderer.setRenderTarget(null); } }9.2 水体渲染实现简单的水体效果// src/blocks/WaterBlock.ts class WaterBlock extends Block { private material: THREE.ShaderMaterial; constructor() { super(BlockType.WATER, true, false); this.material this.createWaterMaterial(); } private createWaterMaterial(): THREE.ShaderMaterial { return new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, color: { value: new THREE.Color(0x0077be) }, opacity: { value: 0.8 } }, vertexShader: varying vec2 vUv; varying vec3 vPosition; void main() { vUv uv; vPosition position; gl_Position projectionMatrix * modelViewMatrix * vec4(position, 1.0); } , fragmentShader: uniform float time; uniform vec3 color; uniform float opacity; varying vec2 vUv; varying vec3 vPosition; void main() { // 简单的波浪效果 float wave sin(vPosition.x * 2.0 time) * cos(vPosition.z * 2.0 time) * 0.1; vec3 finalColor color vec3(wave * 0.1); gl_FragColor vec4(finalColor, opacity); } , transparent: true }); } update(deltaTime: number): void { this.material.uniforms.time.value deltaTime; } }10. 调试与性能监控10.1 性能统计面板添加实时性能监控// src/debug/StatsPanel.ts class StatsPanel { private fpsElement: HTMLElement; private memoryElement: HTMLElement; private chunksElement: HTMLElement; private positionElement: HTMLElement; private frames: number 0; private lastTime: number performance.now(); constructor() { this.fpsElement document.getElementById(fps)!; this.memoryElement document.getElementById(memory)!; this.chunksElement document.getElementById(chunks)!; this.positionElement document.getElementById(position)!; } update(cameraPosition: THREE.Vector3, chunkCount: number): void { this.frames; const currentTime performance.now(); if (currentTime this.lastTime 1000) { const fps Math.round((this.frames * 1000) / (currentTime - this.lastTime)); this.fpsElement.textContent fps.toString(); // 内存使用如果浏览器支持 if (performance.memory) { const memoryMB Math.round(performance.memory.usedJSHeapSize / 1048576); this.memoryElement.textContent ${memoryMB} MB; } this.frames 0; this.lastTime currentTime; } this.chunksElement.textContent chunkCount.toString(); this.positionElement.textContent ${Math.round(cameraPosition.x)}, ${Math.round(cameraPosition.y)}, ${Math.round(cameraPosition.z)}; } }10.2 调试工具添加开发者调试功能// src/debug/DebugTools.ts class DebugTools { private scene: THREE.Scene; private helpers: THREE.Object3D[] []; constructor(scene: THREE.Scene) { this.scene scene; this.setupDebugControls(); } private setupDebugControls(): void { document.addEventListener(keydown, (event) { if (event.code F3) { this.toggleDebugInfo(); } else if (event.code F1) { this.toggleWireframe(); } }); } toggleWireframe(): void { this.scene.traverse((object) { if (object instanceof THREE.Mesh) { if (Array.isArray(object.material)) { object.material.forEach(material { material.wireframe !material.wireframe; }); } else { object.material.wireframe !object.material.wireframe; } } }); } addChunkBoundingBox(chunk: Chunk): void { const boxHelper new THREE.BoxHelper(chunk.mesh, 0xffff00); this.scene.add(boxHelper); this.helpers.push(boxHelper); } }11. 构建与部署11.1 Webpack配置优化构建配置// webpack.config.js const path require(path); module.exports { entry: ./src/main.ts, module: { rules: [ { test: /\.ts$/, use: