本文详细讲解如何在现代 Web 环境(ES 模块 + importmap)中正确引入并使用 cannon-es 为 Three.js 场景添加刚体物理,解决因版本冲突、加载方式错误或初始化缺失导致的页面空白问题。
本文详细讲解如何在现代 web 环境(es 模块 + importmap)中正确引入并使用 `cannon-es` 为 three.js 场景添加刚体物理,解决因版本冲突、加载方式错误或初始化缺失导致的页面空白问题。
在初学者尝试将物理引擎集成到 Three.js 项目时,最常见的报错现象是:页面完全空白,控制台出现 ReferenceError: CANNON is not defined 或 Cannot instantiate cyclic dependency 等错误,甚至无任何提示直接白屏。这通常并非代码逻辑错误,而是由以下关键原因导致:
✅ 正确做法是:统一使用 cannon-es(推荐 v0.19+),通过 importmap 声明标准化导入路径,并在 requestAnimationFrame 循环中严格按“更新物理 → 同步渲染”顺序执行。
以下是可直接运行的最小可行示例(已精简冗余代码,聚焦物理核心):
<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8" /> <title>Cannon-es + Three.js Physics Demo</title> <style>body { margin: 0; overflow: hidden; }</style> <!-- ✅ 正确声明 importmap:仅保留必需项,cannon-es 指向 CDN 上的 ESM 兼容构建 --> <script type="importmap"> { "imports": { "three": "https://unpkg.com/[email protected]/build/three.module.js", "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/", "cannon-es": "https://cdn.jsdelivr.net/npm/[email protected]/dist/cannon-es.min.js" } } </script></head><body> <script type="module"> import * as THREE from 'three'; import { Clock } from 'three'; import * as CANNON from 'cannon-es'; // ✅ 使用 cannon-es,非 cannon.js // 1️⃣ 初始化物理世界 const world = new CANNON.World(); world.gravity.set(0, -9.82, 0); // 地球重力 // 2️⃣ 配置物理材质(避免穿模、控制摩擦/弹性) const physicsMaterial = new CANNON.Material(); const contactMaterial = new CANNON.ContactMaterial( physicsMaterial, physicsMaterial, { friction: 0.1, restitution: 0.7 } // 摩擦小、弹性适中 ); world.addContactMaterial(contactMaterial); // 3️⃣ 创建地面刚体(mass=0 表示静态) const groundBody = new CANNON.Body({ mass: 0, material: physicsMaterial }); groundBody.addShape(new CANNON.Plane()); groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); world.addBody(groundBody); // 4️⃣ 创建下落立方体刚体 const cubeBody = new CANNON.Body({ mass: 1, material: physicsMaterial }); cubeBody.addShape(new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5))); cubeBody.position.set(0, 5, 0); world.addBody(cubeBody); // 5️⃣ Three.js 渲染层 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 3, 5); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; document.body.appendChild(renderer.domElement); // 立方体 Mesh(视觉表现) const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshStandardMaterial({ color: 0x00ff88 }); const cubeMesh = new THREE.Mesh(geometry, material); cubeMesh.castShadow = true; scene.add(cubeMesh); // 地面 Mesh const groundGeometry = new THREE.PlaneGeometry(20, 20); const groundMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.8, metalness: 0.2 }); const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial); groundMesh.rotation.x = -Math.PI / 2; groundMesh.receiveShadow = true; scene.add(groundMesh); // 光源 const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(5, 10, 7); light.castShadow = true; scene.add(light); // 时间控制器 const clock = new Clock(); // ? 主渲染循环:物理更新 + 渲染同步 function animate() { requestAnimationFrame(animate); const delta = Math.min(clock.getDelta(), 0.1); // 限制最大步长防卡顿 world.step(delta); // ✅ 关键:必须调用!否则物理不演进 // ✅ 同步物理体位置/旋转到 Three.js Mesh cubeMesh.position.copy(cubeBody.position); cubeMesh.quaternion.copy(cubeBody.quaternion); 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>
? 关键注意事项总结:
掌握以上要点后,你不仅能解决“屏幕变白”问题,更能建立起对物理引擎与渲染引擎协同工作的清晰认知——这是构建交互式 3D 应用的基石。