You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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>

关键修正点说明

  1. 修复Cannon导入:

    • 移除传统脚本引入,统一用ES模块导入cannon-es
    • 在importmap中配置正确的CDN路径
    • 删除不存在的本地工具文件导入
  2. 配置物理世界:

    • 设置重力参数,配置碰撞检测算法和接触材质的弹性、摩擦系数
  3. 关联Three.js与Cannon:

    • 为每个需要物理效果的物体创建对应Cannon刚体
    • 确保刚体形状尺寸与Three.js几何体匹配(如Cannon.Box尺寸是几何体的一半)
    • 在动画循环中更新物理状态,并同步模型与刚体的位置、旋转
  4. 修复其他报错:

    • 初始化THREE.Clock计算物理步进时间差
    • 移除未使用的模块导入
    • 优化移动控制的向量重置逻辑

内容的提问来源于stack exchange,提问作者Blaze Branham

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 18:54:54