player-physics について
Decentraland のシーン開発者向けプレイヤー物理ガイドです。Physics API でローカルプレイヤーに瞬間的または継続的な力を加える方法に加え、トリガー領域とモバイルでの注意点を説明します。
主な機能
- 01インパルスとノックバックの適用
- 02指定時間の力の適用
- 03距離に応じた反発力の作成
- 04エンティティが所有する力の解除
- 05ローカル方向からワールド方向への変換
- 06プレイヤートリガーの衝突レイヤーの説明
活用シーン
- 01発射台や爆発ノックバックの作成
- 02風の通路、コンベヤー、重力場の実装
- 03点を中心とした反発領域の作成
- 04プレイヤー物理トリガー領域のデバッグ
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ decentraland/ sdk-skills --skill player-physicsインストール後、Claude Code で/player-physicsを入力して読み込まれたか確認します。
同梱ファイルreferences/physics-patterns.md
詳細
Decentraland のプレイヤー物理演算
@dcl/sdk/ecs の Physics API を使用して、プレイヤーのアバターに力を加えます。物理演算はすべてローカルプレイヤーにのみ影響します。
このスキルが必要な理由 — Transform の誤用
プレイヤーの Transform(engine.PlayerEntity 上)はエンジンによって制御されており、シーンコードからは読み取り専用です。Transform.getMutable、Transform.createOrReplace、または .position/.rotation の直接変更によって書き込んでも、何も起こりません。コードはコンパイルされ、システムも実行されますが、エラーは発生せず、アバターも動きません。
プレイヤーを浮かせる、空中に留める、押す、ノックバックさせる、または継続的な力を加える場合は、このスキルの Physics API を使用してください。 特定の位置への即時テレポートや滑らかな移動には、~system/RestrictedActions の movePlayerTo を使用してください(スキル:player-avatar)。
// WRONG — has no effect in-world
const t = Transform.getMutable(engine.PlayerEntity)
t.position.y += 0.1 // ignored every frame
// CORRECT — lift the player upward
import { Physics } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
Physics.applyImpulseToPlayer(Vector3.create(0, 50, 0)) // one-shot upward launch
// or, sustained lift / hover:
const lifter = engine.addEntity()
Physics.applyForceToPlayer(lifter, Vector3.create(0, 1, 0), 12) // continuous upward force
// stop with: Physics.removeForceFromPlayer(lifter)
インパルス(一度だけの押し出し)
Physics.applyImpulseToPlayer(direction, magnitude?) を使用して、瞬間的な力を一度だけ加えます。大きさを別途指定すると、方向は自動的に正規化されます。同じフレーム内で複数回呼び出すと、力は累積されます。発射台、ジャンプ、爆発などに使用します。
ノックバック(点から遠ざける)
Physics.applyKnockbackToPlayer(sourcePos, magnitude, radius?, falloff?) を使用して、プレイヤーを発生源の位置から遠ざけます。発生源からプレイヤーへの方向は自動的に計算されます。
KnockbackFalloff のオプション
| フォールオフ | 動作 |
|---|---|
KnockbackFalloff.CONSTANT | 半径内では距離にかかわらず同じ大きさ(デフォルト) |
KnockbackFalloff.LINEAR | 半径の端で 0 になるまで滑らかに直線的に減少 |
KnockbackFalloff.INVERSE_SQUARE | 物理的に現実的な急激な減衰 |
プレイヤーが発生源とまったく同じ位置にいる場合、真上に押し出されます。負の大きさを指定すると、プレイヤーはその点に引き寄せられます。applyRepulsionForceToPlayer() でも同じフォールオフ値を使用できます。ほとんどの範囲効果では、自然で予測しやすい KnockbackFalloff.LINEAR をおすすめします。
継続的な力
エンティティ(力の「所有者」)で識別される持続的な方向の力を加えます。異なるエンティティによる複数の力は重なり合います。
- 適用:
Physics.applyForceToPlayer(entity, direction, magnitude?) - 削除:
Physics.removeForceFromPlayer(entity)
トリガーゾーンと組み合わせて、風のトンネル、コンベアベルト、重力場などに使用します。
時間指定の力
固定時間だけ力を加えるには、Physics.applyForceToPlayerForDuration(entity, seconds, direction, magnitude?) を使用します。時間が経過すると自動的に終了します。
反発力
距離に応じてフォールオフする、固定点から遠ざける継続的な力を加えます。この力は毎フレーム再計算されます。Physics.applyRepulsionForceToPlayer(entity, position, magnitude, radius) を使用します。位置は明示的に渡す必要があります。エンティティの Transform から自動的に取得されるわけではありません。Physics.removeForceFromPlayer(entity) で削除します。
座標変換(ローカル方向からワールド方向へ)
Transform.localToWorldDirection(entity, localDir) を使用して、ローカル方向をワールド空間に変換します。エンティティの向きに対して力を加える場合(例:「この大砲の前方へ」)に使用します。
クイックリファレンス
| メソッド | 種類 | 説明 |
|---|---|---|
Physics.applyImpulseToPlayer(dir, mag?) | 一度だけ | 指定方向への瞬間的な押し出し |
Physics.applyKnockbackToPlayer(pos, mag, radius?, falloff?) | 一度だけ | 点から遠ざける |
Physics.applyForceToPlayer(entity, dir, mag?) | 継続 | 有効な間、継続的に押し出す |
Physics.removeForceFromPlayer(entity) | 制御 | 継続的な力を止める |
Physics.applyForceToPlayerForDuration(entity, secs, dir, mag?) | 時間指定 | 時間経過で自動的に終了する力 |
Physics.applyRepulsionForceToPlayer(entity, pos, mag, radius) | 継続 | 点から距離に応じて押し出す |
Transform.localToWorldDirection(entity, dir) | ユーティリティ | ローカル方向をワールド空間に変換 |
トリガーゾーンのコライダーレイヤー — どのプレイヤーがトリガーを発火させるか
Physics.* は常にローカルプレイヤーに作用します。TriggerArea から力を加える場合、コライダーマスクによって、どのアバターがコールバックを発火させるかが決まります(テストシーン 5,5-collider-layers で検証済み)。
| マスク | 発火する対象 |
|---|---|
ColliderLayer.CL_MAIN_PLAYER | ローカルプレイヤーのみ |
ColliderLayer.CL_PLAYER | リモートアバターのみ(ローカルプレイヤーは対象外) |
CL_PLAYER | CL_MAIN_PLAYER | ローカルとリモートの両方 |
ColliderLayer.CL_PHYSICS | どのアバターに対しても発火しません(キャラクターではなく、シーンのメッシュや壁が対象です) |
プレイヤーの物理演算に使うトリガーゾーンには、CL_MAIN_PLAYER を使用してください。 コールバックはローカルプレイヤーに対してのみ発火するため、リモートかローカルかを判定する必要がなく、力は作用可能な唯一のアバターに加わります。CL_PLAYER のみを使ったジャンプ台は、ローカルプレイヤーに対して発火しないため(リモートアバターに対してのみ発火します)、その上に立っているプレイヤーをジャンプさせません。これはよくあるバグです。
CL_PLAYER | CL_MAIN_PLAYER を使う場合は、result.trigger?.entity === engine.PlayerEntity で判定してください。result.trigger?.entity は、トリガーに入ったエンティティです。注:result.triggeredEntity はトリガーエリア自体のエンティティであり、進入したエンティティではありません。そのため、これを PlayerEntity と比較しても、誰が入ったかは判別できません。
滑空中の力
プレイヤーが滑空している(グライダーを開いている)間は、力の挙動が異なります。
- 継続的な力(
applyForceToPlayer、applyForceToPlayerForDuration、applyRepulsionForceToPlayer)は1.5 倍強くなります。開いたグライダーが気流を受けるため、風のゾーンや気流に対する反応がより敏感になります。 - 継続的な力の上向き成分によって、滑空中のプレイヤーを上昇させることができます。
AvatarLocomotionSettingsのglidingFallingSpeedが制限するのは下降速度のみで、上向きの動きを打ち消しません。そのため、斜めや垂直方向の気流は、力の向きに沿ってプレイヤーを押し上げます。これにより、上昇気流や風の通り道を作れます。 - 一度だけ加えるインパルス(
applyImpulseToPlayer、applyKnockbackToPlayer)は、滑空の影響を受けません。グライダーを開いていても閉じていても、挙動は同じです。
モバイルの動作はデスクトップとまったく同じではありません
モバイルアプリの v1.15.0(2026年10月)では、Unity と同等の動作を目指す最初の調整として、カプセルサイズ、段差の高さ、ジャンプの軌道と重力、走行速度、空中制御が反映され、下向きのプレイヤー用レイキャストも修正されました。これにより、傾斜したジオメトリ上で正しい hitDistance が報告されるようになりました。ただし、これは最初の調整であり、完全な同等性には至っていません。以下の項目は引き続き未対応で、対応時期も未定です(ドキュメントのコミット 5f212e3)。
- 傾斜、端、壁、着地、動くプラットフォームの挙動は、引き続き Unity と異なります。滑空の挙動も同様です(修正をレビュー中)。
- **シーンのコライダーの厚さが 1 cm 未満の場合、上からのみ衝突します。**厚さ 0.5 cm の壁、薄い不可視の床板、紙のように薄い傾斜路は、モバイルでは横から通り抜けてしまいます。すべてのコライダーに最低 1 cm の厚みを持たせてください。手間のかからない予防策であり、ここで最もすぐ実践できるルールです。
- **不可視の障害物や一部の SDK の衝突レイヤーは、モバイルではプレイヤーをまったくブロックしません。**プレイエリアの区切り、キル境界、「範囲内に留まる」ルールを、不可視のコライダーだけで実現しないでください。
TriggerAreaとmovePlayerToを組み合わせるか、プレイヤーから見えるジオメトリで補ってください。 - スポーン、着地、ジャンプのアニメーションと移動のブレンドがありません。そのため、アバターの動きが硬く見えます。モバイルクライアントでは再生されないアニメーションのタイミングに、仕掛けを合わせないでください。
デスクトップで「ぎりぎり」動作するよう調整した段差、縁石、棚の高さは、スマートフォンでは登れないことも、逆に登れるべきでないのに登れてしまうこともあります。また、インパルスやノックバック後の着地位置も異なる場合があります。通行できるジオメトリには余裕を持たせ、強制移動を伴う仕掛けには着地しやすいエリアを設けてください。精密なプラットフォームアクションを公開する前に、実機で確認してください。また、TweenSequence はモバイルではアニメーションを実行しません(animations-tweens)。そのため、これを使った動くプラットフォームは、スマートフォンでは動きません。モバイルでの同等性に関する一覧は advanced-input → モバイルに関する考慮事項 をご覧ください。
コード例の全体(ジャンプ台、風のトンネル、反発フィールド、座標変換)については、{baseDir}/references/physics-patterns.md をご覧ください。