advanced-input について
Decentraland SDK のシーン開発者向け高度な入力スキルです。ポインターロック、カーソルのレイ、入力ポーリング、移動制限を扱い、複雑な入力パターンや誤解を招きやすいエンティティ入力の挙動を説明します。
主な機能
- 01ポインターロック状態の取得と制御
- 02カーソル座標、移動量、ワールドレイの取得
- 03グローバルまたはエンティティ単位の入力ポーリング
- 04InputModifier による移動制限
- 05プラットフォームと言語の検出
活用シーン
- 01一人称視点のマウスルック制御の実装
- 02特定エンティティのキーやポインターイベント処理
- 03カットシーン中のプレイヤー移動の停止
- 04プラットフォームや言語に応じたシーン UI の調整
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ decentraland/ sdk-skills --skill advanced-inputインストール後、Claude Code で/advanced-inputを入力して読み込まれたか確認します。
同梱ファイルreferences/input-patterns.mdreferences/touch-screen-controls.md
詳細
Decentraland の高度な入力処理
基本的なクリック/ホバーイベントについては、add-interactivity スキルを参照してください。このスキルでは、高度な入力パターンを扱います。単純なエンティティのクリックには pointerEventsSystem.onPointerDown()(add-interactivity)を優先し、複雑な複数キー入力やポーリングのパターンには inputSystem を使用してください。
ポインターロックの状態
カーソルがキャプチャされている(ファーストパーソンモード)か、自由に動かせるかを検出します。
import { engine, PointerLock } from '@dcl/sdk/ecs'
function checkPointerLock() {
const isLocked = PointerLock.get(engine.CameraEntity).isPointerLocked
if (isLocked) {
// Cursor is captured — player is in first-person control
} else {
// Cursor is free — player can click UI elements
}
}
engine.addSystem(checkPointerLock)
ポインターロックの要求/解除(書き込み可能)
PointerLock.isPointerLocked は通常の書き込み可能なブール値です。シーンからこの値を変更して、カーソルのキャプチャを要求または解除できます(確認済み:31,20-pointer-lock-control ではクリックハンドラーと時間指定システムからこの値を設定しています)。
PointerLock.createOrReplace(engine.CameraEntity, { isPointerLocked: false })
// request lock (e.g. from a button)
PointerLock.getMutable(engine.CameraEntity).isPointerLocked = true
// release lock
PointerLock.getMutable(engine.CameraEntity).isPointerLocked = false
注意点:カメラ上で PointerLock が作成されていない場合、getMutable(engine.CameraEntity) を呼び出すと例外が発生します。値を変更する前に、main() 内で一度 PointerLock.createOrReplace(engine.CameraEntity, { isPointerLocked: false }) を呼び出してください。true の設定はあくまで_要求_であり、実際にキャプチャするかどうかはクライアント/プレイヤー側で制御される場合があります(例:Esc キーでロックを解除できます)。
ポインターロックの変更検出
PointerLock.onChange(engine.CameraEntity, (pointerLock) => {
if (pointerLock?.isPointerLocked) {
console.log('Cursor locked')
} else {
console.log('Cursor unlocked')
}
})
カーソル位置とワールドレイ
カーソルの画面上の位置と、カーソルから 3D ワールドに向かって伸びるレイを取得します。
import { engine, PrimaryPointerInfo } from '@dcl/sdk/ecs'
function readPointer() {
const pointerInfo = PrimaryPointerInfo.getOrCreateMutable(engine.RootEntity)
console.log('Cursor position:', pointerInfo.screenCoordinates)
console.log('Cursor delta:', pointerInfo.screenDelta)
console.log('World ray direction:', pointerInfo.worldRayDirection)
}
engine.addSystem(readPointer)
フィールドの詳細
screenCoordinates(optional Vector2) — カーソルのピクセル単位の位置です。原点は画面の左上隅です(Y の正の方向は下向きです。UiTransform/UiCanvasInformationと同じ座標空間なので、カーソル座標をそのまま UI のpositionに指定できます)。カーソルがロックされている場合は画面の中央で固定されます。screenDelta(optional Vector2) — 前フレームからのマウス移動量(ピクセル単位)です。xの正の方向は右、yの正の方向は下です(screenCoordinatesと同じく左上が原点です)。そのため、マウスを上に動かすと、yは負の値になります。この点が参照用のマウスルックシーンで問題となり、sdk7-test-scenes の0e4eecfで修正されるまで、delta.yを減算していました。カーソルがロックされている間も、生のマウス移動量を報告し続けます。画面中央で固定されるscreenCoordinatesやworldRayDirectionとは異なります。そのため、ポインターロック中にマウスの移動量を読み取る唯一の方法がscreenDeltaであり、マウスルック/FPS カメラ制御に適した入力です(マウスルックのパターンについては、camera-control スキルを参照してください)。screenDeltaはモバイルでは常に 0 です(連続的なカーソルがないため)。screenCoordinatesとworldRayDirectionは両方のプラットフォームで動作します。worldRayDirection(optional Vector3) — カメラからカーソルを通る方向です。ロック中は画面中央を通るレイで固定されます。pointerType— なしの場合は0、マウスの場合は1です。
注意点:すべてのフィールドはオプションです。スキーマと 0,5-primary-cursor-info で確認済みのとおり、各値を読み取る前にチェックしてください(pointerInfo.screenCoordinates?.x ?? -666、pointerInfo.worldRayDirection?.x.toFixed(2))。最初に読み取る前にコンポーネントが存在するよう、getOrCreateMutable(engine.RootEntity) を使用し、フィールドは必ず null チェックしてください。worldRayDirection はカメラのレイキャスト方向として直接使用できます(そのシーンの「カーソル位置にスポーン」のパターンを参照してください)。
inputSystem による入力のポーリング
エンティティごとの入力コマンド
特定のエンティティで特定の入力アクションが発生したかどうかを確認します。
import { engine, inputSystem, InputAction, PointerEventType } from '@dcl/sdk/ecs'
function myInputSystem() {
// Check for click on a specific entity
const clickData = inputSystem.getInputCommand(
InputAction.IA_POINTER,
PointerEventType.PET_DOWN,
myEntity
)
if (clickData) {
console.log('Entity clicked via system:', clickData.hit.entityId)
}
}
engine.addSystem(myInputSystem)
返されるコマンドには hit データ(位置とエンティティ)が含まれます。何がクリックされたかを確認する必要がある場合は、getInputCommand() を使用してください。
任意のアクションに一致させるには InputAction.IA_ANY を渡します。getInputCommand(InputAction.IA_ANY, PointerEventType.PET_DOWN) は、押されたキーに対応するコマンドを返し、cmd.button でどのキーかを確認できます。
訂正 —「エンティティ引数を省略するとグローバルに確認できる」は誤りです。 エンティティを省略しても、「ターゲットなし」や「シーンのルート」を意味しません。
@dcl/ecs/src/engine/input.tsで確認したところ、isTriggeredとgetInputCommandはどちらもif (entity) { ...そのエンティティの結果... } else { ...globalState.thisFrameCommands... }で分岐しており、thisFrameCommandsはengine.getEntitiesWith(PointerEventsResult)を反復して埋められます。つまり、シーン内のすべてのエンティティが対象です。したがって、エンティティに紐づいた押下でもエンティティを指定しない呼び出しが成立し、シーンルートへのブロードキャストなのか、何らかのキューブをクリックしたのかを判別できません。
engine.RootEntityを渡しても解決しません。RootEntityは0であり、falsy と評価されるため、if (entity)によって同じ全エンティティ分岐に進みます。これにより、実際に誤診が発生しています。149,149-synthetic-input-showcaseでは、入力抑制テストが3回の実行にわたって壊れているように見え、無関係なステーションへのクリックでペイントステーションが作動しました。ルートエンティティの入力を正しく読み取るには、ルート自身の追記専用結果セットを読み取り、タイムスタンプのウォーターマークを追跡します。
let lastRootTimestampSeen = 0 engine.addSystem(() => { let maxTimestamp = lastRootTimestampSeen for (const cmd of PointerEventsResult.get(engine.RootEntity)) { if (cmd.timestamp <= lastRootTimestampSeen) continue if (cmd.timestamp > maxTimestamp) maxTimestamp = cmd.timestamp if (cmd.state !== PointerEventType.PET_DOWN) continue // ...ルートレベルの押下を処理... } lastRootTimestampSeen = maxTimestamp })同じ問題は
0,1-input-modifierテストシーンにもあります(エンティティを指定しないgetInputCommand(InputAction.IA_ANY, PointerEventType.PET_DOWN))。この行を「グローバル入力」の慣用句としてコピーしないでください。
Tag を使ったエンティティごとのクックブック(エンティティに Tag を付け、engine.getEntitiesByTag で取得し、システム内で各エンティティを getInputCommand でポーリングする方法)については、{baseDir}/references/input-patterns.md →「エンティティごとの入力コマンドのクックブック(Tag ベース)」を参照してください。
グローバル入力の確認
プレイヤーのカーソルがエンティティを指しているかどうかに関係なく、特定のキーが押されたかどうかを確認します。
1回限りのアクション(武器を発射する、ドアを開ける)には isTriggered() を使用します。キーが最初に押されたフレームだけ true を返します。連続するアクション(移動、シールドを構え続ける)には isPressed() を使用します。キーを押し続けている間、毎フレーム true を返します。
function globalInputSystem() {
// Was the key just pressed this frame?
if (inputSystem.isTriggered(InputAction.IA_PRIMARY, PointerEventType.PET_DOWN)) {
console.log('E key pressed!')
}
// Is the key currently held down?
if (inputSystem.isPressed(InputAction.IA_SECONDARY)) {
console.log('F key is held!')
}
}
engine.addSystem(globalInputSystem)
エンティティ間の入力干渉(@dcl/sdk 7.28.0 で修正)
2778e4bb。エンティティごとのコマンドスキャンは、エンティティのコマンドを新しい順に調べ、グローバルなボタン状態より新しくないコマンドに到達すると停止していました。その状態は、同じフレーム内で先に処理されたエンティティが通常すでに書き込んでいました。スキャンはそこでそのエンティティの残りのコマンドの確認を中断し、別のボタンに関するコマンドまで取りこぼしていました。
7.27.x 以前での症状:同じフレーム内に、あるエンティティ上でポインターを離し、別のエンティティ上でキーを押すと、そのキー入力が失われていました。 isPressed とグローバルな isTriggered はどちらも false を返す一方、エンティティごとの isTriggered は true のままでした。この矛盾により、シーンのロジックに問題があるように見えていました。
スキャンは、別のエンティティのコマンドに到達した時点ではなく、フレームの境界で停止するようになりました。シーンのコードを変更する必要はありません。回避策の使用をやめ、古い SDK を使用している場合は以前の動作がまだ残っていることに注意してください。
すべての InputAction 値
| InputAction | キー/ボタン |
|---|---|
IA_POINTER | マウスの左ボタン |
IA_PRIMARY | E キー |
IA_SECONDARY | F キー |
IA_ACTION_3 | 1 キー |
IA_ACTION_4 | 2 キー |
IA_ACTION_5 | 3 キー |
IA_ACTION_6 | 4 キー |
IA_JUMP | スペースキー |
IA_FORWARD | W キー |
IA_BACKWARD | S キー |
IA_LEFT | A キー |
IA_RIGHT | D キー |
IA_WALK | Control キー |
IA_MODIFIER | Shift キー(走る) |
IA_ANY | 任意の入力アクションに一致します(ワイルドカード。getInputCommand と併用します) |
イベントの種類
PointerEventType.PET_DOWN // Button/key pressed
PointerEventType.PET_UP // Button/key released
PointerEventType.PET_HOVER_ENTER // Cursor enters entity
PointerEventType.PET_HOVER_LEAVE // Cursor leaves entity
InputModifier(移動制限)
プレイヤーの移動を制限または停止します。
import { engine, InputModifier } from '@dcl/sdk/ecs'
// Freeze player completely
InputModifier.create(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({ disableAll: true })
})
// Restrict specific movement (all flags optional; a false/omitted flag is ignored)
InputModifier.createOrReplace(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({
disableWalk: true,
disableJog: true,
disableRun: true,
disableJump: true,
disableEmote: true,
disableDoubleJump: true,
disableGliding: true
})
})
// Restore normal movement
InputModifier.deleteFrom(engine.PlayerEntity)
標準フラグ(すべて省略可能なブール値。input_modifier.gen.d.ts で確認済み):disableAll、disableWalk、disableJog、disableRun、disableJump、disableEmote、disableDoubleJump、disableGliding。false のフラグと省略されたフラグは無視されます(帯域幅を消費しません)。InputModifier.Mode.Standard({...}) と生の { $case: 'standard', standard: {...} } 形式は同等です(どちらもテストシーンで確認されています)。
重要: InputModifier は DCL 2.0 デスクトップクライアントでのみ動作します。ウェブブラウザーのエクスプローラーでは効果がありません。シーンで InputModifier を使用する場合は、デスクトップクライアントでテストしてください。
カットシーンのパターン
カットシーンの実装例(シネマティック演出中に disableAll でプレイヤーを停止し、InputModifier.deleteFrom で移動を再開する方法)については、{baseDir}/references/input-patterns.md → 「カットシーンのパターン(シネマティック演出中にプレイヤーを停止)」を参照してください。
WASD 移動パターン
WASD でカスタムエンティティを動かすパターン(isPressed で IA_FORWARD/IA_BACKWARD/IA_LEFT/IA_RIGHT をポーリングして Transform を移動する方法、および InputModifier でアバターを停止する方法と、WASD のポーリングがプレイヤーの移動とどう関係するかについての注記)については、{baseDir}/references/input-patterns.md → 「WASD 移動パターン(カスタムエンティティを動かす)」を参照してください。
入力パターンの組み合わせ
アクションバー/数字キーのパターン(isTriggered で IA_ACTION_3~IA_ACTION_6 をアビリティスロットに割り当てる方法)については、{baseDir}/references/input-patterns.md → 「数字キーを使ったアクションバー」を参照してください。
プラットフォームの検出
シーンがモバイルで実行されているかを検出し、コントロールや UI を状況に応じて調整します。
import { getPlatform, isMobile } from '@dcl/sdk/platform'
// getPlatform() returns 'mobile' | 'desktop' | 'web' | null
// Returns null until the explorer reports its platform (async, shortly after scene start)
// Defer platform-dependent setup until getPlatform() is non-null:
function platformCheckSystem() {
if (getPlatform() === null) return
engine.removeSystem(platformCheckSystem)
if (isMobile()) {
// mobile-specific setup here (e.g. larger UI, touch-friendly interactions)
}
}
engine.addSystem(platformCheckSystem)
@dcl/sdk/platform からインポートします。ドキュメントのコミット 17ca7be に基づいて確認済みです。
プレイヤーの言語(getPlayerLanguage)
@dcl/sdk 7.30.0 以降が必要です(js-sdk-toolchain 201a8a35、PR #1622 — 7.29.0 のタグ後に取り込まれ、7.30.0 でリリースされました。git merge-base --is-ancestor 201a8a35 7.30.0 で確認済み)。古いプロジェクトで使用する前に、シーンのバージョン固定を確認してください。
同じモジュール、@dcl/sdk/platform を使用します。UI テキスト、NPC の会話、看板、ホバーテキストのローカライズ文字列を選ぶ際に使用してください。
import { getPlayerLanguage, onPlayerLanguageChanged } from '@dcl/sdk/platform'
const STRINGS: Record<string, string> = { en: 'Press E to open', es: 'Pulsa E para abrir' }
function label() {
const lang = getPlayerLanguage() // 'es', 'pt-BR', …
return STRINGS[lang] ?? STRINGS[lang.split('-')[0]] ?? STRINGS.en
}
onPlayerLanguageChanged.add(({ language }) => {
// the player switched the client language mid-session — rebuild any cached strings
})
getPlayerLanguage(): string— BCP-47 タグ('es'、'pt-BR')です。getExplorerInformation().configurations['locale']から取得し、_は-に正規化され(pt_BR→pt-BR)、前後の空白は削除されます。^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$に一致しない値は'en'にフォールバックします。- 注意 — 同期的に動作し、エクスプローラーから応答があるまでは
'en'を返します。 不明な間はnullを返すgetPlatform()とは異なり、ここには「まだ不明」を示す値がありません。main()で呼び出すと、すべてのクライアントで確実に'en'が返されます。最初のフレームが描画された後にシステム内で読み取るか、onPlayerLanguageChangedから再度読み取ってください。 onPlayerLanguageChanged: Observable<{ language: string }>は、実際に言語が変わった場合にのみ発火します(同じタグを 2 回設定した場合、通知は 1 回です)。古いクライアントでは発火しません。- クライアントからロケールが報告されない場合、
configurationsがない場合、またはgetExplorerInformationRPC が拒否された場合は'en'を返します。地域サブタグはそのまま渡されます。まず完全なタグで照合し、次に主要サブタグ、最後に'en'の順でフォールバックしてください。 - 内部では、
IEventsにlocaleChanged: { locale: string }シーンイベントが追加されます。@dcl/sdk/platformをインポートすると、このイベントが自動的にサブスクライブされるため、追加の設定は不要です。
画面上のタッチ操作(TouchScreenControls)
概要:モバイルクライアントのネイティブ画面上操作(仮想ジョイスティック、照準、ゲームパッドボタン)を設定します。SDK 7.26.0 以降が必要です。
import { engine, TouchScreenControls, InputAction } from '@dcl/sdk/ecs'
engine.RootEntityに設定してください。クライアントはそれ以外の場所では読み取りません。- プレイヤーがシーン内にいる間に適用され、退出するとデフォルトに戻るため、このコンポーネントを使用しないシーンには影響しません。
- ネイティブの画面上操作に対応していないプラットフォーム(デスクトップ)では何も行わず、VR でも影響はありません。
isMobile()によるガードは不要で、無条件に記述しても安全です。 - 対象は入力操作のみです。クライアント独自の HUD(エモートホイール、プロフィール、チャット、ミニマップ)には影響しません。
- シーンで Creator Hub UI Designer を使用している場合、このコンポーネントはエディターの所有物です。 Creator Hub 0.50.0 以降では、UI Designer に
TouchScreenControlsを視覚的に編集する MobileHUD エントリーが追加され、src/mobile-hud.tsを管理します(必要に応じて遅延作成され、設定がデフォルトに戻ると自動削除されます)。engine.RootEntity上のコンポーネントは 1 つだけであり、最後に書き込んだ設定が有効になるため、このようなシーンで別の場所から手書きのcreateOrReplaceを実行すると、パネルの設定が上書きされるか、逆にパネルによって上書きされます。TouchScreenControlsを手書きで記述する前にsrc/mobile-hud.tsがあるか確認し、build-ui スキルの「MobileHUD」も参照してください。
PBTouchScreenControls のフィールド:
| フィールド | 型 | デフォルト値 | 説明 |
|---|---|---|---|
touchInputs | PBTouchScreenControls_TouchInput[] | [] | ボタンごとの上書きです。記載されていないボタンはデフォルトのままです(表示、デフォルトのアイコン)。 |
mainAction | InputAction | undefined | undefined | 大きな中央ボタンで実行するアクションです。未設定の場合はデフォルト(IA_JUMP)が維持されます。 |
hideJoystick | boolean | false | 標準の仮想ジョイスティックを削除します。 |
hideCrosshair | boolean | false | 画面上のクロスヘア/照準を非表示にします。 |
TouchInput エントリー:{ inputAction: InputAction, hide: boolean, icon?: TextureUnion } — icon はボタンのアイコンをシーン画像で上書きします。IA_JUMP では、動的な状態(ジャンプ/二段ジャンプ/滑空)のすべてが置き換わります。
画面上のボタンに対応するのはゲームパッドのアクションのみです:IA_POINTER(インタラクション)、IA_PRIMARY(E)、IA_SECONDARY(F)、IA_JUMP(中央)、IA_ACTION_3..IA_ACTION_6(1/2/3/4)。その他の InputAction(移動アクション、IA_ANY、IA_MODIFIER、不明または将来追加される値)は無視されます。これらを指定した touchInputs エントリーは効果がなく、有効なゲームパッドアクションではない mainAction は IA_JUMP にフォールバックします。
コンポーネントの便利なヘルパー(いずれも RootEntity に書き込み、現在の値とマージするため、どこからでも呼び出せます):
| ヘルパー | 効果 |
|---|---|
TouchScreenControls.hide(actions: InputAction[]) | 指定したボタンを非表示にし、現在の設定にマージします。 |
TouchScreenControls.hideAll() | 8 つすべてのゲームパッドボタンを非表示にします。 |
TouchScreenControls.showAll() | ボタンの非表示リストをクリアします。ジョイスティック/クロスヘアには影響しません。 |
TouchScreenControls.setMainAction(action: InputAction) | 大きな中央ボタンのアクションを設定します。 |
TouchScreenControls.hideJoystick() / .showJoystick() | 標準の仮想ジョイスティックを切り替えます。 |
TouchScreenControls.hideCrosshair() / .showCrosshair() | クロスヘア/照準を切り替えます。 |
TouchScreenControls.hideJoystick()
TouchScreenControls.setMainAction(InputAction.IA_PRIMARY)
TouchScreenControls.hide([InputAction.IA_ACTION_3, InputAction.IA_ACTION_4])
注意点:
showAll()はtouchInputsを[]にリセットするため、これを通じて設定したカスタムiconも破棄されます。その後、アイコンを再設定してください。- ボタンを非表示にしてもアクションは無効になりません。別の方法で実行できる場合、
inputSystemは引き続きそのアクションを報告します。非表示になるのは ボタン であり、入力 ではありません。 - ジョイスティックを非表示にすると、モバイルプレイヤーは標準の移動手段を失います。シーン UI で代替するか、シーン内で移動しない設計にしてください。
- ボタンの位置は変更できません。スロットは固定されており、シーンで選択できるのは、どのボタンを表示するかと、どのボタンをメインにするかだけです。
生の形式(ヘルパーが書き込む内容)— icon のネストされた形式に注意してください。これは通常のパスではなく TextureUnion です。33,20-spectate-mode より:
import { engine, InputAction, TouchScreenControls } from '@dcl/sdk/ecs'
import { isMobile } from '@dcl/sdk/platform'
const ICON_DIR = 'assets/Images/spectate-mode'
const textureIcon = (src: string) => ({ tex: { $case: 'texture' as const, texture: { src } } })
TouchScreenControls.createOrReplace(engine.RootEntity, {
hideJoystick: false,
hideCrosshair: false,
touchInputs: [
{ inputAction: InputAction.IA_ACTION_3, hide: false, icon: textureIcon(`${ICON_DIR}/icon-next.png`) },
{ inputAction: InputAction.IA_ACTION_5, hide: true },
{ inputAction: InputAction.IA_PRIMARY, hide: false, icon: textureIcon(`${ICON_DIR}/icon-zoomIn.png`) },
],
})
createOrReplace は設定全体を置き換えます(ヘルパーはマージします)。解除するには touchInputs: [] を指定します。シーンの状態に応じてアイコンを変更する場合は、そのたびに再度呼び出してください。たとえば、spectate シーンでは、追跡対象が選択されているかどうかに応じて、ズームイン/ズームアウトを上げる/下げるに切り替えます。
UiInputBinding(UiEntity の uiInputBinding プロパティ。build-ui を参照)と組み合わせることで、画面上のアクションボタンをカスタマイズできます。ここでは標準ボタンを非表示にし、同じ InputAction を独自のシーン UI にバインドします。
タッチ操作に合わせたモバイル UI のサイズ調整: isMobile() は、TouchScreenControls 用ではなく、シーン独自の UI を切り替えるためのものです(デスクトップでは TouchScreenControls は安全に何もしません)。33,20-spectate-mode のパターンでは、isMobile() でシーンの HUD を分岐します。ポインターロックの案内を非表示にし、キーボード操作の説明をジョイスティック/タップ操作の説明に切り替え、W/A/S/D のキーキャップの代わりにアイコンのキャップを表示し、タップ領域を大きくします(このシーンでは 32px から 48px に拡大しています)。[未検証:48px はこのシーンで測定された値であり、公開されている Decentraland の最小値ではありません。妥当な開始点として扱い、実機で確認してください。]
ボタンの優先順位スタック、「+」オーバーフローのルール、カスタムアイコン、詳しい使用例については、{baseDir}/references/touch-screen-controls.md を参照してください。
モバイルに関する考慮事項
モバイル関連ドキュメントの拡張(コミット 17ca7be)からの重要な情報:
- タッチ操作のみ — マウスのホバー状態、キーボードショートカット、右クリックには対応していません。
- モバイル UI では
borderRadiusはサポートされません — モバイル向けシーンでは角を丸くしないでください。 - SDK 7.26.0 以降では、画面上のコントロールをシーンごとに設定できます。
TouchScreenControlsを使って、ジョイスティック、照準、個々のゲームパッドボタンを非表示にしたり、中央のボタンを再割り当てしたり、ボタンのグリフをシーン画像に置き換えたりできます。位置は引き続き固定です。これは、モバイル HUD は静的だとする古いドキュメントに代わるものです。上記のTouchScreenControlsセクションを参照してください。 - 「モバイルでは約 3 倍に拡大する」という以前のルールは、記載どおりには適用されなくなりました。 SDK 7.26.0 以降では、ピクセル単位で指定した UI は、
devicePixelRatioが UI のスケール係数から削除されたため、スマートフォン上では以前よりすでに約 2~3 倍大きく表示されます。また、モバイルの仮想画面(デスクトップの1920x1080に対して1600x720)により、さらに約 1.2 倍大きくなります。まずデスクトップ用のサイズから始め、デバイス上で測定し、不足するものだけを拡大してください。[[build-ui]] を参照してください。 - SDK 7.26.0 以降では、UI はデバイスのセーフエリア(ノッチ、ホームインジケーター)内に自動的に収まります — レンダラーの
screenInsetオプションはデフォルトで'device'です。UI を画面全体に表示したい場合に限り、screenInset: 'none'を渡してください。その場合、個々のサブツリーをインセットするためにScreenInsetAreaコンポーネントを使用できます。7.26.0 より前のバージョンでは、@dcl/sdk/react-ecsのScreenInsetAreaで UI を自分でラップしてください。[[build-ui]] を参照してください。 - SDK の機能がデスクトップクライアントと同等かどうか(ドキュメントのコミット
5f212e3、2026 年 10 月、モバイルアプリ v1.15.0 にて確認):- 利用可能になった機能:
ParticleSystem、PrimaryPointerInfo、AssetLoad、LightSource(シーンのダイナミックライト、v1.13.0)、アバターマスク/上半身のみのエモート(v1.13.0)。音声機能の同等化(音量カーブ+空間減衰)、AudioEvent(PBAudioEvent)、AudioAnalysis(PBAudioAnalysis)、ピンチ操作によるズーム(v1.14.0)。制限付きアクションのstopEmote、TweenのMoveRotateScaleモード、他のプレイヤーの位置をローカルプレイヤーと同様にシーン相対座標で報告する機能(getPlayer({ userId }).position、onEnterScene、他のプレイヤーにアタッチされたアイテム)、プレイヤー移動の Unity との同等化に向けた最初の対応、傾斜したジオメトリ上で下向きにプレイヤーをレイキャストした際の正確なhitDistance(v1.15.0)。 - 未対応、提供時期未定:
TweenSequenceは実装されていません — シーケンス化された Tween はまったくアニメーションしません。 単体のTweenコンポーネントは動作します(v1.15.0 以降のMoveRotateScaleを含む)。動作しないのは シーケンス ラッパーです。複数段階の動きはシステムから制御してください。[[animations-tweens]] を参照してください。AvatarNametag— シーンで作成する、プレイヤーのネームタグの上に表示されるプレートです。(以前は v1.14.0 で提供予定と記載されていましたが、2026 年 10 月の更新で未対応に戻されました。)- Draco メッシュ圧縮(
KHR_draco_mesh_compression) — モバイルアプリには 実行時 Draco デコーダーがありません。Draco モデルを読み込めるのは、アセットサーバーがすでに展開しているシーンの 最適化済みアセット からのみです。最適化済みアセットがない場合(ローカルプレビュー)は、エラーが表示されず、読み込みに失敗します。Draco を使わずにエクスポートしてください。 - カスタムフォント(
fontSrc) — モバイルアプリでは、TextShapeと UI テキストに常にfontのフォールバックが使用されます。fonts を参照してください。 UiBackgroundの ナインスライス(引き伸ばしではなく、モバイルでタイル表示)、近接ボイスチャット、ワールド内のポイント指定。Smart Items はモバイルで公式にはサポートされていません。
- 既知の不整合(v1.15.0 の移動に関する対応を除く):傾斜、段差、壁、着地、移動プラットフォーム、滑空の挙動は、依然として Unity と異なります。シーンコライダーの厚さが 1 cm 未満の場合、上からのみ衝突します。また、見えない障害物や一部の SDK 衝突レイヤーは、プレイヤーをまったくブロックしません。スポーン/着地/ジャンプのアニメーションと移動のブレンドは未対応です。他のクライアントからの一部のエモート(Bevy-Web のシーンエモート、LiveKit 経由の Unity エモート)はモバイルで再生されません。Creator Hub のモバイルプレビューでは、モバイルプレイヤーのアバターが デスクトップのテスターから見えません。デスクトップでの操作感だけを基準に、シビアなプラットフォームアクションや段差の形状を調整しないでください — [[player-physics]] を参照してください。
- モバイルには、ライト、VFX エミッター、パーティクル、三角形など、独自の固定シーン上限もあります — Mobile Scene Limits の [[optimize-scene]] を参照してください。最新情報については、ドキュメントの機能対応状況トラッカーを確認してください。
- 利用可能になった機能:
サンプルシーン
これらの API をテストするエンジンチームのシーン(正解データ):
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,1-input-modifier — InputModifier の標準フラグ(
disableWalk/disableJogを含む)、押されたキーを読み取るgetInputCommand(IA_ANY, PET_DOWN)。注意点: エンティティを指定しないこのgetInputCommandの呼び出しは、すべてのエンティティのPointerEventsResultから応答が返されるため、エンティティに紐づいたクリックでも発火します。「エンティティごとの入力コマンド」にある修正を参照してください。グローバル入力の書き方としてコピーしないでください。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/149,149-synthetic-input-showcase — 2×2区画のリグに10個のステーション(S1~S10)を配置し、Unity Explorer MCP で合成入力の全機能(歩行、カメラ操作、クリック/ホバー、キー入力、UI テキスト入力とクリック)を試します。前述のルートエンティティから入力を読み取るパターン、背の高い不可視
TriggerAreaによる修正、占有状態を再計算するInputModifierパターンの出典です。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/33,20-spectate-mode — 観戦/フリーカメラ:2エンティティの
VirtualCameraリグ、アバターを停止するInputModifier disableAll、追従対象を切り替えるためのonEnterScene/onLeaveSceneによる対象リスト、TouchScreenControlsとisMobile()によるモバイル対応(状態ごとにアイコンを切り替えます)。README に記載されているのはデスクトップの操作割り当てのみです。モバイルへの対応はソースに記載されています。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/31,20-pointer-lock-control —
PointerLock.isPointerLockedに値を書き込んでカーソルの捕捉を要求/解除する方法、PointerLock.onChange。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,5-primary-cursor-info — 毎フレーム
PrimaryPointerInfo(画面座標/差分/ワールドのレイ方向)を読み取り、worldRayDirectionをカメラのレイキャストに渡す方法。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/2,22-virtual-cameras —
isPressed(IA_FORWARD/...)を使った WASD 操作可能なカメラ、VirtualCamera と連動した InputModifier の切り替え。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,0-cube-spawner —
getEntitiesWith(Cube, PointerEvents)とinputSystem.isTriggered(IA_POINTER, PET_DOWN, entity)を使った、システムベースのエンティティごとのクリック処理。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/32,20-virtual-camera-mouse-look — ポインターをロックしている間、
PrimaryPointerInfo.screenDeltaでカメラのマウスルックを操作する方法。ロック中もscreenDeltaはマウスの生の動きを報告し続ける一方、screenCoordinatesは画面中央で固定されることを示します。
リファレンス
{baseDir}/references/input-patterns.md— 分岐固有の実用パターン:タグベースのエンティティごとの入力レシピ、カットシーンでの停止/復元フロー、WASD 操作のカスタムエンティティ、アクションバーの数字キー割り当て。{baseDir}/references/touch-screen-controls.md—TouchScreenControls:ボタンの優先順位スタックと「+」オーバーフローのルール、カスタムボタンアイコン、画面をすっきりさせる例/完全カスタム HUD の例、ヘルパーの動作仕様。
基本的なポインターイベントとクリックハンドラーについては、add-interactivity スキルを参照してください。