衝突イベント
2つのRigidBodyが接触すると、物理エンジンはその接触を両方のエンティティに通知します。サウンドを再生したり、ダメージを与えたり、パーティクルを生成したり、ゲームの状態を変えたりして応答できます。イベントは、関係する各エンティティのCollisionコンポーネントとRigidBodyコンポーネントの両方で発火するため、都合のよい方で監視できます。
イベント
| イベント | 発火するタイミング | ハンドラーの引数 |
|---|---|---|
collisionstart | 2つのボディが接触し始めた物理ステップで1回 | ContactResult |
contact | ボディが接触している間、毎物理ステップ | ContactResult |
collisionend | ボディの接触が終わったときに1回 | 相手のEntity |
接触が通知されるのは、2つのボディのうち少なくとも1つがDynamicな場合だけです。StaticなボディとKinematicなボディが互いに衝突することはありません。トリガーボリュームとの重なりでは、これらのイベントの代わりにtriggerenterとtriggerleaveが発火します。
衝突の監視
onで購読するとEventHandleが返されます。そのハンドルを保持し、リスナーが不要になったらハンドルのoff()メソッドを呼び出します。イベントで説明しているように、同じコールバックをコンポーネントのoff(eventName, callback)に渡してもリスナーを解除できます。衝突音のような1回限りの反応にはcollisionstartを、毎ステップ更新される接触の詳細が必要な場合にはcontactを使用します。
const handle = entity.collision.on('collisionstart', (result) => {
console.log(`${entity.name} hit ${result.other.name}`);
});
// 後で、エンティティが破棄されたときやリスナーが不要になったときに
handle.off();
このコードをどこに置くかは、構築方法によって異なります。
- Engine
- Editor
- React
- Web Components
上記のようにエンティティにコンポーネントが揃った後で購読するか、Editorタブに示すようにエンティティにアタッチしたScriptから購読します。スクリプトについてはスクリプティングセクションを参照してください。
エンティティに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) {
// 同じエンティティのSoundコンポーネントの 'hit' スロットを再生する
this.entity.sound.play('hit');
}
}
onの3番目の引数は、ハンドラー内のthisをスクリプトインスタンスにバインドします。
<Entity>の中、その<Collision>の後にコンポーネントを配置し、物理演算の読み込み後にエフェクトで購読します。
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;
}
Editorタブに示したImpactSoundスクリプトを<pc-script>でアタッチするか、アプリの準備ができた後に要素のentityプロパティを通してページのJavaScriptから購読します。
<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は、2つのプロパティを持つContactResultを渡します。
other- このボディが衝突した相手のエンティティ。contacts- ContactPointオブジェクトの配列。2つの形状が触れている点ごとに1つ。
各接触点には次の情報が含まれます。
| プロパティ | 説明 |
|---|---|
point | このエンティティ上の接触点(ワールド空間) |
pointOther | 相手のエンティティ上の接触点(ワールド空間) |
localPoint | このエンティティのローカル空間での接触点 |
localPointOther | 相手のエンティティのローカル空間での接触点 |
normal | ワールド空間での接触法線。接触点において相手のエンティティの表面から離れる方向を指します |
impulse | 物理エンジンがボディを分離するために加えたインパルス。値が大きいほど強い衝突です |
インパルスは、2つのボディがどれだけ強くぶつかったかを表す便利な指標です。サウンドの音量やダメージ量のスケーリングに使ったり、かすっただけの接触を無視したりできます。
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');
}
});
シーン全体の接触
例えば集中管理型のオーディオやダメージのマネージャーのように、すべての接触を1か所で処理するには、個々のエンティティではなくRigidBodyコンポーネントシステムのcontactイベントを監視します。このイベントは接触点ごとに1回発火し、両方のエンティティを示すSingleContactResultを渡します。
app.systems.rigidbody.on('contact', (result) => {
// result.a と result.b が2つのエンティティ
// result.pointA、result.pointB、result.normal はワールド空間
if (result.impulse > 5) {
console.log(`${result.a.name} and ${result.b.name} collided hard`);
}
});
contactは2つのボディが触れている間、毎物理ステップ発火します。床の上に置かれた箱の場合、これは毎フレームということです。1回限りのエフェクトにはcollisionstartを使い、contactのハンドラーは軽く保ってください。
関連情報
- トリガーボリューム - 動きを遮らない領域の重なりイベント
- 衝突とトリガー - 衝突時にサウンドを再生するチュートリアル
- イベント - スクリプティングのイベントシステムの仕組み
- CollisionComponentとRigidBodyComponent - 各コンポーネントのイベントのAPIリファレンス