云计算百科
云计算领域专业知识百科平台

cannon-es 使用指南

cannon-es 使用指南

cannon-es 是一个轻量级的 3D 物理引擎,专为 Web 环境设计。它是经典 cannon.js 的现代分支,支持 ESM 模块化、TypeScript 类型安全,并持续合并社区改进。


一、安装与引入

1.1 Node.js / 构建工具环境

yarn add cannon-es
# 或
npm install cannon-es

// 按需引入(推荐,支持 tree shaking)
import { World, Body, Box, Sphere, Plane, Vec3 } from 'cannon-es'

// 或整体导入
import * as CANNON from 'cannon-es'

1.2 纯 HTML / CDN 引入(无需 Node.js)

cannon-es 原生是 ESM 模块,不提供全局 CANNON 对象,需要用 <script type="importmap"> 或直接通过 ESM CDN 引入。

方法一:使用 importmap(推荐)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script type="importmap">
{
"imports": {
"cannon-es": "https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.js"
}
}
</script>
<script type="module">
import { World, Body, Box, Sphere, Plane, Vec3 } from 'cannon-es'

// 你的物理代码…
const world = new World()
world.gravity.set(0, 9.82, 0)
console.log('cannon-es 已加载')
</script>
</body>
</html>

方法二:直接使用 CDN URL(无需 importmap)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script type="module">
import { World, Body, Box, Sphere, Plane, Vec3 } from 'https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.js'

const world = new World()
world.gravity.set(0, 9.82, 0)
console.log('cannon-es 已加载')
</script>
</body>
</html>


二、核心概念

2.1 物理世界 (World)

世界是所有物理对象的容器,管理重力、碰撞检测和约束求解。

import { World, Vec3 } from 'cannon-es'

// 创建世界
const world = new World()

// 设置重力(默认 (0, -9.82, 0))
world.gravity.set(0, 9.82, 0)

// 允许物体休眠(提升性能)
world.allowSleep = true

// 设置求解器迭代次数(精度与性能的权衡,默认 10)
world.solver.iterations = 10

// 设置容差(默认 0.001)
world.solver.tolerance = 0.001

2.2 刚体 (Body)

刚体是物理模拟的基本单位,包含质量、形状和位置等信息。

import { Body, Box, Vec3 } from 'cannon-es'

// 创建刚体
const body = new Body({
mass: 1, // 质量(0 为静态物体)
position: new Vec3(0, 5, 0), // 初始位置
velocity: new Vec3(0, 0, 0), // 初始速度
angularVelocity: new Vec3(0, 0, 0), // 初始角速度
shape: new Box(new Vec3(1, 1, 1)), // 可直接传入形状
})

// 或使用 setter
body.mass = 1
body.position.set(0, 5, 0)

// 添加形状
const boxShape = new Box(new Vec3(0.5, 0.5, 0.5))
body.addShape(boxShape)

// 添加球体形状
import { Sphere } from 'cannon-es'
const sphereShape = new Sphere(0.5)
body.addShape(sphereShape)

// 将刚体添加到世界
world.addBody(body)

Body 常用属性
属性类型说明
mass number 质量,0 表示静态
position Vec3 位置
quaternion Quaternion 旋转(四元数)
velocity Vec3 线速度
angularVelocity Vec3 角速度
force Vec3 合力
torque Vec3 合力矩
type BodyType 刚体类型:DYNAMIC、STATIC、KINEMATIC
shape Shape 碰撞形状
material Material 材质属性

2.3 碰撞形状 (Shape)

cannon-es 支持多种碰撞形状:

import { Box, Sphere, Plane, Cylinder, Trimesh, Cone } from 'cannon-es'

// 盒子:半长宽高
const box = new Box(new Vec3(0.5, 0.5, 0.5))

// 球体:半径
const sphere = new Sphere(0.5)

// 平面:无限大平面
const plane = new Plane()

// 圆柱体:半径、高度、分段数
const cylinder = new Cylinder(0.5, 0.5, 1, 8)

// 圆锥体:半径、高度、分段数
const cone = new Cone(0.5, 1, 8)

// 三角网格(复杂形状)
const vertices = [0,0,0, 1,0,0, 0,1,0] // 顶点数组
const indices = [0,1,2] // 索引数组
const trimesh = new Trimesh(vertices, indices)

2.4 材质 (Material) 与接触材质 (ContactMaterial)

材质定义了物体的物理属性(摩擦、恢复系数等)。

import { Material, ContactMaterial } from 'cannon-es'

// 创建材质
const groundMaterial = new Material('ground')
const ballMaterial = new Material('ball')

// 设置材质属性
groundMaterial.friction = 0.3 // 摩擦系数
groundMaterial.restitution = 0.0 // 弹性系数(反弹)

ballMaterial.friction = 0.1
ballMaterial.restitution = 0.8

// 创建接触材质(定义两种材质之间的碰撞行为)
const contactMat = new ContactMaterial(groundMaterial, ballMaterial, {
friction: 0.3,
restitution: 0.7,
})

world.addContactMaterial(contactMat)

// 将材质赋给刚体
const groundBody = new Body({ mass: 0 })
groundBody.material = groundMaterial

const ballBody = new Body({ mass: 1 })
ballBody.material = ballMaterial


三、基本操作

3.1 添加物体到世界

// 创建地面(静态)
const groundBody = new Body({ mass: 0 })
groundBody.addShape(new Plane())
groundBody.quaternion.setFromAxisAngle(new Vec3(1, 0, 0), Math.PI / 2)
world.addBody(groundBody)

// 创建球体(动态)
const sphereBody = new Body({ mass: 1, position: new Vec3(0, 5, 0) })
sphereBody.addShape(new Sphere(0.5))
world.addBody(sphereBody)

// 创建立方体(动态)
const boxBody = new Body({ mass: 1, position: new Vec3(2, 5, 0) })
boxBody.addShape(new Box(new Vec3(0.5, 0.5, 0.5)))
world.addBody(boxBody)

3.2 施加力与冲量

// 施加力(持续作用)
body.applyForce(new Vec3(0, 10, 0), body.position)

// 施加冲量(瞬间作用)
body.applyImpulse(new Vec3(0, 5, 0), body.position)

// 施加局部力
body.applyLocalForce(new Vec3(0, 10, 0), new Vec3(0, 0, 0))

// 施加局部冲量
body.applyLocalImpulse(new Vec3(0, 5, 0), new Vec3(0, 0, 0))

// 设置速度
body.velocity.set(0, 10, 0)
body.angularVelocity.set(0, 1, 0)

3.3 更新物理世界

// 固定时间步长(推荐)
const fixedTimeStep = 1 / 60
const maxSubSteps = 3

function animate() {
requestAnimationFrame(animate)

// 步进物理世界
world.step(fixedTimeStep, undefined, maxSubSteps)

// 获取刚体位置用于渲染
const pos = body.position
const quat = body.quaternion
// 更新 Three.js / Babylon.js / 其他渲染器…
}

3.4 移除物体

world.removeBody(body)

// 移除接触材质
world.removeContactMaterial(contactMat)

// 清空所有物体
world.bodies.forEach(b => world.removeBody(b))


四、碰撞检测与事件

4.1 碰撞事件

// 碰撞开始
world.addEventListener('postStep', () => {
// 每次步进后检查碰撞
})

// 更精确的碰撞事件监听
body.addEventListener('collide', (event) => {
const { body: otherBody, contact } = event

console.log('碰撞发生!')
console.log('碰撞物体:', otherBody)
console.log('碰撞点:', contact.bi.position)
console.log('法线方向:', contact.ni)

// 获取碰撞冲击力
const impulse = contact.getImpactVelocityAlongNormal()
if (Math.abs(impulse) > 5) {
console.log('强力碰撞!')
}
})

4.2 碰撞检测查询(射线检测)

import { Ray, RaycastResult } from 'cannon-es'

// 创建射线
const ray = new Ray()
ray.from.set(0, 10, 0)
ray.to.set(0, 10, 0)

// 执行射线检测
const result = new RaycastResult()
ray.intersectWorld(world, result)

if (result.hasHit) {
console.log('射线击中:', result.body)
console.log('击中点:', result.hitPointWorld)
console.log('法线:', result.hitNormalWorld)
}

4.3 重叠检测

// 检测两个形状是否重叠
const overlap = body1.overlaps(body2)
if (overlap) {
console.log('两个物体重叠!')
}

// 检测世界中的所有重叠(需要启用碰撞检测)
world.bodies.forEach((b1, i) => {
for (let j = i + 1; j < world.bodies.length; j++) {
const b2 = world.bodies[j]
if (b1.overlaps(b2)) {
console.log(`${b1.id}${b2.id} 重叠`)
}
}
})


五、约束 (Constraint)

约束用于限制两个刚体之间的相对运动。

import { PointToPointConstraint, HingeConstraint, ConeTwistConstraint, DistanceConstraint } from 'cannon-es'

// 点对点约束(铰链/球关节)
const p2p = new PointToPointConstraint(bodyA, new Vec3(0, 0, 0), bodyB, new Vec3(1, 0, 0))
world.addConstraint(p2p)

// 距离约束(保持固定距离)
const distance = new DistanceConstraint(bodyA, bodyB, { distance: 2 })
world.addConstraint(distance)

// 铰链约束(类似门轴)
const hinge = new HingeConstraint(bodyA, bodyB, {
pivotA: new Vec3(0, 0, 0),
axisA: new Vec3(0, 1, 0),
pivotB: new Vec3(0, 0, 0),
axisB: new Vec3(0, 1, 0),
})
world.addConstraint(hinge)

// 圆锥-扭转约束(类似肩膀关节)
const coneTwist = new ConeTwistConstraint(bodyA, bodyB, {
pivotA: new Vec3(0, 0, 0),
axisA: new Vec3(0, 1, 0),
pivotB: new Vec3(0, 0, 0),
axisB: new Vec3(0, 1, 0),
angle: Math.PI / 4,
})
world.addConstraint(coneTwist)

// 移除约束
world.removeConstraint(p2p)


六、高级功能

6.1 触发器 (Trigger)

通过检测重叠实现触发器效果。

// 设置一个触发器区域(静态,不可见)
const triggerBody = new Body({ mass: 0, position: new Vec3(0, 1, 0) })
triggerBody.addShape(new Box(new Vec3(2, 2, 2)))
triggerBody.collisionFilterGroup = 2
triggerBody.collisionFilterMask = 1
world.addBody(triggerBody)

// 在碰撞事件中检测
triggerBody.addEventListener('collide', (event) => {
const other = event.body
if (other.id !== triggerBody.id) {
console.log('触发器被触发!')
// 执行触发逻辑:开门、得分、传送等
}
})

6.2 碰撞过滤

// 设置碰撞组和掩码(按位运算)
const body1 = new Body()
body1.collisionFilterGroup = 1 // 属于组 1
body1.collisionFilterMask = 2 | 4 // 与组 2 或 4 碰撞

const body2 = new Body()
body2.collisionFilterGroup = 2
body2.collisionFilterMask = 1 // 与组 1 碰撞

const body3 = new Body()
body3.collisionFilterGroup = 4
body3.collisionFilterMask = 0 // 不与任何组碰撞

6.3 运动学物体

// 运动学物体:位置由代码控制,但能推动其他动态物体
const kinematicBody = new Body({
mass: 0,
type: Body.KINEMATIC,
position: new Vec3(0, 2, 0),
})
kinematicBody.addShape(new Box(new Vec3(1, 1, 1)))
world.addBody(kinematicBody)

// 更新位置(每帧调用)
kinematicBody.position.x += 0.01
kinematicBody.velocity.set(0.01, 0, 0) // 必须设置速度使碰撞有效

6.4 休眠 (Sleep)

// 全局开启休眠
world.allowSleep = true

// 设置休眠参数
world.sleepTimeLimit = 0.5 // 静止多久后休眠(秒)
world.sleepSpeedLimit = 0.1 // 速度低于此值视为静止

// 单个物体设置
body.sleepSpeedLimit = 0.05
body.sleepTimeLimit = 0.5

// 手动唤醒
body.wakeUp()

// 强制休眠
body.sleep()


七、完整示例

7.1 基础 Falling Ball 示例

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>cannon-es 基础示例</title>
</head>
<body>
<script type="importmap">
{
"imports": {
"cannon-es": "https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.js"
}
}
</script>
<script type="module">
import { World, Body, Box, Sphere, Plane, Vec3, Material, ContactMaterial } from 'cannon-es'

// 1. 创建世界
const world = new World()
world.gravity.set(0, 9.82, 0)

// 2. 创建材质
const groundMat = new Material('ground')
const ballMat = new Material('ball')

const contactMat = new ContactMaterial(groundMat, ballMat, {
friction: 0.3,
restitution: 0.5,
})
world.addContactMaterial(contactMat)

// 3. 创建地面
const ground = new Body({ mass: 0 })
ground.addShape(new Plane())
ground.quaternion.setFromAxisAngle(new Vec3(1, 0, 0), Math.PI / 2)
ground.material = groundMat
world.addBody(ground)

// 4. 创建多个球体
const balls = []
for (let i = 0; i < 5; i++) {
const ball = new Body({
mass: 1,
position: new Vec3(i 2, 3 + i * 0.5, 0),
})
ball.addShape(new Sphere(0.3))
ball.material = ballMat
world.addBody(ball)
balls.push(ball)
}

// 5. 创建盒子
const box = new Body({
mass: 1,
position: new Vec3(2, 4, 0),
})
box.addShape(new Box(new Vec3(0.4, 0.4, 0.4)))
world.addBody(box)

// 6. 物理循环
const fixedTimeStep = 1 / 60
const maxSubSteps = 3

function physicsLoop() {
requestAnimationFrame(physicsLoop)
world.step(fixedTimeStep, undefined, maxSubSteps)

// 输出球体位置
balls.forEach((b, i) => {
if (i === 0) {
console.log(`球体位置: (${b.position.x.toFixed(2)}, ${b.position.y.toFixed(2)}, ${b.position.z.toFixed(2)})`)
}
})
}

physicsLoop()

// 7. 施加冲量演示(3秒后)
setTimeout(() => {
balls[0].applyImpulse(new Vec3(2, 5, 0), balls[0].position)
console.log('冲量已施加!')
}, 3000)

console.log('物理模拟已启动!')
</script>
</body>
</html>

7.2 完整 HTML 示例(带可视化)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>cannon-es 完整示例</title>
<style>
body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
#info {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
color: #fff;
background: rgba(0,0,0,0.7);
padding: 10px 20px;
border-radius: 8px;
z-index: 10;
pointer-events: none;
}
</style>
</head>
<body>
<div id="info">cannon-es 物理模拟 | 点击画面施加冲量</div>

<!– 使用 importmap 引入 cannon-es –>
<script type="importmap">
{
"imports": {
"cannon-es": "https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.js"
}
}
</script>

<!– 使用 Three.js 进行可视化 –>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
}
}
</script>

<script type="module">
// 导入 cannon-es
import { World, Body, Box, Sphere, Plane, Vec3, Material, ContactMaterial } from 'cannon-es'

// 导入 Three.js
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js'

// ========== 1. 创建物理世界 ==========
const world = new World()
world.gravity.set(0, 9.82, 0)
world.allowSleep = true

// ========== 2. 创建材质 ==========
const groundMat = new Material('ground')
const boxMat = new Material('box')
const ballMat = new Material('ball')

const contactMat = new ContactMaterial(groundMat, boxMat, {
friction: 0.4,
restitution: 0.3,
})
world.addContactMaterial(contactMat)

const contactMatBall = new ContactMaterial(groundMat, ballMat, {
friction: 0.2,
restitution: 0.6,
})
world.addContactMaterial(contactMatBall)

// ========== 3. 创建 Three.js 场景 ==========
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x1a1a2e)

const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100)
camera.position.set(8, 6, 10)
camera.lookAt(0, 2, 0)

const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
document.body.appendChild(renderer.domElement)

const controls = new OrbitControls(camera, renderer.domElement)
controls.target.set(0, 2, 0)
controls.update()

// 光照
const ambientLight = new THREE.AmbientLight(0x404060)
scene.add(ambientLight)

const dirLight = new THREE.DirectionalLight(0xffffff, 1.5)
dirLight.position.set(5, 12, 8)
dirLight.castShadow = true
dirLight.shadow.mapSize.width = 2048
dirLight.shadow.mapSize.height = 2048
scene.add(dirLight)

const fillLight = new THREE.DirectionalLight(0x8888ff, 0.5)
fillLight.position.set(5, 5, 5)
scene.add(fillLight)

// ========== 4. 创建地面 ==========
// 物理地面
const groundBody = new Body({ mass: 0 })
groundBody.addShape(new Plane())
groundBody.quaternion.setFromAxisAngle(new Vec3(1, 0, 0), Math.PI / 2)
groundBody.material = groundMat
world.addBody(groundBody)

// 可视化地面
const groundGeo = new THREE.PlaneGeometry(20, 20)
const groundMat3 = new THREE.MeshStandardMaterial({
color: 0x3a3a5a,
roughness: 0.8,
metalness: 0.1,
})
const groundMesh = new THREE.Mesh(groundGeo, groundMat3)
groundMesh.rotation.x = Math.PI / 2
groundMesh.position.y = 0.01
groundMesh.receiveShadow = true
scene.add(groundMesh)

// 网格辅助线
const gridHelper = new THREE.GridHelper(20, 20, 0x6666aa, 0x444466)
gridHelper.position.y = 0
scene.add(gridHelper)

// ========== 5. 创建物理物体与可视化映射 ==========
const physicsToVisual = new Map()

function createPhysicalObject(body, mesh) {
physicsToVisual.set(body, mesh)
mesh.castShadow = true
mesh.receiveShadow = true
scene.add(mesh)
world.addBody(body)
return { body, mesh }
}

// 5.1 创建盒子堆
const boxPositions = [
[2.5, 0.5, 0],
[1.5, 1.0, 0],
[0.5, 1.5, 0],
[0.5, 2.0, 0],
[1.5, 2.5, 0],
]

boxPositions.forEach((pos, i) => {
const size = 0.5 + Math.random() * 0.2
const body = new Body({
mass: 1,
position: new Vec3(pos[0], pos[1], pos[2]),
shape: new Box(new Vec3(size, size, size)),
material: boxMat,
})
const geo = new THREE.BoxGeometry(size * 2, size * 2, size * 2)
const mat = new THREE.MeshStandardMaterial({
color: new THREE.Color().setHSL(0.6 + i * 0.05, 0.7, 0.5),
roughness: 0.3,
metalness: 0.2,
})
const mesh = new THREE.Mesh(geo, mat)
createPhysicalObject(body, mesh)
})

// 5.2 创建球体
const spherePositions = [
[3, 1.5, 2.5],
[0, 2.0, 3.0],
[3, 1.5, 2.5],
]

spherePositions.forEach((pos, i) => {
const radius = 0.4 + Math.random() * 0.2
const body = new Body({
mass: 1,
position: new Vec3(pos[0], pos[1], pos[2]),
shape: new Sphere(radius),
material: ballMat,
})
const geo = new THREE.SphereGeometry(radius, 32, 32)
const mat = new THREE.MeshStandardMaterial({
color: new THREE.Color().setHSL(0.95 + i * 0.05, 0.8, 0.6),
roughness: 0.2,
metalness: 0.3,
})
const mesh = new THREE.Mesh(geo, mat)
createPhysicalObject(body, mesh)
})

// 5.3 创建静态墙(作为障碍物)
const wallBody = new Body({ mass: 0, position: new Vec3(4, 0.5, 1) })
wallBody.addShape(new Box(new Vec3(0.2, 0.5, 1.5)))
wallBody.material = groundMat
world.addBody(wallBody)

const wallMesh = new THREE.Mesh(
new THREE.BoxGeometry(0.4, 1, 3),
new THREE.MeshStandardMaterial({ color: 0x666688, roughness: 0.7 })
)
wallMesh.position.set(4, 0.5, 1)
wallMesh.castShadow = true
wallMesh.receiveShadow = true
scene.add(wallMesh)

// ========== 6. 鼠标交互 ==========
const raycaster = new THREE.Raycaster()
const pointer = new THREE.Vector2()

renderer.domElement.addEventListener('click', (event) => {
pointer.x = (event.clientX / window.innerWidth) * 2 1
pointer.y = (event.clientY / window.innerHeight) * 2 + 1

raycaster.setFromCamera(pointer, camera)

// 检测点击到的物体
const meshes = Array.from(physicsToVisual.values())
const intersects = raycaster.intersectObjects(meshes)

if (intersects.length > 0) {
const hitMesh = intersects[0].object
let hitBody = null
for (const [body, mesh] of physicsToVisual) {
if (mesh === hitMesh) {
hitBody = body
break
}
}
if (hitBody && hitBody.mass > 0) {
// 施加向上的冲量
const impulse = new Vec3(
(Math.random() 0.5) * 2,
5 + Math.random() * 3,
(Math.random() 0.5) * 2
)
hitBody.applyImpulse(impulse, hitBody.position)
hitBody.wakeUp()

// 闪烁反馈
if (hitMesh.material) {
const origColor = hitMesh.material.color.clone()
hitMesh.material.color.setHex(0xffffff)
setTimeout(() => {
hitMesh.material.color.copy(origColor)
}, 100)
}
}
}
})

// 键盘:按 R 重置场景
window.addEventListener('keydown', (e) => {
if (e.key === 'r' || e.key === 'R') {
// 移除所有动态物体
const toRemove = []
for (const [body, mesh] of physicsToVisual) {
if (body.mass > 0) {
world.removeBody(body)
scene.remove(mesh)
toRemove.push(body)
}
}
toRemove.forEach(b => physicsToVisual.delete(b))

// 重新创建物体…
// 此处简化为重新加载页面
window.location.reload()
}
})

// ========== 7. 物理与渲染同步 ==========
const fixedTimeStep = 1 / 60
const maxSubSteps = 3

function animate() {
requestAnimationFrame(animate)

// 步进物理
world.step(fixedTimeStep, undefined, maxSubSteps)

// 同步物理到可视化
for (const [body, mesh] of physicsToVisual) {
mesh.position.copy(body.position)
mesh.quaternion.copy(body.quaternion)
}

controls.update()
renderer.render(scene, camera)
}

animate()

// ========== 8. 窗口自适应 ==========
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})

console.log('🚀 cannon-es 完整示例已启动!')
console.log('💡 点击物体施加冲量 | 按 R 重置场景')
</script>
</body>
</html>


八、性能优化

8.1 启用休眠

world.allowSleep = true
world.sleepTimeLimit = 0.5
world.sleepSpeedLimit = 0.1

8.2 减少求解器迭代次数

world.solver.iterations = 5 // 默认 10,降低可提升性能但精度下降

8.3 使用宽阶段碰撞过滤

// 减少不必要的碰撞检测
body.collisionFilterGroup = 1
body.collisionFilterMask = 2 // 只与组 2 碰撞

8.4 批量操作

// 批量添加物体时暂停世界
world.broadphase.useBoundingBoxes = true // 使用 AABB 加速

// 批量添加
const bodies = []
for (let i = 0; i < 100; i++) {
const b = new Body({ mass: 1 })
// … 配置
bodies.push(b)
}
bodies.forEach(b => world.addBody(b))

8.5 降低碰撞检测精度

// 增加容差,减少检测次数
world.solver.tolerance = 0.01 // 默认 0.001

8.6 使用 WASM(实验性)

cannon-es 目前不原生支持 WASM,但可以考虑使用 cannon-es 的 C++ 分支或等待未来更新。


九、常见问题与调试

9.1 物体穿透地面

// 增加求解器迭代次数
world.solver.iterations = 15

// 减小时间步长
world.step(1 / 120, undefined, 5)

// 检查质量是否合理
body.mass = 1 // 质量过大会导致穿透

9.2 物体抖动

// 增加阻尼
body.linearDamping = 0.01
body.angularDamping = 0.01

// 降低求解器容差
world.solver.tolerance = 0.0001

9.3 调试可视化

// 使用 cannon-es-debugger(需要单独安装)
// yarn add cannon-es-debugger

// 或手动绘制碰撞体
import { Body, Vec3 } from 'cannon-es'
// 使用 Three.js 辅助线
const helper = new THREE.BoxHelper(mesh)
scene.add(helper)

9.4 常见错误

错误解决方案
Cannot read property 'quaternion' of undefined 确保 body 已添加到 world
物体飞出去 检查质量是否合理,或约束是否过强
碰撞事件不触发 检查碰撞过滤掩码设置
性能下降 减少物体数量,启用休眠,降低迭代次数

十、API 速查

World

方法/属性说明
world.gravity 重力向量
world.addBody(body) 添加刚体
world.removeBody(body) 移除刚体
world.addConstraint(constraint) 添加约束
world.removeConstraint(constraint) 移除约束
world.addContactMaterial(mat) 添加接触材质
world.removeContactMaterial(mat) 移除接触材质
world.step(dt, time, maxSubSteps) 步进物理
world.bodies 所有刚体数组

Body

方法/属性说明
body.mass 质量
body.position 位置 (Vec3)
body.quaternion 旋转 (Quaternion)
body.velocity 线速度 (Vec3)
body.angularVelocity 角速度 (Vec3)
body.addShape(shape) 添加碰撞形状
body.removeShape(shape) 移除碰撞形状
body.applyForce(force, point) 施加力
body.applyImpulse(impulse, point) 施加冲量
body.wakeUp() 唤醒物体
body.sleep() 休眠物体
body.overlaps(other) 检测重叠

Shape

形状构造函数参数
Box(halfExtents) Vec3 半长宽高
Sphere(radius) number 半径
Plane()
Cylinder(radiusTop, radiusBottom, height, numSegments) 上下半径、高度、分段数
Cone(radius, height, numSegments) 半径、高度、分段数
Trimesh(vertices, indices) 顶点数组、索引数组

十一、资源与参考

  • 官方仓库: https://github.com/pmndrs/cannon-es
  • API 文档: https://pmndrs.github.io/cannon-es/docs/
  • 示例集合: https://github.com/pmndrs/cannon-es/tree/main/examples
  • Three.js 集成: https://github.com/pmndrs/cannon-es/tree/main/examples/threejs

十二、版本历史

版本主要变化
v0.20.0 修复大量 bug,改进 TypeScript 支持
v0.19.0 新增 Trimesh 支持,优化性能
v0.18.0 重构约束系统,改进 API
v0.17.0 初始 ESM 重构版本

结语

cannon-es 是一个功能强大且易于使用的 3D 物理引擎。通过本指南,你应该能够快速上手并构建复杂的物理场景。建议从基础示例开始,逐步探索高级功能。如果遇到问题,可以查阅官方文档或在 GitHub 提交 issue。

Happy Physics Coding! 🚀

赞(0)
未经允许不得转载:网硕互联帮助中心 » cannon-es 使用指南
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!