使用Cannon.js时页面空白,求实现立方体物理效果方案
问题原因与解决方案
你的代码出现页面空白的核心问题有几个:
- 同时混用传统脚本引入和ES模块导入Cannon,且
importmap中cannon-es的路径指向不存在的本地文件,导致模块加载失败 - 引入了不存在的本地工具文件
CannonUtils和CannonDebugRenderer,引发报错 - 创建了Cannon物理世界但未配置关键参数,也没将Three.js物体与物理刚体关联,更没在动画循环中更新物理状态
以下是无需额外文件的完整修正方案,实现立方体的物理下落效果:
修正后的完整代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>My first three.js app</title> <style> body { margin: 0; } </style> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.155.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.155.0/examples/jsm/", "cannon-es": "https://unpkg.com/cannon-es@0.20.0/dist/cannon-es.js" } } </script> </head> <body> <script type="module"> import * as THREE from "three"; import { PointerLockControls } from "three/addons/controls/PointerLockControls.js"; import * as CANNON from "cannon-es"; // 初始化Cannon物理世界 const world = new CANNON.World(); world.gravity.set(0, -9.82, 0); // 设置重力 world.broadphase = new CANNON.SAPBroadphase(world); world.defaultContactMaterial.restitution = 0.3; // 弹性系数 // Three.js场景基础配置 const scene = new THREE.Scene(); scene.background = new THREE.Color(0x87ceeb); const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); camera.position.y = 1; const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; document.body.appendChild(renderer.domElement); // 光照与阴影配置 const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(0, 15, 5); light.castShadow = true; light.shadow.camera.near = 0.1; light.shadow.camera.far = 40; light.shadow.mapSize.set(1024, 1024); light.shadow.camera.left = -10; light.shadow.camera.right = 10; light.shadow.camera.top = 10; light.shadow.camera.bottom = -10; light.shadow.bias = -0.002; scene.add(light); const helper = new THREE.CameraHelper(light.shadow.camera); scene.add(helper); // 创建地面(Three.js模型 + Cannon刚体) const concreteTexture = new THREE.TextureLoader().load( "https://threejsfundamentals.org/threejs/resources/images/wall.jpg" ); const groundMaterial = new THREE.MeshPhysicalMaterial({ map: concreteTexture, clearcoat: 1, clearcoatRoughness: 1, metalness: 0.9, }); const groundGeometry = new THREE.PlaneGeometry(20, 20); const ground = new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true; scene.add(ground); // 地面刚体(质量0表示静态物体) const groundBody = new CANNON.Body({ mass: 0, shape: new CANNON.Plane(), material: new CANNON.Material({ friction: 0.8, restitution: 0.2 }) }); groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0); world.addBody(groundBody); // 创建立方体(Three.js模型 + Cannon刚体) const cubeGeometry = new THREE.BoxGeometry(1, 1, 1); const cubeMaterial = new THREE.MeshPhysicalMaterial({ color: 0x00ff00, map: concreteTexture, clearcoat: 1.0, clearcoatRoughness: 0.2, }); const cube = new THREE.Mesh(cubeGeometry, cubeMaterial); cube.position.y = 5; // 初始位置高于地面 cube.castShadow = true; scene.add(cube); // 立方体刚体(质量>0表示动态物体) const cubeBody = new CANNON.Body({ mass: 1, shape: new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)), // 尺寸对应Three.js几何体的一半 material: new CANNON.Material({ friction: 0.5, restitution: 0.3 }) }); cubeBody.position.copy(cube.position); world.addBody(cubeBody); // 相机控制与动画循环 const controls = new PointerLockControls(camera, document.body); const clock = new THREE.Clock(); let deltaTime = 0; const moveVector = new THREE.Vector3(); const keys = {}; camera.rotation.order = "YXZ"; window.addEventListener("keydown", ev => keys[ev.key] = true); window.addEventListener("keyup", ev => keys[ev.key] = false); function go() { moveVector.set(0, 0, 0); if (keys.w) moveVector.z -= 0.1; if (keys.s) moveVector.z += 0.1; if (keys.a) moveVector.x -= 0.1; if (keys.d) moveVector.x += 0.1; if (keys.c) moveVector.y -= 0.1; if (keys.v) moveVector.y += 0.1; if (keys.m) controls.lock(); if (keys.ArrowRight) camera.rotation.y -= 0.05; if (keys.ArrowLeft) camera.rotation.y += 0.05; if (keys.ArrowUp) camera.rotation.x += 0.05; if (keys.ArrowDown) camera.rotation.x -= 0.05; moveVector.multiplyScalar(0.6); camera.translateX(moveVector.x); camera.translateZ(moveVector.z); camera.translateY(moveVector.y); } function animate() { requestAnimationFrame(animate); deltaTime = clock.getDelta(); // 更新物理世界状态 world.step(1/60, deltaTime, 3); // 同步立方体模型与刚体的位置、旋转 cube.position.copy(cubeBody.position); cube.quaternion.copy(cubeBody.quaternion); go(); renderer.render(scene, camera); } animate(); // 窗口大小自适应 window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); </script> </body> </html>
关键修正点说明
修复Cannon导入:
- 移除传统脚本引入,统一用ES模块导入
cannon-es - 在
importmap中配置正确的CDN路径 - 删除不存在的本地工具文件导入
- 移除传统脚本引入,统一用ES模块导入
配置物理世界:
- 设置重力参数,配置碰撞检测算法和接触材质的弹性、摩擦系数
关联Three.js与Cannon:
- 为每个需要物理效果的物体创建对应Cannon刚体
- 确保刚体形状尺寸与Three.js几何体匹配(如Cannon.Box尺寸是几何体的一半)
- 在动画循环中更新物理状态,并同步模型与刚体的位置、旋转
修复其他报错:
- 初始化
THREE.Clock计算物理步进时间差 - 移除未使用的模块导入
- 优化移动控制的向量重置逻辑
- 初始化
内容的提问来源于stack exchange,提问作者Blaze Branham
相关产品推荐
相关产品推荐

