关于 advanced-input
面向 Decentraland SDK 场景开发者的高级输入处理技能,涵盖指针锁定、鼠标射线、按键轮询和移动限制。它说明了复杂输入模式的适用方式及容易导致误判的实体输入细节。
主要功能
- 01读取和控制指针锁定状态
- 02获取光标坐标、位移与世界射线
- 03轮询全局或实体输入命令
- 04通过 InputModifier 限制移动
- 05检测运行平台和玩家语言
适用场景
- 01实现第一人称鼠标视角控制
- 02为不同实体处理按键或指针事件
- 03在过场动画期间冻结玩家移动
- 04根据平台或语言调整场景界面
安装
复制下面的命令,在终端中运行。
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(可选 Vector2) — 光标的像素位置。原点位于屏幕左上角(Y 轴正方向朝下——与UiTransform/UiCanvasInformation使用相同的坐标空间,因此可以直接将光标坐标传入 UI 的position)。光标锁定时,该值会固定在屏幕中心。screenDelta(可选 Vector2) — 鼠标自上一帧以来移动的像素数。x正值表示向右,y正值表示向下(与screenCoordinates使用相同的左上角原点),因此鼠标向上移动时,y为负值。参考鼠标视角场景曾因此出错:它们会减去delta.y,直到 sdk7-test-scenes0e4eecf修正这一问题。光标锁定时仍会报告原始鼠标移动量——而screenCoordinates和worldRayDirection会固定在屏幕中心。因此,screenDelta是指针锁定期间读取鼠标移动的唯一方式,也是鼠标视角/FPS 摄像机控制的正确输入(请参阅 camera-control 技能中的鼠标视角模式)。移动设备上的screenDelta始终为 0(没有连续光标);两个平台上都可以使用screenCoordinates和worldRayDirection。worldRayDirection(可选 Vector3) — 从摄像机穿过光标的方向。锁定时,该值会固定为中心射线方向。pointerType—0表示无,1表示鼠标。
注意:每个字段都是可选的——已通过架构和 0,5-primary-cursor-info 验证,后者会检查每次读取(pointerInfo.screenCoordinates?.x ?? -666、pointerInfo.worldRayDirection?.x.toFixed(2))。使用 getOrCreateMutable(engine.RootEntity),确保首次读取前组件已存在,并始终对字段进行空值检查。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) { ...that entity's results... } else { ...globalState.thisFrameCommands... }分支,而thisFrameCommands是通过遍历engine.getEntitiesWith(PointerEventsResult)填充的——也就是说,它涵盖场景中的每个实体。因此,绑定到实体的按下操作会满足不带实体参数的调用,而且你无法区分场景根实体广播和点击某个立方体。传入
engine.RootEntity也没有用。RootEntity的值是0,属于假值,因此if (entity)会让它进入遍历所有实体的同一分支。这导致了实际的误诊:在149,149-synthetic-input-showcase中,它使一项输入抑制测试在三次运行中都被误判为失效,还会因点击无关的工作站而启动涂装工作站。若要真正读取根实体的输入,请读取根实体自身只增不减的结果集,并跟踪时间戳水位线:
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 // ...handle the root-level press... } lastRootTimestampSeen = maxTimestamp })基于 Tag 的按实体操作指南也存在同样的问题(
getInputCommand(InputAction.IA_ANY, PointerEventType.PET_DOWN)不带实体参数)——不要照搬那一行作为“全局输入”的惯用写法。
关于基于 Tag 的按实体操作指南(为实体添加 Tag、使用 engine.getEntitiesByTag 获取它们,并在系统中逐个轮询 getInputCommand),请参阅 {baseDir}/references/input-patterns.md → “按实体输入命令操作指南(基于 Tag)”。
全局输入检查
检查特定按键是否被按下,不论玩家的光标是否指向某个实体。
对于一次性操作(开火、开门),使用 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 桌面客户端。在网页浏览器探索器中不会生效——如果场景依赖此功能,请使用桌面客户端进行测试。
过场动画模式
如需查看完整的过场动画流程(在电影片段期间使用 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'。- 注意事项——这是同步函数,在 explorer 响应前会返回
'en'。 与未知时返回null的getPlatform()不同,这里没有“尚未获取”的值:在main()中调用它,会在每个客户端上稳定地得到'en'。请在首帧之后的系统中读取,或通过onPlayerLanguageChanged再次读取。 onPlayerLanguageChanged: Observable<{ language: string }>仅在语言确实发生变化时触发(两次设置相同标签只会通知一次)。较旧的客户端不会触发此事件。- 当客户端未报告语言区域、缺少
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 中提供 MobileHUD 条目,可通过可视化方式编辑
TouchScreenControls,并管理src/mobile-hud.ts(按需写入,配置恢复默认值时自动删除)。engine.RootEntity上只有一个组件,且以最后一次写入为准,因此在此类场景的其他位置手动调用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() | 隐藏全部八个游戏手柄按钮。 |
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: [] 清除配置。每当图标需要随场景状态变化时,都要再次调用它 — 观战场景会根据是否选中了跟随目标,在放大/缩小图标与上升/下降图标之间切换。
与 UiInputBinding 配合使用(UiEntity 上的 uiInputBinding 属性,参见 build-ui),即可构建自定义屏幕操作按钮:在此处隐藏原生按钮,然后将相同的 InputAction 绑定到你自己的场景 UI。
与触控控件搭配的移动端 UI 尺寸: isMobile() 用于切换_场景自身_的 UI,而不是用于切换 TouchScreenControls(它在桌面端会安全地不执行任何操作)。33,20-spectate-mode 中的做法是在场景 HUD 上根据 isMobile() 分支处理:隐藏指针锁定提示,将键盘操作说明替换为摇杆/轻触操作说明,使用图标键帽替代 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 已经比以前大约 2–3 倍(
devicePixelRatio已从 UI 缩放系数中移除),而移动端虚拟屏幕(1600x720,桌面端为1920x1080)又会使其额外放大约 1.2 倍。请从桌面端尺寸开始,在设备上测量,只放大显示不足的部分。请参阅 [[build-ui]]。 - SDK 7.26.0+ 会自动将 UI 限制在设备安全区域内(避开刘海和主屏幕指示条)——渲染器的
screenInset选项默认值为'device'。只有在希望 UI 覆盖整个屏幕时,才传入screenInset: 'none';此时可使用ScreenInsetArea组件为单个子树添加内边距。在低于 7.26.0 的版本中,请自行使用ScreenInsetArea(来自@dcl/sdk/react-ecs)包裹 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组件可以正常工作(包括自 v1.15.0 起支持的MoveRotateScale);失效的是 sequence 包装器。请改为通过系统驱动多步移动。请参阅 [[animations-tweens]]。 AvatarNametag——场景中为玩家姓名标签上方绘制的铭牌。(此前曾列为 v1.14.0 已发布;2026 年 10 月更新后重新列为未支持。)- Draco 网格压缩(
KHR_draco_mesh_compression)——移动应用没有 Draco 运行时解码器。只有场景的优化资源中的 Draco 模型可以加载,因为资源服务器已提前对其解压;如果没有优化资源——例如在本地预览中——模型会静默加载失败,且不会报错。导出时请勿使用 Draco。 - 自定义字体(
fontSrc)——移动应用始终会在TextShape和 UI 文本中渲染font后备字体。请参阅字体。 UiBackground九宫格(移动端会平铺而非拉伸)、近距离语音聊天、指向场景中的对象。移动端不正式支持 Smart Items。
- **未实现
- 已知差异(除 v1.15.0 的移动功能更新外):斜坡、边缘、墙壁、着陆、移动平台和滑翔行为仍与 Unity 不同;厚度小于 1 cm 的场景碰撞体仅能从上方发生碰撞,不可见阻挡物和某些 SDK 碰撞层完全无法阻挡玩家;缺少出生 / 着陆 / 跳跃动画和移动混合;来自其他客户端的某些表情(Bevy-Web 场景表情、通过 LiveKit 传输的 Unity 表情)不会在移动端播放;在 Creator Hub 移动端预览中,桌面端测试者看不到移动端玩家的头像。请勿仅根据桌面端的手感调整精细的平台跳跃或边缘几何体——请参阅 [[player-physics]]。
- 移动端还有自身的场景总量上限(光源、VFX 发射器、粒子、三角形……)——请参阅 [[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 — 一个占地 2x2 个地块、包含十个站点(S1-S10)的装置,通过 Unity Explorer MCP 展示完整的合成输入功能:行走、摄像机视角、点击/悬停、按键、UI 文本输入和点击。这里展示了上述根实体输入读取模式、高而不可见的
TriggerArea修复方案,以及重新计算占用状态的InputModifier模式。 - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/33,20-spectate-mode — 观察/自由摄像机模式:由两个实体组成的
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— 特定分支的实用模式:基于 Tag 的按实体输入指南、过场动画冻结/恢复流程、WASD 驱动的自定义实体、操作栏数字键映射。{baseDir}/references/touch-screen-controls.md—TouchScreenControls:按钮优先级堆栈和“+”溢出规则、自定义按钮图标、界面精简/完全自定义 HUD 示例、辅助工具语义。
基本指针事件和点击处理程序请参阅 add-interactivity skill。