| name | galacean-collider |
| description | 此 Skill 包含了在 Galacean Engine 中创建物理碰撞体的核心模式,涵盖动态碰撞体 (DynamicCollider) 和 静态碰撞体 (StaticCollider) 的实现 |
Galacean Collider
介绍
为了实现各种碰撞场景,需要给实体添加碰撞行为。
1. 核心依赖 (Imports)
在使用物理组件前,需引入以下核心模块:
import {
Entity,
WebGLEngine,
StaticCollider,
DynamicCollider,
BoxColliderShape,
PlaneColliderShape,
SphereColliderShape,
Vector3
} from '@galacean/engine'
2. 物理引擎初始化
在创建 WebGLEngine 时,需要根据需求配置 physics 属性:
import { LitePhysics } from '@galacean/engine-physics-lite';
import { PhysXPhysics, PhysXRuntimeMode } from '@galacean/engine-physics-physx';
const engine = await WebGLEngine.create({
canvas,
physics: new PhysXPhysics(PhysXRuntimeMode.Auto, {
wasmModeUrl: './libs/physx.release.js',
javaScriptModeUrl: './libs/physx.release.downgrade.js'
}),
});
3. 动态碰撞体实现 (Dynamic Collider)
适用场景:受重力影响、需要产生物理反应的物体(如掉落的方块)。
代码模式:
function createDynamicBox(engine: WebGLEngine, position: Vector3, size: Vector3 = new Vector3(1, 1, 1)) {
const entity = new Entity(engine);
entity.transform.position = position;
const dynamicCollider = entity.addComponent(DynamicCollider);
dynamicCollider.mass = 10.0;
const boxShape = new BoxColliderShape();
boxShape.size=size
dynamicCollider.addShape(boxShape);
return entity;
}
4. 静态碰撞体实现 (Static Collider)
适用场景:虽然参与碰撞但不移动的物体(如地面、墙壁、障碍物)。
高级技巧:可以使用一个 StaticCollider 挂载多个 Shape 来构建复杂的环境。
代码模式:
function createStaticEnvironment(rootEntity: Entity) {
const groundEntity = rootEntity.createChild('ground');
const staticCollider = groundEntity.addComponent(StaticCollider);
const planeShape = new PlaneColliderShape();
staticCollider.addShape(planeShape);
const width = 4;
const length = 6;
const height = 100;
const thickness = 0.1;
const frontWall = new BoxColliderShape();
frontWall.size.set(width, height, thickness);
frontWall.position.set(0, 0, length / 2);
staticCollider.addShape(frontWall);
const backWall = new BoxColliderShape();
backWall.size.set(width, height, thickness);
backWall.position.set(0, 0, -length / 2);
staticCollider.addShape(backWall);
return groundEntity;
}
5. 碰撞事件系统 (Collision Events)
5.1 事件类型对比
Galacean 物理系统提供两类碰撞事件:
| 事件类型 | 触发条件 | 参数类型 | 物理反应 | 使用场景 |
|---|
| 碰撞器事件 | 两个碰撞器产生物理碰撞 | Collision | ✅ 有物理效果 | 真实碰撞交互、伤害检测 |
| 触发器事件 | 碰撞器进入触发区域 | ColliderShape | ❌ 无物理效果 | 区域检测、道具拾取 |
关键区别:
- 碰撞器模式(
isTrigger = false):会产生真实的物理碰撞效果,触发 onCollision* 事件
- 触发器模式(
isTrigger = true):仅检测重叠,不产生物理反应,触发 onTrigger* 事件
5.2 碰撞器事件 (Collision Events)
适用场景:需要基于真实物理碰撞计算伤害、检测冲击力度等。
重要概念:
- 碰撞事件回调参数是
Collision 对象,不是 Collider
Collision 对象包含:碰撞形状 (shape)、接触点信息等
- 通过
collision.shape.collider 访问碰撞器,再通过 collider.entity 访问实体
代码模式:
import { Script, Collision, DynamicCollider, Vector3 } from '@galacean/engine';
class DamageDetectionScript extends Script {
private collider!: DynamicCollider;
private health: number = 100;
private lastCollisionTime: number = 0;
private collisionCooldown: number = 0.1;
onAwake() {
this.collider = this.entity.getComponent(DynamicCollider)!;
}
onUpdate(deltaTime: number) {
this.lastCollisionTime += deltaTime;
}
onCollisionEnter(collision: Collision) {
this.handleCollision(collision);
}
onCollisionStay(collision: Collision) {
if (this.lastCollisionTime > this.collisionCooldown) {
this.handleCollision(collision);
}
}
onCollisionExit(collision: Collision) {
console.log('碰撞结束');
}
private handleCollision(collision: Collision) {
const otherCollider = collision.shape.collider;
if (!otherCollider) return;
const otherEntity = otherCollider.entity;
if (!otherEntity) return;
const otherName = otherEntity.name;
if (otherName !== 'bullet' && otherName !== 'enemy') {
return;
}
const otherDynamicCollider = otherCollider instanceof DynamicCollider
? otherCollider
: null;
if (!otherDynamicCollider) return;
const otherVelocity = otherDynamicCollider.linearVelocity;
const myVelocity = this.collider.linearVelocity;
const relativeVelocity = new Vector3(
Math.abs(otherVelocity.x - myVelocity.x),
Math.abs(otherVelocity.y - myVelocity.y),
Math.abs(otherVelocity.z - myVelocity.z)
);
const impactSpeed = Math.sqrt(
relativeVelocity.x * relativeVelocity.x +
relativeVelocity.y * relativeVelocity.y +
relativeVelocity.z * relativeVelocity.z
);
if (impactSpeed > 2.0) {
const damage = (impactSpeed - 2.0) * 10;
this.takeDamage(damage);
this.lastCollisionTime = 0;
}
}
private takeDamage(damage: number) {
this.health -= damage;
console.log(`受到 ${damage.toFixed(1)} 点伤害,剩余生命: ${this.health.toFixed(1)}`);
if (this.health <= 0) {
this.entity.destroy();
}
}
}
5.3 触发器事件 (Trigger Events)
适用场景:区域检测、道具拾取、传送门等不需要物理反应的场景。
关键步骤:
- 将碰撞形状设置为触发器:
shape.isTrigger = true
- 使用
onTrigger* 系列事件
代码模式:
class TriggerScript extends Script {
onTriggerEnter(shape: ColliderShape) {
const entity = shape.collider.entity;
if (entity.name === 'player') {
console.log('玩家进入区域');
}
}
onTriggerStay(shape: ColliderShape) {
}
onTriggerExit(shape: ColliderShape) {
const entity = shape.collider.entity;
if (entity.name === 'player') {
console.log('玩家离开区域');
}
}
}
function createTrigger(engine: WebGLEngine) {
const entity = new Entity(engine);
const collider = entity.addComponent(StaticCollider);
const triggerShape = new BoxColliderShape();
triggerShape.size.set(5, 5, 5);
triggerShape.isTrigger = true;
collider.addShape(triggerShape);
entity.addComponent(TriggerScript);
return entity;
}
5.4 ❌ 反模式:定时轮询检测碰撞
错误做法:
class BadDamageScript extends Script {
private checkTimer: number = 0;
onUpdate(deltaTime: number) {
this.checkTimer += deltaTime;
if (this.checkTimer > 0.1) {
this.checkTimer = 0;
const velocity = this.collider.linearVelocity;
const speed = velocity.length();
if (speed > 5.0) {
this.takeDamage(10);
}
}
}
}
问题:
- ❌ 碰撞发生在检查间隔之间会被遗漏
- ❌ 无法准确获取碰撞时的瞬时速度
- ❌ 无法区分碰撞对象类型
- ❌ 无法获取碰撞接触点信息
正确做法:
class GoodDamageScript extends Script {
onCollisionEnter(collision: Collision) {
this.calculateDamage(collision);
}
}
5.5 关键要点总结
-
参数类型:
onCollision* 接收 Collision 对象
onTrigger* 接收 ColliderShape 对象
-
访问路径:
collision.shape
collision.shape.collider
collision.shape.collider.entity
-
事件触发条件:
- 至少有一方必须是
DynamicCollider
- 双方都需要有
Collider 组件
- 碰撞器模式触发
onCollision*
- 触发器模式触发
onTrigger*
-
性能优化:
- 使用冷却时间避免
onCollisionStay 频繁触发
- 尽早返回(early return)过滤不相关碰撞
- 使用
instanceof 检查碰撞器类型
6. 关键概念速查
| 概念 | 类名 | 描述 |
|---|
| 碰撞体组件 | DynamicCollider | 受力运动。有质量,受重力影响。 |
| StaticCollider | 静止不动。质量视为无限大,不受力。 |
| 碰撞形状 | BoxColliderShape | 盒子形状,拥有 size 属性。 |
| PlaneColliderShape | 无限平面,常用于地面。 |
| SphereColliderShape | 球体,拥有 radius 属性。 |
| 碰撞事件 | onCollisionEnter(collision) | 碰撞开始时触发,参数类型 Collision |
| onCollisionStay(collision) | 碰撞持续时触发,需做频率控制 |
| onCollisionExit(collision) | 碰撞结束时触发 |
| 触发器事件 | onTriggerEnter(shape) | 进入触发区域,参数类型 ColliderShape |
| onTriggerStay(shape) | 停留在触发区域 |
| onTriggerExit(shape) | 离开触发区域 |
| 碰撞信息获取 | collision.shape.collider | 从 Collision 对象获取碰撞器 |
| collision.shape.collider.entity | 从碰撞器获取实体 |
| collider.linearVelocity | 获取碰撞器的线性速度 |
| 复合碰撞 | N/A | 一个 Collider 组件通过 addShape() 添加多个 Shape。 |
7. 最佳实践
✅ 推荐做法
- 使用事件驱动:通过
onCollision* 事件检测碰撞,而非定时轮询
- 类型检查:使用
instanceof 判断碰撞器类型
- 早期返回:在事件处理开始时过滤不相关碰撞
- 冷却控制:为
onCollisionStay 添加冷却时间避免重复计算
- 相对速度:计算伤害时使用相对速度而非绝对速度
❌ 避免的做法
- 定时轮询:不要用
onUpdate 轮询速度来判断碰撞
- 错误参数类型:不要将
Collider 作为 onCollisionEnter 的参数类型
- 错误访问路径:不要直接从
Collision 尝试获取 entity,应该通过 collision.shape.collider.entity
- 忽略类型转换:获取速度前要先检查是否为
DynamicCollider