碰撞事件
两个刚体接触时,物理引擎会向双方实体报告。可以播放音效、施加伤害、生成粒子或改变游戏状态。相关实体的 collision 和 rigidbody 都会发出事件,选择方便的组件监听即可。
事件
| 事件 | 触发时机 | 处理函数参数 |
|---|---|---|
collisionstart | 开始接触的物理步中触发一次 | ContactResult |
contact | 保持接触期间每个物理步 | ContactResult |
collisionend | 停止接触时触发一次 | 对方 Entity |
只有至少一方为动态刚体时才报告接触,静态和运动学彼此不碰撞。与触发体积重叠时发出的是 triggerenter 和 triggerleave。
监听碰撞
使用 on 订阅,会返回 EventHandle。保留句柄,不再需要时调用其 off();也可在组件上调用 off(eventName, callback),传入相同回调,见事件。碰撞音效等一次性反应用 collisionstart,逐步更新接触详情则用 contact:
const handle = entity.collision.on('collisionstart', (result) => {
console.log(`${entity.name} hit ${result.other.name}`);
});
// Later, when the entity is destroyed or the listener is no longer wanted
handle.off();
代码位置取决于开发方式:
- 引擎
- 编辑器
- React
- Web Components
实体具有组件后订阅,或像编辑器标签页所示附加脚本,见脚本编程。
为实体添加 Script 组件,附加在 initialize 中订阅、销毁时取消订阅的脚本:
import { Script } from 'playcanvas';
export class ImpactSound extends Script {
static scriptName = 'impactSound';
initialize() {
const handle = this.entity.collision.on('collisionstart', this.onCollisionStart, this);
this.on('destroy', () => handle.off());
}
onCollisionStart(result) {
// Play the 'hit' slot of a Sound component on the same entity
this.entity.sound.play('hit');
}
}
on 的第三个参数将处理函数中的 this 绑定到脚本实例。
在 <Entity> 内、<Collision> 之后放置组件,等待物理加载后在 effect 中订阅:
import { useEffect } from 'react';
import { useParent, usePhysics } from '@playcanvas/react/hooks';
function ImpactSound() {
const entity = useParent();
const { isPhysicsLoaded } = usePhysics();
useEffect(() => {
if (!isPhysicsLoaded || !entity.collision) return;
const handle = entity.collision.on('collisionstart', (result) => {
console.log(`${entity.name} hit ${result.other.name}`);
});
return () => handle.off();
}, [entity, isPhysicsLoaded]);
return null;
}
通过 <pc-script> 附加编辑器标签页相同的 ImpactSound 脚本,或在应用就绪后从页面 JavaScript 通过元素 entity 属性订阅:
<script type="module">
import { whenReady } from '@playcanvas/web-components';
await whenReady('pc-app');
const { entity } = document.querySelector('pc-entity[name="crate"]');
entity.collision.on('collisionstart', (result) => {
console.log(`${entity.name} hit ${result.other.name}`);
});
</script>
whenReady 用法见程序化访问。
接触数据
collisionstart 和 contact 传入 ContactResult,包含:
other:碰撞的另一个实体。contacts:ContactPoint 数组,对应两个形状的各接触点。
每个接触点包含:
| 属性 | 说明 |
|---|---|
point | 当前实体上的世界空间接触点 |
pointOther | 对方实体上的世界空间接触点 |
localPoint | 当前实体局部空间中的接触点 |
localPointOther | 对方实体局部空间中的接触点 |
normal | 世界空间接触法线,从接触点朝远离对方表面的方向指向 |
impulse | 物理引擎为分离刚体施加的冲量,越大表示撞击越强 |
冲量适合衡量碰撞强度,可据此调整音量或伤害,并忽略轻微擦碰:
entity.collision.on('collisionstart', (result) => {
let strongest = 0;
for (const contact of result.contacts) {
strongest = Math.max(strongest, contact.impulse);
}
if (strongest > 5) {
entity.sound.play('crash');
}
});
场景范围的接触
若要在中央音频或伤害管理器中统一处理,监听刚体组件系统的 contact,而非各实体。每个接触点触发一次,传入同时包含两个实体的 SingleContactResult:
app.systems.rigidbody.on('contact', (result) => {
// result.a and result.b are the two entities
// result.pointA, result.pointB and result.normal are in world space
if (result.impulse > 5) {
console.log(`${result.a.name} and ${result.b.name} collided hard`);
}
});
只要两个刚体接触,contact 每个物理步都会触发,盒子静置在地板上也会持续触发。一次性效果优先使用 collisionstart,并保持 contact 处理函数轻量。
另请参阅
- 触发体积:不会阻挡运动的区域重叠事件。
- 碰撞与触发器:碰撞时播放声音的教程。
- 事件:脚本事件系统。
- CollisionComponent 和 RigidBodyComponent:各组件事件 API。