碰撞与碰撞组
一、核心知识点讲解
在 Cannon.js 中,我们可以使用 碰撞过滤 来控制哪些物体可以彼此发生碰撞。主要依赖以下两个属性:
| 属性 | 类型 | 说明 |
|---|---|---|
collisionFilterGroup | Number | 表示刚体所属的组(可以多个组按位或组合) |
collisionFilterMask | Number | 表示刚体可以与哪些组发生碰撞(按位与判断) |
类似 bitmask 位运算控制。比如:
- A 属于组 1,只想和组 2 碰撞,则:
A.collisionFilterGroup = 1A.collisionFilterMask = 2
二、示例:三个方块,控制不同的碰撞组
- 地面:属于组 1,允许与组 2 和组 4 碰撞
- 红色方块:属于组 2,只碰地面
- 绿色方块:属于组 4,只碰地面
- 蓝色方块:属于组 8,不碰任何人
示例代码
<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, 8, 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
// ------------------- 光照 -------------------
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 groundShape = new CANNON.Plane()
const groundBody = new CANNON.Body({
mass: 0,
shape: groundShape,
collisionFilterGroup: 0b0001, // 属于组 1
collisionFilterMask: 0b1111 // 可与所有组碰撞
})
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0)
world.addBody(groundBody)
const groundGeo = new THREE.PlaneGeometry(20, 20)
const groundMat = new THREE.MeshStandardMaterial({ color: 0x888888, side: THREE.DoubleSide })
const groundMesh = new THREE.Mesh(groundGeo, groundMat)
groundMesh.rotation.x = -Math.PI / 2
scene.add(groundMesh)
// ------------------- 创建球体函数 -------------------
function createBall(color, x, group, mask) {
const radius = 0.5
const geometry = new THREE.SphereGeometry(radius, 32, 32)
const material = new THREE.MeshStandardMaterial({ color })
const mesh = new THREE.Mesh(geometry, material)
mesh.position.set(x, 5, 0)
scene.add(mesh)
const shape = new CANNON.Sphere(radius)
const body = new CANNON.Body({
mass: 1,
shape,
position: new CANNON.Vec3(x, 5, 0),
collisionFilterGroup: group,
collisionFilterMask: mask
})
world.addBody(body)
return { mesh, body }
}
// ------------------- 创建三个球体 -------------------
const redBall = createBall(0xff0000, -4, 0b0010, 0b0001) // 红球:只与地面碰撞
const greenBall = createBall(0x00ff00, 0, 0b0100, 0b1111) // 绿球:与所有碰撞
const blueBall = createBall(0x0000ff, 4, 0b1000, 0b0100) // 蓝球:只与绿球碰撞
const balls = [redBall, greenBall, blueBall] // 避免 undefined
// ------------------- 动画更新 -------------------
const clock = new THREE.Clock()
const fixedTimeStep = 1 / 60
function animate() {
requestAnimationFrame(animate)
const delta = clock.getDelta()
world.step(fixedTimeStep, delta)
for (const { mesh, body } of balls) {
if (mesh && body) {
mesh.position.copy(body.position)
mesh.quaternion.copy(body.quaternion)
}
}
controls.update()
renderer.render(scene, camera)
}
animate()
})
</script>
<template>
<canvas ref="canvasRef"></canvas>
</template>
三、效果说明
- 红色方块和绿色方块都能落在地面上(因为它们的组和掩码与地面对上了)
- 蓝色方块直接穿透地面(它没有任何碰撞掩码匹配地面)
四、小贴士
- 碰撞组默认是
1,所有物体都在组1 - 掩码为
-1时表示与所有组碰撞 - 可用
1 << N来表示不同的位(如1 << 3 = 8)