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! 🚀
网硕互联帮助中心


评论前必须登录!
注册