05-碰撞与碰撞组

81 阅读2分钟

碰撞与碰撞组


一、核心知识点讲解

Cannon.js 中,我们可以使用 碰撞过滤 来控制哪些物体可以彼此发生碰撞。主要依赖以下两个属性:

属性类型说明
collisionFilterGroupNumber表示刚体所属的组(可以多个组按位或组合)
collisionFilterMaskNumber表示刚体可以与哪些组发生碰撞(按位与判断)

类似 bitmask 位运算控制。比如:

  • A 属于组 1,只想和组 2 碰撞,则:
    • A.collisionFilterGroup = 1
    • A.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