02 - Cannon 引擎基础碰撞讲解
本节目标
- 理解 Cannon.js 中的碰撞原理
- 学习如何让两个刚体发生碰撞
- 配置质量、形状、位置使碰撞真实生效
- 用 Vue3 示例观察碰撞过程
什么是碰撞?
在 Cannon.js 中,碰撞(Collision) 是指两个或多个刚体接触后发生的物理响应。要实现碰撞:
- 两个物体都必须有
shape
- 至少一个物体必须有质量(
mass > 0
) - 物体必须靠近、发生交错
碰撞最小实现逻辑
我们用一个最基本的例子说明:
- 一个球从上往下掉
- 底下是一个静止的平面(地板)
- 当球体接触地板后停止
示例:球落地碰撞+掉落
vue
<template>
<canvas ref="canvasRef"></canvas>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import * as THREE from 'three'
import * as CANNON from 'cannon-es'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
const canvasRef = ref()
onMounted(() => {
// 初始化场景
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 5, 15)
const renderer = new THREE.WebGLRenderer({ canvas: canvasRef.value })
renderer.setSize(window.innerWidth, window.innerHeight)
// 添加轨道控制器
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
// 添加球体 Mesh
const ballGeo = new THREE.SphereGeometry(1)
const ballMat = new THREE.MeshStandardMaterial({ color: 0xff5555 })
const ballMesh = new THREE.Mesh(ballGeo, ballMat)
scene.add(ballMesh)
// 添加地面 Mesh
const groundGeo = new THREE.BoxGeometry(10, 1, 10)
const groundMat = new THREE.MeshStandardMaterial({ color: 0x88cc88 })
const groundMesh = new THREE.Mesh(groundGeo, groundMat)
groundMesh.position.set(0, -0.5, 0)
scene.add(groundMesh)
// 添加灯光
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(10, 20, 10)
scene.add(light)
// 初始化物理世界
const world = new CANNON.World()
world.gravity.set(0, -9.82, 0)
// 创建球体刚体
const ballBody = new CANNON.Body({
mass: 1, // 会受到重力影响
shape: new CANNON.Sphere(1),
position: new CANNON.Vec3(0, 5, 0)
})
world.addBody(ballBody)
// 创建地面刚体(静止)
const groundBody = new CANNON.Body({
type: CANNON.Body.STATIC, // 不会动
shape: new CANNON.Box(new CANNON.Vec3(5, 0.5, 5)),
position: new CANNON.Vec3(0, -0.5, 0)
})
world.addBody(groundBody)
// 动画循环
const clock = new THREE.Clock()
const timeStep = 1 / 60
function animate() {
requestAnimationFrame(animate)
const delta = clock.getDelta()
world.step(timeStep, delta)
// 同步位置
ballMesh.position.copy(ballBody.position)
ballMesh.quaternion.copy(ballBody.quaternion)
controls.update()
renderer.render(scene, camera)
}
animate()
})
</script>

vue
<script setup>
import { onMounted, ref } from 'vue'
import * as THREE from 'three'
import * as CANNON from 'cannon-es'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
const canvasRef = ref()
onMounted(() => {
// 初始化场景
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 5, 15)
const renderer = new THREE.WebGLRenderer({ canvas: canvasRef.value })
renderer.setSize(window.innerWidth, window.innerHeight)
// 添加轨道控制器
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
// 添加球体 Mesh
const ballGeo = new THREE.SphereGeometry(1)
const ballMat = new THREE.MeshStandardMaterial({ color: 0xff5555 })
const ballMesh = new THREE.Mesh(ballGeo, ballMat)
scene.add(ballMesh)
// 添加倾斜地面 Mesh(略微倾斜)
const groundGeo = new THREE.BoxGeometry(10, 1, 10)
const groundMat = new THREE.MeshStandardMaterial({ color: 0x88cc88 })
const groundMesh = new THREE.Mesh(groundGeo, groundMat)
groundMesh.position.set(0, -0.5, 0)
groundMesh.rotation.z = 0.1 // 倾斜 0.1 弧度
scene.add(groundMesh)
// 添加灯光
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(10, 20, 10)
scene.add(light)
// 初始化物理世界
const world = new CANNON.World()
world.gravity.set(0, -9.82, 0)
// 创建球体刚体
const ballBody = new CANNON.Body({
mass: 1,
shape: new CANNON.Sphere(1),
position: new CANNON.Vec3(0, 5, 0)
})
world.addBody(ballBody)
// 创建倾斜地面刚体
const groundBody = new CANNON.Body({
type: CANNON.Body.STATIC,
shape: new CANNON.Box(new CANNON.Vec3(5, 0.5, 5)),
position: new CANNON.Vec3(0, -0.5, 0)
})
groundBody.quaternion.setFromEuler(0, 0, 0.1) // Z 轴倾斜 0.1 弧度
world.addBody(groundBody)
// 动画循环
const clock = new THREE.Clock()
const timeStep = 1 / 60
function animate() {
requestAnimationFrame(animate)
const delta = clock.getDelta()
world.step(timeStep, delta)
ballMesh.position.copy(ballBody.position)
ballMesh.quaternion.copy(ballBody.quaternion)
groundMesh.quaternion.copy(groundBody.quaternion)
controls.update()
renderer.render(scene, camera)
}
animate()
})
</script>
<template>
<canvas ref="canvasRef"></canvas>
</template>

核心知识点说明
1. mass
决定是否受重力影响
mass = 0
(或type: STATIC
) 表示不受重力、不会移动mass > 0
表示刚体受力、能运动
2. shape
决定碰撞体积
- 用
CANNON.Sphere(radius)
创建球体 - 用
CANNON.Box(new Vec3(x,y,z))
创建立方体(地面)
3. position
设置初始位置
- 球体在上方
y=5
- 地面在
y = -0.5
,高度为1(底面居中)
4. step()
每帧推进物理计算
world.step()
计算物理- 然后把位置同步回 Three.js 的 Mesh 上
如何确认碰撞发生了?
在这个例子中,我们还没有使用事件监听,但你可以通过观察:
- 球体从空中落下
- 触碰地面后停止下落
下一节将介绍如何监听碰撞事件,获取更详细的碰撞信息。
小结
- 碰撞需要物体有
shape
,并至少一个有质量 STATIC
类型物体常用于地面、墙体等不可动物体- 每帧调用
step()
推进物理,并同步位置 - 碰撞响应是 Cannon.js 的核心机制之一
下一节:监听碰撞事件与获取碰撞信息