👨⚕️主页: gis分享者
👨⚕️感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!
👨⚕️收录于专栏:threejs gis工程师
文章目录
- 一、🍀前言
- 1.1 ☘️cannon-es物理引擎
- 1.1.1 ☘️核心特性
- 1.1.2 ☘️安装
- 1.1.3 ☘️使用示例
- 二、🍀生成复杂3D迷宫游戏
- 1. ☘️实现思路
- 2. ☘️代码样例
一、🍀前言
本文详细介绍如何基于threejs在三维场景中生成复杂3D迷宫游戏。亲测可用。希望能帮助到您。一起学习,加油!加油!
1.1 ☘️cannon-es物理引擎
cannon-es 是一个轻量级的 JavaScript 3D 物理引擎,专为网页端设计,能够高效模拟现实世界中的物理效果,如碰撞检测、重力、刚体动力学和约束等。作为经典物理引擎 cannon.js 的现代分支,cannon-es 在保留核心功能的同时,通过优化代码结构、支持模块化(ESM/CJS)和树摇(Tree Shaking),提升了性能和兼容性,更适合现代前端开发环境。
cannon-es 官网
1.1.1 ☘️核心特性
物理模拟
支持重力、碰撞检测、刚体动力学等基础物理效果。
刚体类型
提供动态刚体(受力和碰撞影响)、静态刚体(固定不动)和运动学刚体(通过速度控制,不受力影响)。
碰撞形状
支持多种几何形状,如球体(Sphere)、立方体(Box)、平面(Plane)、凸多面体(ConvexPolyhedron)、粒子(Particle)、高度场(Heightfield)和三角网格(Trimesh)。
约束系统
支持固定约束、距离约束、点对点约束等,用于限制物体间的相对运动。
材质系统
通过定义摩擦系数和反弹系数,模拟不同材质间的交互效果。
性能优化
支持时间步进控制(fixedStep/step),适合需要精确控制的场景。
1.1.2 ☘️安装
通过 npm 或 yarn 安装 cannon-es:
npm install cannon-es # 或 yarn add cannon-es1.1.3 ☘️使用示例
完整示例代码:
import*asCANNONfrom'cannon-es';// 创建物理世界constworld=newCANNON.World({gravity:newCANNON.Vec3(0,-9.82,0),});// 创建动态球体constsphereShape=newCANNON.Sphere(1);constsphereBody=newCANNON.Body({mass:5,shape:sphereShape,position:newCANNON.Vec3(0,10,0),});world.addBody(sphereBody);// 创建静态地面constgroundShape=newCANNON.Plane();constgroundBody=newCANNON.Body({type:CANNON.Body.STATIC,shape:groundShape,});groundBody.quaternion.setFromEuler(-Math.PI/2,0,0);world.addBody(groundBody);// 动画循环functionanimate(){requestAnimationFrame(animate);world.fixedStep();console.log(`球体Y坐标:${sphereBody.position.y}`);}animate();结合 Three.js 渲染:
import*asTHREEfrom'three';// 创建Three.js场景、相机和渲染器constscene=newTHREE.Scene();constcamera=newTHREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000);constrenderer=newTHREE.WebGLRenderer();renderer.setSize(window.innerWidth,window.innerHeight);document.body.appendChild(renderer.domElement);// 创建Three.js球体和地面(与cannon-es中的刚体对应)constsphereGeometry=newTHREE.SphereGeometry(1,32,32);constsphereMaterial=newTHREE.MeshBasicMaterial({color:0xff0000});constsphereMesh=newTHREE.Mesh(sphereGeometry,sphereMaterial);scene.add(sphereMesh);constgroundGeometry=newTHREE.PlaneGeometry(20,20);constgroundMaterial=newTHREE.MeshBasicMaterial({color:0x00ff00,side:THREE.DoubleSide});constgroundMesh=newTHREE.Mesh(groundGeometry,groundMaterial);groundMesh.rotation.x=-Math.PI/2;scene.add(groundMesh);camera.position.z=15;// 动画循环中更新Three.js物体位置functionanimate(){requestAnimationFrame(animate);// 推进物理模拟world.fixedStep();// 更新Three.js球体位置sphereMesh.position.copy(sphereBody.position);sphereMesh.quaternion.copy(sphereBody.quaternion);renderer.render(scene,camera);}animate();二、🍀生成复杂3D迷宫游戏
1. ☘️实现思路
集成了 Three.js(渲染)、Cannon-es(物理引擎)以及一个自动生成的迷宫算法。具体代码参考代码样例。可以直接运行。
2. ☘️代码样例
<!DOCTYPEhtml><htmllang="zh"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>三维迷宫</title><style>body{margin:0;overflow:hidden;background-color:#1a1a1a;font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;}#game-ui{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;flex-direction:column;background:rgba(0,0,0,0.7);color:white;z-index:10;pointer-events:auto;}h1{font-size:3rem;margin-bottom:10px;text-shadow:0 0 10px #00d2ff;}p{font-size:1.2rem;color:#ccc;}#start-btn{margin-top:20px;padding:15px 40px;font-size:1.5rem;background:#00d2ff;border:none;border-radius:30px;cursor:pointer;color:#000;font-weight:bold;transition:transform 0.1s;}#start-btn:hover{transform:scale(1.05);background:#5be0ff;}#instructions{position:absolute;bottom:20px;left:20px;color:rgba(255,255,255,0.5);pointer-events:none;}</style><!-- 配置 Import Map 以从 CDN 加载模块 --><scripttype="importmap">{"imports":{"three":"https://esm.sh/three@0.160.0","three/addons/":"https://esm.sh/three@0.160.0/examples/jsm/","cannon-es":"https://esm.sh/cannon-es@0.20.0"}}</script></head><body><!-- UI 界面 --><divid="game-ui"><h1>物理迷宫</h1><p>使用 W A S D 或 方向键 控制小球到达绿色区域</p><buttonid="start-btn">开始游戏</button></div><divid="instructions">WASD 移动 | R 重置</div><scripttype="module">import*asTHREEfrom'three';import*asCANNONfrom'cannon-es';// --- 全局变量 ---letscene,camera,renderer;letworld;letballMesh,ballBody;letlastCallTime;letmazeWidth=15;// 迷宫宽度 (必须是奇数)letmazeHeight=15;// 迷宫高度 (必须是奇数)constwallSize=2;constinput={w:false,a:false,s:false,d:false};letisGameRunning=false;letgoalPosition=newTHREE.Vector3();// --- 1. 迷宫生成算法 (深度优先搜索) ---functiongenerateMaze(width,height){// 初始化全为墙 (1)constmap=Array(height).fill().map(()=>Array(width).fill(1));functioncarve(x,y){constdirections=[[0,-2],[0,2],[-2,0],[2,0]// 上下左右].sort(()=>Math.random()-0.5);// 随机打乱方向directions.forEach(([dx,dy])=>{constnx=x+dx;constny=y+dy;if(nx>0&&nx<width&&ny>0&&ny<height&&map[ny][nx]===1){map[y+dy/2][x+dx/2]=0;// 打通中间的墙map[ny][nx]=0;// 打通目标点carve(nx,ny);}});}// 起点map[1][1]=0;carve(1,1);// 确保终点附近是空的map[height-2][width-2]=0;returnmap;}// --- 2. 初始化 Three.js 和 Cannon.js ---functioninit(){// 渲染器renderer=newTHREE.WebGLRenderer({antialias:true});renderer.setSize(window.innerWidth,window.innerHeight);renderer.shadowMap.enabled=true;renderer.shadowMap.type=THREE.PCFSoftShadowMap;document.body.appendChild(renderer.domElement);// 场景scene=newTHREE.Scene();scene.background=newTHREE.Color(0x202025);scene.fog=newTHREE.Fog(0x202025,10,50);// 摄像机camera=newTHREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,100);camera.position.set(0,20,10);// 灯光constambientLight=newTHREE.AmbientLight(0xffffff,0.4);scene.add(ambientLight);constdirLight=newTHREE.DirectionalLight(0xffffff,0.8);dirLight.position.set(10,20,10);dirLight.castShadow=true;dirLight.shadow.camera.top=30;dirLight.shadow.camera.bottom=-30;dirLight.shadow.camera.left=-30;dirLight.shadow.camera.right=30;dirLight.shadow.mapSize.width=2048;dirLight.shadow.mapSize.height=2048;scene.add(dirLight);// 物理世界world=newCANNON.World();world.gravity.set(0,-20,0);// 增加重力让手感更沉稳// 物理材质constdefaultMaterial=newCANNON.Material('default');constdefaultContact=newCANNON.ContactMaterial(defaultMaterial,defaultMaterial,{friction:0.4,restitution:0.2,// 低反弹});world.addContactMaterial(defaultContact);// 地板constfloorGeo=newTHREE.PlaneGeometry(100,100);constfloorMat=newTHREE.MeshStandardMaterial({color:0x333333});constfloorMesh=newTHREE.Mesh(floorGeo,floorMat);floorMesh.rotation.x=-Math.PI/2;floorMesh.receiveShadow=true;scene.add(floorMesh);constfloorBody=newCANNON.Body({mass:0,shape:newCANNON.Plane(),material:defaultMaterial});floorBody.quaternion.setFromEuler(-Math.PI/2,0,0);world.addBody(floorBody);// 生成迷宫createLevel();// 事件监听window.addEventListener('resize',onWindowResize);window.addEventListener('keydown',(e)=>handleKey(e,true));window.addEventListener('keyup',(e)=>handleKey(e,false));// 开始循环requestAnimationFrame(animate);}functioncreateLevel(){// 清理旧物体 (如果需要重置逻辑,这里可以扩展)constmap=generateMaze(mazeWidth,mazeHeight);// 墙壁材质constwallGeo=newTHREE.BoxGeometry(wallSize,wallSize*1.5,wallSize);constwallMat=newTHREE.MeshStandardMaterial({color:0x4a90e2});// 物理墙壁形状constwallShape=newCANNON.Box(newCANNON.Vec3(wallSize/2,(wallSize*1.5)/2,wallSize/2));constoffsetX=-(mazeWidth*wallSize)/2;constoffsetZ=-(mazeHeight*wallSize)/2;for(letz=0;z<mazeHeight;z++){for(letx=0;x<mazeWidth;x++){constposX=x*wallSize+offsetX;constposZ=z*wallSize+offsetZ;if(map[z][x]===1){// 1. Three.js Meshconstmesh=newTHREE.Mesh(wallGeo,wallMat);mesh.position.set(posX,wallSize*0.75,posZ);mesh.castShadow=true;mesh.receiveShadow=true;scene.add(mesh);// 2. Cannon Bodyconstbody=newCANNON.Body({mass:0});body.addShape(wallShape);body.position.set(posX,wallSize*0.75,posZ);world.addBody(body);}}}// 创建终点区域 (视觉效果)constgoalX=(mazeWidth-2)*wallSize+offsetX;constgoalZ=(mazeHeight-2)*wallSize+offsetZ;goalPosition.set(goalX,0,goalZ);constgoalGeo=newTHREE.BoxGeometry(wallSize,0.1,wallSize);constgoalMat=newTHREE.MeshStandardMaterial({color:0x00ff00,emissive:0x004400});constgoalMesh=newTHREE.Mesh(goalGeo,goalMat);goalMesh.position.set(goalX,0.05,goalZ);scene.add(goalMesh);// 放置光柱在终点constlight=newTHREE.PointLight(0x00ff00,1,10);light.position.set(goalX,2,goalZ);scene.add(light);// 创建玩家小球createPlayer((1*wallSize)+offsetX,(1*wallSize)+offsetZ);}functioncreatePlayer(x,z){constradius=0.6;// VisualconstballGeo=newTHREE.SphereGeometry(radius,32,32);constballMat=newTHREE.MeshStandardMaterial({color:0xff3333,roughness:0.4,metalness:0.3});ballMesh=newTHREE.Mesh(ballGeo,ballMat);ballMesh.castShadow=true;scene.add(ballMesh);// Physicsconstshape=newCANNON.Sphere(radius);ballBody=newCANNON.Body({mass:5,shape:shape,position:newCANNON.Vec3(x,5,z),linearDamping:0.3,// 阻尼,防止无限滚动angularDamping:0.3});world.addBody(ballBody);}// --- 3. 控制与逻辑 ---functionhandleKey(e,pressed){if(e.key.toLowerCase()==='w'||e.key==='ArrowUp')input.w=pressed;if(e.key.toLowerCase()==='a'||e.key==='ArrowLeft')input.a=pressed;if(e.key.toLowerCase()==='s'||e.key==='ArrowDown')input.s=pressed;if(e.key.toLowerCase()==='d'||e.key==='ArrowRight')input.d=pressed;if(e.key.toLowerCase()==='r'&&pressed)location.reload();// 简单的重置}functionupdatePhysics(){if(!ballBody)return;// 施加力constforce=40;constvec=newCANNON.Vec3(0,0,0);// 相机是斜着的,所以需要稍微转换一下输入方向,这里简化为直接对应世界坐标if(input.w)vec.z-=force;if(input.s)vec.z+=force;if(input.a)vec.x-=force;if(input.d)vec.x+=force;ballBody.applyForce(vec,ballBody.position);// 检查是否掉落if(ballBody.position.y<-10){resetBall();}// 检查胜利constdist=ballBody.position.distanceTo(newCANNON.Vec3(goalPosition.x,goalPosition.y,goalPosition.z));if(dist<1.5){showWin();}}functionresetBall(){ballBody.position.set(goalPosition.x*-1+4,5,goalPosition.z*-1+4);// 简易回到起点附近ballBody.velocity.set(0,0,0);ballBody.angularVelocity.set(0,0,0);}functionshowWin(){if(!isGameRunning)return;isGameRunning=false;document.getElementById('game-ui').style.display='flex';document.querySelector('h1').innerText="你赢了!";document.getElementById('start-btn').innerText="再玩一次";}functiononWindowResize(){camera.aspect=window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth,window.innerHeight);}// --- 4. 游戏循环 ---functionanimate(time){requestAnimationFrame(animate);if(!isGameRunning){// 暂停时也可以渲染,但不更新物理renderer.render(scene,camera);return;}constdt=lastCallTime?(time-lastCallTime)/1000:1/60;lastCallTime=time;// 物理步进 (固定时间步长以保持稳定性)world.step(1/60);updatePhysics();// 同步 Mesh 和 Bodyif(ballMesh&&ballBody){ballMesh.position.copy(ballBody.position);ballMesh.quaternion.copy(ballBody.quaternion);// 摄像机平滑跟随constoffset=newTHREE.Vector3(0,18,12);consttargetPos=newTHREE.Vector3(ballMesh.position.x,0,ballMesh.position.z).add(offset);camera.position.lerp(targetPos,0.1);camera.lookAt(ballMesh.position);}renderer.render(scene,camera);}// --- 5. UI 交互 ---conststartBtn=document.getElementById('start-btn');startBtn.addEventListener('click',()=>{document.getElementById('game-ui').style.display='none';if(document.querySelector('h1').innerText==="你赢了!"){location.reload();}isGameRunning=true;lastCallTime=performance.now();});// 启动初始化init();</script></body></html>效果如下