mcpField
A
advanced-input

advanced-input

github.com/decentraland开发4292 次安装今天更新
代码仓库

关于 advanced-input

面向 Decentraland SDK 场景开发者的高级输入处理技能,涵盖指针锁定、鼠标射线、按键轮询和移动限制。它说明了复杂输入模式的适用方式及容易导致误判的实体输入细节。

主要功能

  1. 01读取和控制指针锁定状态
  2. 02获取光标坐标、位移与世界射线
  3. 03轮询全局或实体输入命令
  4. 04通过 InputModifier 限制移动
  5. 05检测运行平台和玩家语言

适用场景

  1. 01实现第一人称鼠标视角控制
  2. 02为不同实体处理按键或指针事件
  3. 03在过场动画期间冻结玩家移动
  4. 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-scenes 0e4eecf 修正这一问题。光标锁定时仍会报告原始鼠标移动量——而 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_PRIMARYE 键
IA_SECONDARYF 键
IA_ACTION_31 键
IA_ACTION_42 键
IA_ACTION_53 键
IA_ACTION_64 键
IA_JUMP空格键
IA_FORWARDW 键
IA_BACKWARDS 键
IA_LEFTA 键
IA_RIGHTD 键
IA_WALKControl 键
IA_MODIFIERShift 键(奔跑)
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 或 getExplorerInformation RPC 被拒绝时,返回 '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 字段:

字段类型默认值说明
touchInputsPBTouchScreenControls_TouchInput[][]按钮级覆盖设置。未列出的按钮保留默认状态(显示、使用默认图标)。
mainActionInputAction | undefinedundefined大型中央按钮触发的操作。未设置时,保留默认操作(IA_JUMP)。
hideJoystickbooleanfalse移除原生虚拟摇杆。
hideCrosshairbooleanfalse隐藏屏幕上的准星/瞄准标记。

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 的场景(权威依据):

参考资料

  • {baseDir}/references/input-patterns.md — 特定分支的实用模式:基于 Tag 的按实体输入指南、过场动画冻结/恢复流程、WASD 驱动的自定义实体、操作栏数字键映射。
  • {baseDir}/references/touch-screen-controls.md — TouchScreenControls:按钮优先级堆栈和“+”溢出规则、自定义按钮图标、界面精简/完全自定义 HUD 示例、辅助工具语义。

基本指针事件和点击处理程序请参阅 add-interactivity skill。

评论

支持 Markdown