add-interactivity について
Decentraland シーンにインタラクションを追加するためのスキルで、ポインター、近接、入力、レイキャストの実装を案内します。コライダー要件や距離ルール、イベント処理の注意点も説明します。
主な機能
- 01クリックとホバーのイベント
- 02近接イベントとトリガーエリア
- 03グローバル入力とレイキャスト
- 04コライダー設定ガイド
- 05イベント距離と処理上の注意
活用シーン
- 01シーン内オブジェクトへのクリック操作追加
- 02プレイヤーのエリア進入時に処理を実行
- 03キーボード入力への対応
- 04反応しないインタラクションの調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ decentraland/ sdk-skills --skill add-interactivityインストール後、Claude Code で/add-interactivityを入力して読み込まれたか確認します。
同梱ファイルreferences/input-reference.mdreferences/interactivity-patterns.md
詳細
Decentraland シーンへのインタラクティブ機能の追加
ルール:複合エンティティを取得する — 再作成しない
インタラクティブにするエンティティが assets/scene/main.composite で定義されている場合は、index.ts で名前またはタグを使って検索してください。engine.addEntity() + コンポーネントの作成を呼び出さないでください。そうすると重複が作成されます。
import { engine, pointerEventsSystem, InputAction } from "@dcl/sdk/ecs";
import { EntityNames } from "../assets/scene/entity-names";
export function main() {
// By name (type-safe via auto-generated EntityNames enum)
const door = engine.getEntityOrNullByName(EntityNames.Door_1);
if (door) {
pointerEventsSystem.onPointerDown(
{
entity: door,
opts: { button: InputAction.IA_PRIMARY, hoverText: "Open" },
},
() => {
/* open door */
}
);
}
// By tag (batch operations on groups of composite entities)
const crystals = engine.getEntitiesByTag("Crystal");
for (const crystal of crystals) {
pointerEventsSystem.onPointerDown(
{
entity: crystal,
opts: { button: InputAction.IA_PRIMARY, hoverText: "Collect" },
},
() => {
/* collect crystal */
}
);
}
}
これらの検索は、main() 内、または main() の後に呼び出される関数内で行う必要があります。複合エンティティはその時点より前にはインスタンス化されません。
選択フロー
| 必要な機能 | 方法 | API |
|---|---|---|
| 特定のエンティティのクリック/ホバー | ポインターイベント | pointerEventsSystem.onPointerDown() |
| プレイヤーが近くにいるときのボタン操作(照準不要) | 近接イベント | pointerEventsSystem.onProximityDown() |
| プレイヤーがエリアに入ったことを検知する | トリガーエリア | TriggerArea + triggerAreaEventsSystem |
| フレームごとにキーの状態を確認する | グローバル入力 | inputSystem.isTriggered() / isPressed() |
| 特定方向にあるオブジェクトを検知する | レイキャスト | raycastSystem または Raycast コンポーネント |
| カーソル位置/ロック状態を読み取る | カーソル状態 | PointerLock、PrimaryPointerInfo |
ポインターイベント(クリック/ホバー)
pointerEventsSystem.onPointerDown() を使って、エンティティにクリックハンドラーを追加します。.onPointerUp()、.onPointerHoverEnter()、.onPointerHoverLeave() も利用できます。opts に maxDistance(通常は 8~16m)を設定して操作範囲を制限します。距離はカメラではなくアバターから測定されます(下記「距離に関するルール」を参照)。また、プレイヤーが操作の結果を把握できるように hoverText を設定します。.removeOnPointerDown(entity) などで削除できます。エンティティが削除されるときにハンドラーをクリーンアップしてください。
コライダーを所有するエンティティにハンドラーを登録してください。 ポインターイベントは、レイがコライダーに当たったエンティティに届きます。親グループ、または独自のコライダーを持たない子に設定したハンドラーは発火しません。モデルが空の親で、コライダーメッシュが子エンティティにある場合は、子に登録するか、親に独自の MeshCollider を設定してください。プレイヤーがクリックできなかったヘッドセット(2026-10-12)は、コライダーエンティティの親にハンドラーが設定されていました。コライダーレイヤーについては、下記の**「コライダーが必要」**を参照してください。
距離ルール(maxDistance / maxCameraDistance / maxPlayerDistance)
maxDistance はカメラではなく、アバターからの距離です。 以前の SDK/プロトコルのコメントではカメラ距離とされていましたが、これは誤りです。Unity/Godot では常にプレイヤー距離を測定していました。確認済み:プロトコルの pointer_events.proto(0e82dcd)、@dcl/ecs の EventSystemOptions(6a98834a)、テストシーン 34,20-pointer-events-distances。
| オプション | 測定起点 | デフォルト |
|---|---|---|
maxDistance | アバター/プレイヤーの位置 | 10 |
maxCameraDistance | アクティブなカメラの原点 | なし(未設定) |
maxPlayerDistance | maxDistance の [DEPRECATED] エイリアス(同じ意味) | なし |
2 つの有効な距離しきい値の組み合わせ:
| 設定 | インタラクションが許可される条件 |
|---|---|
maxDistance のみ | プレイヤー距離 <= maxDistance |
maxCameraDistance のみ | カメラ距離 <= maxCameraDistance |
| 両方 | プレイヤー距離 <= maxDistance または カメラ距離 <= maxCameraDistance(OR。AND ではありません) |
| どちらもなし | プレイヤー距離 <= 10(暗黙のデフォルト) |
maxDistanceとmaxPlayerDistanceの両方が設定されている場合、プレイヤー距離のしきい値には大きい方が適用されます。maxDistanceを優先してください。maxCameraDistanceは、チェックの基準が本当にカメラである場合(たとえば、アバターが決して近づかない対象を映す固定のVirtualCamera)にのみ使用してください。デフォルトの三人称カメラはアバターの約 7m 後方にあるため、カメラ距離のみを基準とする 10m のルールは、実際には数値から受ける印象よりもかなり厳しくなります。- 近接イベントは
maxCameraDistanceを完全に無視します — 「近接イベント」セクションを参照してください。
maxCameraDistance(EventSystemOptions.maxCameraDistance)には @dcl/sdk 7.28.0+ が必要です。[未確認:レンダラーのサポート — Bevy explorer と unity-explorer PR #9902 で実装済みです。この PR がプレイヤーの使用している Unity ビルドに含まれていない場合、そこで maxCameraDistance は無視され、プレイヤー距離の条件のみが適用されます。カメラ距離のみのルールに依存するシーンをリリースする前に、デプロイ済みの explorer で確認してください。]
距離の測定起点(しきい値が計算値より約 1m ずれている場合に重要です):
- カーソルのプレイヤー距離:アバターの ROOT(足元。
Transform.get(engine.PlayerEntity).position)から、コライダー表面上のレイキャストヒット地点まで。 - カーソルのカメラ距離:カメラからヒット地点までのレイの長さ(
hitInfo.distance)。 - 近接距離:足元でもヒット地点でもなく、カプセルの中心(足元から約 1m 上)から測定します。同じ場所に立っていても、近接距離とカーソル距離の表示には約 1m の差が出ることがあります。
フィードバックオプション:showFeedback が hoverText の表示を制御
30,20-pointer-events-feedback で確認済み:
showFeedback(デフォルトはtrue)— ホバー UI を有効にします。hoverTextはshowFeedbackが true の場合にのみ表示されます。showFeedback: falseの場合、hoverTextの設定にかかわらずホバーテキストは表示されません。showHighlight(デフォルトはtrue)— ホバー時にエンティティの縁/輪郭をハイライト表示します。テキストとは独立していますが、表示するにはshowFeedbackが有効である必要があります。showFeedback: trueでhoverTextが未設定または空の場合、何も表示されるのではなく、explorer のデフォルトのアクションプロンプト(ボタンのグリフなど)が表示されます。
輪郭の描画対象: ヒットしたコライダーを所有するエンティティのレンダラー(そのエンティティには PointerEvents も必要です)と、そのすべての子孫です。親や兄弟は対象になりません。輪郭が描画されるのは MeshRenderer のプリミティブと GLB のみで、TextShape には描画されません(unity-explorer の InteractionHighlightSystem、main 2026-10)。そのため:
- 不可視のヒットボックス(コライダーと
PointerEventsがあり、メッシュなし)は、表示パーツがその子でない限り、何も輪郭表示されません。モデルの隣に配置したヒットボックスや、モデルの子として配置したヒットボックスでも、イベントとホバーテキストは発生しますが、輪郭は表示されません。 - 複数のエンティティで構成され、それぞれが独自のコライダーとハンドラーを持つプロップは、一度に 1 つのパーツだけが輪郭表示されます。コライダーと
PointerEventsをルートに 1 つだけ配置し、表示パーツを子にするか、プロップを単一の GLB にしてください。
落とし穴:ポインターハンドラーを、自身のコールバック内から登録(再登録)しないでください
エンティティ自身のポインターコールバック内から、そのエンティティに対してonPointerDown/removeOnPointerDown(または Up/Hover 用の on/remove バリアント)を呼び出すと、同じクリックでハンドラーが複数回実行されます(確認された動作:1 回のクリックで 3 回実行。ステートマシンが実行のたびに再登録されていました)。
理由(確認済み — @dcl/ecs/dist/systems/events.js):EventSystemは、フレームごとにエンティティごとのハンドラーを格納したMapを反復処理します。onPointerDownはremoveEvent(entity, EventType.Down)を実行した後、getEvent(entity).set(EventType.Down, …)を呼び出します。これにより、同じMapにDownキーが再挿入されます。Map自体のfor…of反復中にキーを再挿入すると、同じパス内で再び処理対象になります。また、inputSystem.getInputCommand(...)は同じバッファー済みの down コマンドを返し続けるため、コールバックが再実行されます。
修正方法 — ホバーテキストを動的に変更する場合は、再登録する代わりに既存のPointerEventsコンポーネントをその場で変更します:
import { PointerEvents } from '@dcl/sdk/ecs'
function setHoverText(entity: Entity, hoverText: string) {
const pe = PointerEvents.getMutableOrNull(entity)
if (!pe) return
for (const entry of pe.pointerEvents) {
if (entry.eventInfo) entry.eventInfo.hoverText = hoverText
}
}
ハンドラーの登録は 1 回だけ(たとえばエンティティの作成時に)行い、クリック/ホバーコールバック内やフレームごとのシステムから再度呼び出さないでください。
重要:コライダーが必要です — ポインターイベントは、ColliderLayer.CL_POINTERレイヤーを使用するコライダーがあるエンティティでのみ機能します。不可視のコライダーにはMeshCollider.setBox(entity)を使用するか、GltfContainerにvisibleMeshesCollisionMask: ColliderLayer.CL_POINTERを設定してください。
GltfContainerのクリック可能性 — 使用するマスクとスキンメッシュの例外
GltfContainerのデフォルト値(確認済み — @dcl/ecsのgltf_container.gen.d.ts):
visibleMeshesCollisionMaskのデフォルト値:0(可視メッシュはデフォルトでクリック/衝突の対象になりません)invisibleMeshesCollisionMaskのデフォルト値:CL_POINTER | CL_PHYSICS(GLB の*_colliderメッシュには両方のレイヤーが設定されます)
モデルの作成方法に合わせて選択してください:
| モデル | クリック可能にする方法 |
|---|---|
_colliderメッシュがある | 不可視メッシュのデフォルト設定により、すでにCL_POINTERが適用されています。レイヤーを分けるには、invisibleMeshesCollisionMaskを明示的に設定し、visibleMeshesCollisionMask: 0を維持してください。 |
静的メッシュで、_colliderがない | visibleMeshesCollisionMask: ColliderLayer.CL_POINTERを設定します。可視メッシュがクリック対象になります。 |
スキン付き/アーマチュアでリグ設定されたメッシュ(NPC、キャラクター、ゴースト)で、_colliderがない | visibleMeshesCollisionMaskを設定してもクリック可能にはなりません。明示的な不可視の子コライダーを追加してください(下記参照)。 |
落とし穴:スキン付き/リグ設定された GLB は、visibleMeshesCollisionMaskを設定してもポインタークリック可能にはなりません。 SDK7 のシーンで確認された動作です。_colliderメッシュがなく、visibleMeshesCollisionMask: ColliderLayer.CL_POINTERを設定した NPC/ゴーストの GLB は、ポインターイベントを受け取りませんでした。SDK6 のGLTFShapeでは、リグ設定の有無にかかわらず可視メッシュがクリック可能でした。SDK7 では、このケースにおいて、可視メッシュのマスクを設定しても、スキン付き/アニメーション付きの可視メッシュに対するポインターイベントのレイキャストは行われませんでした。(これは確認されたレンダラーの動作です。正確なリグ設定の条件はプロトコルに記載されていないため、モデルごとに確認してください。)
修正方法 — CL_POINTERレイヤーに不可視のボックスコライダーを追加し、動きに追従するようエンティティを親に設定して、コライダーにハンドラーを登録します:
import { engine, MeshCollider, ColliderLayer, pointerEventsSystem, InputAction, Transform } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
// `npc` is the rigged GLB entity (with GltfContainer + Animator).
const clicker = engine.addEntity()
// scale to the avatar bounds; parent so it follows the NPC as it moves
Transform.create(clicker, { position: Vector3.create(0, 0.45, 0), scale: Vector3.create(2, 2.9, 2), parent: npc })
MeshCollider.setBox(clicker, ColliderLayer.CL_POINTER)
pointerEventsSystem.onPointerDown(
{ entity: clicker, opts: { button: InputAction.IA_PRIMARY, hoverText: 'Talk', showFeedback: true } },
() => { /* handle click */ }
)
GltfContainer自体のvisibleMeshesCollisionMask: 0は維持できます。メッシュコライダーがまったくない AvatarShape の NPC にも、同じ子コライダーの方法を適用できます — [[npcs]] のオプション A を参照してください。
すべての入力アクション
InputAction.IA_POINTER; // Left mouse button
InputAction.IA_PRIMARY; // E key
InputAction.IA_SECONDARY; // F key
InputAction.IA_ACTION_3; // 1 key
InputAction.IA_ACTION_4; // 2 key
InputAction.IA_ACTION_5; // 3 key
InputAction.IA_ACTION_6; // 4 key
InputAction.IA_JUMP; // Space key
InputAction.IA_FORWARD; // W key
InputAction.IA_BACKWARD; // S key
InputAction.IA_LEFT; // A key
InputAction.IA_RIGHT; // D key
InputAction.IA_WALK; // Control key
InputAction.IA_MODIFIER; // Shift key
すべてのイベントタイプ
PointerEventType.PET_DOWN; // Button pressed
PointerEventType.PET_UP; // Button released
PointerEventType.PET_HOVER_ENTER; // Cursor enters entity
PointerEventType.PET_HOVER_LEAVE; // Cursor leaves entity
PointerEventType.PET_PROXIMITY_ENTER; // Player walks within entity's proximity range
PointerEventType.PET_PROXIMITY_LEAVE; // Player moves out of entity's proximity range
ステップでハンドラーを削除しても PointerEvents エントリーが残る問題(SDK 7.28.1+)
@dcl/ecs で修正されました(js-sdk-toolchain #1577、e3d4af57)。今後は次の動作を前提にしてください。
removeOnPointerDown/removeOnClick/removeOnPointerUp/ ホバーおよび近接の各バリアントは、hoverTextが渡されたかどうかにかかわらず、登録時に作成されたPointerEventsエントリーを削除するようになりました。 以前は、hoverTextを指定して登録したハンドラーだけがエントリーを削除していました。それ以外の削除ではエントリーが残り、コールバックがなくなったインタラクションをレンダラーが引き続き通知していました。また、再登録のたびに配列のエントリーが1つずつ増えていました(配列全体が CRDT 経由で再送信されます)。回避策としてダミーのhoverTextを追加しないでください。この回避策は不要になりました。- コールバックマップのキーは
eventType:interactionTypeなので、同じエンティティでonProximityDownを登録しても、onPointerDownが削除されなくなりました。 同じイベントタイプに対してカーソルハンドラーと近接ハンドラーの両方を登録できるようになり、両方の条件が満たされると両方のコールバックが実行されます。 - 削除されるのは、その登録に対応するエントリーだけです。
removeOnClickがonPointerDownの記述子を奪うことはなくなりました(どちらもPET_DOWN+CURSORを書き込みます)。
@dcl/sdk 7.28.0(現在の安定版)はこの修正より前のバージョンで、修正は @next(7.28.1-…commit-e3d4af5)に含まれています。7.28.0 以前では、hoverText を指定せずに削除するとエントリーが残るものと考えてください。
近接イベント(照準を合わせずに近くでインタラクション)
近接イベントを使うと、プレイヤーがエンティティの近くにいて、おおよそその方向を向いているときに、プレイヤーがカーソルをエンティティに合わせなくても、ボタンの押下にエンティティが反応するようになります。インタラクション領域は、アバターの位置から前方に伸びる広い三角形の範囲です。関係するのはアバターの向きであり、カメラの向きではありません。
近接イベントが発生するには、次の3つの条件をすべて満たす必要があります(34,20-pointer-events-distances および Unity エクスプローラーの近接処理で確認済み)。
- 距離 — プレイヤーの距離が
maxDistance(デフォルトは10)以下であること。距離はカプセルの中心(足元から約1m上)を基準に測定されます。 - 向きの範囲 — エンティティがアバター前方の約120度の水平範囲内にあること。
- 遮蔽物のレイキャスト — プレイヤーとエンティティの間に何もないこと。
注意 — 3m のブロードフェーズ。 Unity エクスプローラーの近接ブロードフェーズでは、3m の
OverlapSphereがハードコードされています。近接エントリーのmaxDistanceが3を超えると、約3mまで暗黙的に制限されることがあります。そのため、8mを指定した近接ハンドラーが約3mで反応しなくなる場合があります。近接インタラクションは至近距離向けに設計し、約3mを超える範囲が必要な場合はTriggerArea(またはカーソルポインターイベント)を使用してください。
プレイヤーが、近接インタラクションを持つエンティティの近接範囲内にいると同時に、ポインターインタラクションを持つエンティティに照準を合わせている場合、常にポインターインタラクションが優先されます。 範囲内に近接対象のエンティティが複数ある場合は、最も近いエンティティ(または優先度が最も高いエンティティ)だけが有効になります。
pointerEventsSystem.onProximityDown() と .onProximityUp() を使用します。シグネチャはポインターイベントと同じです。範囲は maxDistance で指定します(アバターからの距離。デフォルトは10)。maxPlayerDistance は非推奨の別名です。エンティティごとに、イベントタイプごとの近接登録は1つだけにしてください。システムループ内では呼び出さないでください。
プレイヤーが範囲内に入った、または範囲外に出たことを検出するには、.onProximityEnter() と .onProximityLeave() を使用します。サウンド、アニメーション、UI のヒントなどに便利です。
複数のエンティティが重なる場合は、priority オプション(数値が大きいほど優先)を使用してください。同じ優先度の場合は、最も近いエンティティが優先されます。.removeOnProximityDown(entity) などを使って削除します。
近接オプション
button:リッスンするボタン(ポインターイベントと同じです)maxDistance:プレイヤーのアバターからエンティティまでの最大距離(デフォルトは10)— これが近接範囲ですmaxPlayerDistance:[DEPRECATED]maxDistanceの別名です。両方が設定されている場合は、大きい方が適用されますmaxCameraDistance:近接処理では無視されます。 プレイヤーとの距離のしきい値のみが適用されます。近接エントリに設定しても何も起こりませんhoverText:プレイヤーが近くにいるときに表示されるテキストshowHighlight:範囲内にいるときのエッジのハイライト(デフォルト:true)showFeedback:エンティティの中心付近に表示されるホバー時のフィードバック(デフォルト:true)priority:競合を解決します。値が大きいほど優先され、同値の場合は距離が近いものが優先されます
システムベースの方法(同じエンティティでポインターと近接を組み合わせる場合)では、PointerEvents コンポーネントと inputSystem.isTriggered() を使って InteractionType.PROXIMITY を指定します。
トリガーエリア(近接検出)
エンティティが領域に入ったことを検出するためのネイティブ ECS コンポーネントです。「毎フレームプレイヤーの位置を確認する」独自のシステムや、旧式の @dcl-sdk/utils の triggers.addTrigger() ヘルパーではなく、こちらを使用してください。これらはフォールバックとして存在しますが、TriggerArea が SDK7 の標準的なプリミティブです(ADR-258)。
ボリュームのサイズ、位置、回転はエンティティの Transform から決まります。 Transform.scale は、エンティティの姿勢に配置された単位ボックス(または scale.x から決まる球の半径)を定義し、親エンティティの階層も反映します。
Creator Hub でシーンを開いていますか? utils カタログには、「Trigger Area」Smart Item が1つあります(e9fa0eab-44de-4efe-af77-a71171a1a73f)。ユーザーが配置してサイズを変更できる透明なボックスまたは球で、入るとき/出るときに何をするかを指定するプロンプトを AI アシスタントの入力欄にセットするコントロールが付いています。エンティティを手作業で組み立てる代わりに、こちらを使用してください。形状パラメーター、コライダーフィルター、Reactions の配線があらかじめ設定されており、パラメーターを編集しても Reactions が消えなくなりました(creator-hub e43441b5 で修正)。旧式のスクリプトなしアイテムと、暫定的に使われていた「Trigger Area (Script)」というラベルは、どちらも同じコミットで削除されました。現在はエントリが1つだけあります。creator-hub-mcp を参照してください。
注意点 — 薄いトリガーは作動しません。 厚さ約0.1mの板としてモデル化された圧力板や床の目印は、トリガーボリュームとアバターのカプセルがその上に立っている間に重ならないため、
TriggerAreaは何も通知せず作動しません。これはコンポーネントの不具合ではありません。解決策: 薄い板は見た目用として残し、その上に高さ約2mの別の透明なボックスを配置してトリガーにします(
149,149-synthetic-input-showcaseのステーション S2 と S3 で検証済み):// 見た目用の板はそのままにします。トリガーは独立したエンティティです。 const plateTrigger = engine.addEntity() Transform.create(plateTrigger, { position: Vector3.create(plate.x, plate.y + 1, plate.z), // 中心を1m上に配置 scale: Vector3.create(3, 2, 3), // 高さ2m }) TriggerArea.setBox(plateTrigger)落とし穴 — ゾーンが重なる場合:占有状態から再計算し、ハンドラーから直接書き込まないでください。 2つのトリガーゾーンがそれぞれ何らかの状態(
InputModifier、カメラモード、アンビエンストラック)を設定する場合、onTriggerEnterで設定し、onTriggerExitで解除してはいけません。テレポート(movePlayerTo)で両方のボリュームを横切ると、移動先の enter が移動元の exit より先に 発生し、後から発生した古い exit によって、enter が設定した状態が消されることがあります。イベントの順序はどちら向きでも 決定的ではありません。同一セッションで両方の順序が確認されており、それぞれ移動方向によって異なっていました。そのため、どちらの順序を前提にしたコードも書けません。修正方法: ハンドラーでは占有フラグだけを切り替えます。すべてのフラグから状態を導出する関数を1つ用意し、enter と exit のたびに呼び出します。
let insideZoneA = false let insideZoneB = false function applyModifierFromOccupancy() { if (insideZoneA) InputModifier.createOrReplace(engine.PlayerEntity, { mode: { $case: 'standard', standard: { disableAll: true } } }) else if (insideZoneB) InputModifier.createOrReplace(engine.PlayerEntity, { mode: { $case: 'standard', standard: { disableRun: true } } }) else InputModifier.deleteFrom(engine.PlayerEntity) } // すべての enter/exit ハンドラー:フラグを設定してから applyModifierFromOccupancy() を呼び出すこれにより、イベントの順序に左右されなくなります。この方法は、複数のゾーンによって駆動される last-writer-wins の状態全般に適用できます。
最小限の例 — ローカルプレイヤーを検知するボックス:
import {
engine,
Transform,
TriggerArea,
triggerAreaEventsSystem,
ColliderLayer
} from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const zone = engine.addEntity()
Transform.create(zone, {
position: Vector3.create(8, 1, 8),
scale: Vector3.create(4, 2, 4) // 4m × 2m × 4m box
})
TriggerArea.setBox(zone, ColliderLayer.CL_PLAYER)
triggerAreaEventsSystem.onTriggerEnter(zone, (result) => {
if (result.trigger?.entity !== engine.PlayerEntity) return // local player only
console.log('player entered')
})
triggerAreaEventsSystem.onTriggerExit(zone, () => {
console.log('player left')
})
球体のバリエーション: TriggerArea.setSphere(entity, ColliderLayer.CL_PLAYER) — Transform.scale は均一な値を使います(半径には scale.x が使われます)。
衝突マスク: デフォルトは CL_PLAYER です。物理レイヤーやカスタムレイヤーに反応させるには、他の ColliderLayer 値を渡します(ビット単位の OR で組み合わせます。例:CL_PLAYER | CL_CUSTOM4)。
サイズ:ボリュームは Transform そのものです — サイズフィールドはありません。 PBTriggerArea が持つのは mesh と collision_mask のみです。サイズ と回転 はすべてエンティティの Transform から決まり、エンティティまたは親に対していつ変更しても、ボリュームにスケール変更が反映されます。これは、兄弟コンポーネントである AvatarModifierArea や CameraModeArea とは逆です。これらは明示的な area: Vector3 を受け取り、コンポーネントが存在する間は Transform.scale を上書きするため、後からエンティティをスケーリングしても何も変わりません。一方の使い方をもう一方に当てはめないでください(player-avatar と camera-control を参照)。
プレイヤー以外のエンティティの検知: TriggerArea は、そのエンティティ自身のコライダーがエリアの監視対象レイヤー上にある場合に反応します。シーンエンティティでエリアを発動させるには、一致するカスタムレイヤー上に MeshCollider/GltfContainer コライダーを設定し、エリアのマスクにそのレイヤーを含めます(75,-9-trigger-areas で検証済み:visibleMeshesCollisionMask: ColliderLayer.CL_CUSTOM4 を設定した GltfContainer は、TriggerArea.setBox(entity, ColliderLayer.CL_PLAYER | ColliderLayer.CL_CUSTOM4) で作成したエリアを発動させます)。移動または落下するエンティティに TriggerArea を設定して、別のボリュームに そのエンティティ自身が 入ったことを検知することもできます。
コールバック:
triggerAreaEventsSystem.onTriggerEnter(entity, cb)— 進入時に1回発生しますtriggerAreaEventsSystem.onTriggerStay(entity, cb)— 内部にいる間、毎ティック発生します(ENTER/EXIT ステートマシンから SDK が合成します)triggerAreaEventsSystem.onTriggerExit(entity, cb)— 退出時に1回発生しますremoveOnTriggerEnter/Stay/Exit(entity)で解除します
コールバックの形式 — よくある落とし穴:
コールバックは PBTriggerAreaResult を受け取ります。result.trigger?.entity は進入したエンティティです(ローカルプレイヤーに絞り込むには engine.PlayerEntity と比較します)。result.triggeredEntity はトリガーエリアそのものです。これをプレイヤーと比較すると常に true になり、ガードは一度も実行されません。名前は本当に直感に反しています。triggeredEntity は「トリガーを発動させたエンティティ」のように聞こえますが、実際には「トリガーエリアが発動したエンティティ」を意味します。{baseDir}/references/input-reference.md#trigger-area-callback-fields を参照してください。
マルチプレイヤーに関する注意: CL_PLAYER を使用すると、リモートプレイヤーを含め、進入したすべてのプレイヤーに対してトリガーが発火します。物理演算や UI に影響する処理は、必ず if (result.trigger?.entity !== engine.PlayerEntity) return でガードしてください。
基盤となるコンポーネント: TriggerArea(設定)と TriggerAreaResult(CRDT の結果)です。通常、TriggerAreaResult を直接読み取ることはありません。イベントシステムを使用してください。
レイキャスト
方向モードは 4 種類あります。ローカル方向(エンティティの回転に対する相対方向)、グローバル方向(ワールド空間)、グローバルターゲット(位置を狙う)、ターゲットエンティティ(別のエンティティを狙う)です。
コールバックベース(推奨):raycastSystem.registerLocalDirectionRaycast()、.registerGlobalDirectionRaycast()、.registerGlobalTargetRaycast()、.registerTargetEntityRaycast() を使用します。削除するには .removeRaycasterEntity() を使用します。
コンポーネントベース:Raycast コンポーネントを作成し、システム内で RaycastResult を読み取ります。フレームごとの結果が本当に必要でない限り、continuous: false を設定してください(true にすると毎フレーム再キャストされ、その分のコストがかかります)。フィールド(77,-1-raycast-unit-tests で検証済み):direction: { $case: 'localDirection'|'globalDirection'|'globalTarget'|'targetEntity', ... }、originOffset、maxDistance、queryType、continuous、timestamp。RaycastResult(1 フレーム後に追加)には globalOrigin、正規化された direction、hits[]、timestamp、tickNumber が含まれます。
レイの原点はワールド変換を反映します: レイはエンティティのワールド位置(親の階層を適用)に originOffset を加えた位置から始まるため、移動またはスケールされた親の下にネストされたレイキャストエンティティは、解決済みのワールド姿勢からキャストします。localDirection はエンティティのワールド回転によって回転しますが、globalDirection は回転を無視します。
注意点:RaycastQueryType.RQT_HIT_FIRST は範囲内の最初のヒットを選びますが、必ずしも最も近いヒットとは限りません(列挙型のドキュメントで検証済み)。最も近いヒットが必要な場合は、RQT_QUERY_ALL を使用して距離で並べ替えてください。RQT_NONE はキャストをスキップし、空のヒット結果を返します。
カメラのレイキャスト:プレイヤーが見ている対象を検出するエンティティとして engine.CameraEntity を使用します。カーソルに沿ってキャストする場合(ロック中でも解除中でも動作します)は、方向として PrimaryPointerInfo.worldRayDirection を使用してください(advanced-input を参照)。
グローバル入力の処理
エンティティ固有ではなく、どこでもキー入力を受け取るには、engine.addSystem() 内で inputSystem.isTriggered()(このフレームに押された)と inputSystem.isPressed()(現在押されている)を使用します。システム経由でエンティティ固有の入力を扱うには、inputSystem.getInputCommand() を使用してください。
デスクトップとモバイルの両方を想定して設計してください。モバイルにはキーボードがないため、キー入力ではなくポインターイベントや画面上の UI ボタンを使用します。
カーソルの状態
ポインターロックは PointerLock.get(engine.CameraEntity).isPointerLocked で確認します。カーソルの位置とワールドレイは PrimaryPointerInfo.get(engine.RootEntity) で取得します。
切り替えパターン
一般的なパターンは、モジュールレベルのブール値で状態を管理し、クリックハンドラーで反転させて、エンティティをそれに応じて更新する方法です。
複雑なインタラクション(複数ステップのシーケンス、クールダウン、共有状態に反応する複数のエンティティ)では、単一のブール値にとどめず、モジュールレベルのオブジェクトまたはカスタムコンポーネントで状態を管理し、システムから更新を実行します。
スマートアイテム:動作はトリガーグラフではなく Script コードです
カタログにあるほとんどのスマートアイテムは、Actions/Triggers のノーコードグラフから asset-packs::Script コードと @action メソッドへ移行されました(creator-hub 658f4daf)。移行済みアイテムを開いて asset-packs::Triggers を探しても、見つかりません。動作は .ts ファイルに記述され、「When X」フックは省略可能な ActionCallback パラメーター(onClick、onActivate/onDeactivate、onReachStart/onReachEnd、onRing、onOpen/onClose)であり、状態は同期される asset-packs::States です。以前の公開アクション名は call_script_method ポインターとして引き続き利用できるため、既存の接続はそのまま機能します。詳細、未移行の一覧、world_teleport と teleport の統合については、script-components を参照してください。
シーンの例
これらの API を検証するエンジンチームのテストシーン(正解データ):
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/3,2-proximity-interactions —
onProximityDown/Enter/Leave、カーソルと近接時の優先順位、2 つのボタンを備えた宣言型PointerEvents、近接ドア。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/30,20-pointer-events-feedback —
showFeedback/showHighlight/hoverTextの組み合わせ(ホバーテキストにはshowFeedback: trueが必要です)。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/34,20-pointer-events-distances — 距離のルール:8 つのレーンで
maxDistance(プレイヤー)、非推奨のmaxPlayerDistanceエイリアス(大きい値が優先)、maxCameraDistance、OR の組み合わせ、暗黙のデフォルト値 10m、近接ではmaxCameraDistanceが無視されることを検証します。各キューブは、解決してから分岐するロジックに基づいて、毎フレーム LIVE/DEAD を予測するため、ワールド内の動作との不一致が問題の兆候です。エクスプローラーにハードコードされた近接判定の広域フェーズ 3m を調べるプローブも含まれています。@dcl/sdk@nextを固定しています。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/149,149-synthetic-input-showcase — 背の高い不可視の
TriggerAreaを修正する方法(S2/S3)と、占有状態を再計算するInputModifierパターンを、Unity Explorer MCP を通じてライブで実行します。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/75,-9-trigger-areas —
TriggerArea.setBox/setSphere、onTriggerEnter/Stay/Exit、プレイヤー以外のエンティティがエリアに反応するようにするカスタム衝突レイヤーマスク。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,0-cube-spawner — システム内で
inputSystem.isTriggered(IA_POINTER, PET_DOWN, entity)を使用して読み取る宣言型PointerEvents。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/77,-1-raycast-unit-tests — 変換された親チェーンの下にある原点を含む、4 つすべての方向モードに対応するコンポーネントベースの
Raycast/RaycastResult。
コード例と実装パターンの詳細については、{baseDir}/references/interactivity-patterns.md を参照してください。入力アクションのリファレンステーブルと宣言型 PointerEvents コンポーネントについては、{baseDir}/references/input-reference.md を参照してください。