メインコンテンツまでスキップ

レイキャスティング

PlayCanvas物理エンジンを使用すると、レイキャストを実行できます。レイキャストは、2つの3Dポイント間の直線がコリジョン形状と交差するかどうか、またどこで交差するかを判定するクエリです。レイキャストはRigidBodyコンポーネントシステムapp.systems.rigidbodyのメソッドなので、どの方法で構築しても同じように機能します。必要なのはアプリケーションと2つの点だけです。

レイキャストによるピッキング

レイキャスティングの用途の一つはピッキングです。ユーザーが画面をタッチまたはクリックしてエンティティを選択できます。以下の例では、カメラからポインターの位置を通してレイを飛ばし、最も近くでヒットしたボディを報告します。

Raycast

レイはカメラから始まり、ポインターの位置がカメラのファークリップ面に当たる場所で終わるため、ポインターの下にあるものを通過します。

function pick(screenX, screenY) {
// カメラの位置から...
const from = cameraEntity.getPosition();

// ...ポインターの位置をファークリップ面に投影した点まで
const to = cameraEntity.camera.screenToWorld(screenX, screenY, cameraEntity.camera.farClip);

// 最も近いヒットを返す。なければ null
const result = app.systems.rigidbody.raycastFirst(from, to);
if (result) {
console.log(`You selected ${result.entity.name}`);
}
}

app.mouse.on(pc.EVENT_MOUSEDOWN, (event) => pick(event.x, event.y));
app.touch?.on(pc.EVENT_TOUCHSTART, (event) => pick(event.touches[0].x, event.touches[0].y));

エディターでは、このコードはカメラのエンティティに付けたスクリプトの中に置き、appcameraEntityの代わりにthis.appthis.entityを使います。完全なスクリプトは物理演算によるエンティティのピッキングチュートリアルにあります。ReactではuseApp()が、Web ComponentsではwhenReady('pc-app')がアプリケーションを返します。これらのパターンはボディを動かすを参照してください。

レイキャストの結果

RigidBodyコンポーネントシステムは、2つのクエリメソッドを提供します。

  • raycastFirst(start, end, options)は最も近いヒットを返し、何もヒットしなければnullを返します。
  • raycastAll(start, end, options)はレイに沿ったすべてのヒットの配列を返し、何もヒットしなければ空の配列を返します。sort: trueを渡さない限り配列の順序は不定で、渡すと近い順に並びます。

各ヒットはRaycastResultです。

プロパティ説明
entityコリジョン形状がヒットしたエンティティ
pointヒットしたワールド空間の位置
normalヒット位置におけるワールド空間の表面法線
hitFractionレイ上のヒット位置の割合。startで0、endで1

レイキャストはRigidBodyだけでなくトリガーボリュームにもヒットします。トリガーを除外するには、以下のフィルターのいずれかを使用してください。

レイキャストのフィルタリング

どちらのメソッドもオプションオブジェクトを受け取ります。

オプション説明
filterTagsタグが一致するエンティティのヒットだけを報告します。Tags#hasの引数と同じ書き方を配列の中に記述します。['enemy']はそのタグを必須にし、['enemy', 'boss']は両方を必須にし、[['red', 'blue']]はどちらか一方を必須にします
filterCallbackヒットした各エンティティを受け取り、そのヒットを残す場合にtrueを返す関数
filterCollisionGroupレイのコリジョングループ。各ボディのマスクと照合されます
filterCollisionMaskレイのコリジョンマスク。各ボディのグループと照合されます
sortraycastAllのみ。ヒットを近い順に並べ替えます
// 弾が貫通するすべての敵を、近い順に見つける
const hits = app.systems.rigidbody.raycastAll(from, to, {
filterTags: ['enemy'],
sort: true
});
for (const hit of hits) {
hit.entity.script.health.damage(10 * (1 - hit.hitFraction));
}

// プレイヤー自身のボディ以外をピッキングする
const result = app.systems.rigidbody.raycastFirst(from, to, {
filterCallback: (entity) => entity !== player
});

タグやコールバックによるフィルタリングにはレイに沿ったすべてのヒットが必要なため、これらのオプションを付けたraycastFirstraycastAllと同じ処理を行います。グループとマスクによるフィルタリングは物理エンジン内部で行われるため、より低コストです。タグによる物理レイキャスティングチュートリアルでは、完全なプロジェクトでタグのフィルタリングを紹介しています。

環境の探査

レイキャスティングには他の用途もあります。エンティティはレイキャストを飛ばすことで周囲を探査できます。たとえば、キャラクターが地面に立っているかどうかを判定するには、その位置から真下に短いレイを飛ばし、キャラクター自身以外の何かにヒットするかを確認します。

const from = player.getPosition();
const to = new pc.Vec3(from.x, from.y - 1.1, from.z);
const hit = app.systems.rigidbody.raycastFirst(from, to, {
filterCallback: (entity) => entity !== player
});
const onGround = hit !== null;

関連情報