player-avatar
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePlayer and Avatar System in Decentraland
Decentraland 中的玩家与 Avatar 系统
CRITICAL: The player Transform is READ-ONLY from scene code
关键:玩家 Transform 从场景代码中是只读的
Transformengine.PlayerEntitytypescript
// WRONG — compiles cleanly, runs, does NOTHING in-world
const t = Transform.getMutable(engine.PlayerEntity)
t.position.y += 0.1 // ignored
t.position = Vector3.create(8, 0, 8) // ignored
Transform.createOrReplace(engine.PlayerEntity, { ... }) // ignoredSymptom to recognize: TypeScript accepts the code, the system ticks, no console error, but the avatar never moves. If you wrote and expected motion, this is your bug.
Transform...PlayerEntityCorrect API by intent:
| Goal | Use | Skill |
|---|---|---|
| Instant teleport / smooth slide to a point | | this skill, see below |
| Lift / float / launch / jump pad / knockback / push / wind / repulsion | | |
| Restrict / freeze movement | | |
| Change run speed / jump height | | this skill, see below |
Transform.get(engine.PlayerEntity)engine.PlayerEntityTransformtypescript
// WRONG — compiles cleanly, runs, does NOTHING in-world
const t = Transform.getMutable(engine.PlayerEntity)
t.position.y += 0.1 // ignored
t.position = Vector3.create(8, 0, 8) // ignored
Transform.createOrReplace(engine.PlayerEntity, { ... }) // ignored需要识别的症状: TypeScript 可以正常接受代码,系统也会持续运行,控制台没有错误,但 Avatar 永远不会移动。如果你编写了 并期望产生运动,问题就出在这里。
Transform...PlayerEntity按目标选择正确的 API:
| 目标 | 使用方式 | Skill |
|---|---|---|
| 立即传送或平滑移动到某个点 | | 本 Skill,见下文 |
| 抬起、漂浮、发射、跳跃板、击退、推动、风力或排斥力 | | |
| 限制或冻结移动 | 在 | |
| 更改奔跑速度或跳跃高度 | 在 | 本 Skill,见下文 |
Transform.get(engine.PlayerEntity)Player Position and Movement (Reading)
玩家位置与移动(读取)
Access the player's position via the reserved :
engine.PlayerEntitytypescript
import { engine, Transform } from '@dcl/sdk/ecs'
function trackPlayer() {
if (!Transform.has(engine.PlayerEntity)) return
const playerTransform = Transform.get(engine.PlayerEntity)
console.log('Player position:', playerTransform.position)
console.log('Player rotation:', playerTransform.rotation)
}
engine.addSystem(trackPlayer)Always check before reading player data — it may not be ready on the first frame.
Transform.has(engine.PlayerEntity)通过保留实体 访问玩家位置:
engine.PlayerEntitytypescript
import { engine, Transform } from '@dcl/sdk/ecs'
function trackPlayer() {
if (!Transform.has(engine.PlayerEntity)) return
const playerTransform = Transform.get(engine.PlayerEntity)
console.log('Player position:', playerTransform.position)
console.log('Player rotation:', playerTransform.rotation)
}
engine.addSystem(trackPlayer)读取玩家数据前始终检查 ——在第一帧时它可能尚未准备就绪。
Transform.has(engine.PlayerEntity)Distance-Based Logic
基于距离的逻辑
typescript
import { Vector3 } from '@dcl/sdk/math'
function proximityCheck() {
const playerPos = Transform.get(engine.PlayerEntity).position
const npcPos = Transform.get(npcEntity).position
const distance = Vector3.distance(playerPos, npcPos)
if (distance < 5) {
console.log('Player is near the NPC')
}
}
engine.addSystem(proximityCheck)typescript
import { Vector3 } from '@dcl/sdk/math'
function proximityCheck() {
const playerPos = Transform.get(engine.PlayerEntity).position
const npcPos = Transform.get(npcEntity).position
const distance = Vector3.distance(playerPos, npcPos)
if (distance < 5) {
console.log('Player is near the NPC')
}
}
engine.addSystem(proximityCheck)Player Profile Data
玩家个人资料数据
Get the player's name, wallet address, and guest status:
typescript
import { getPlayer } from '@dcl/sdk/src/players'
function main() {
const player = getPlayer()
if (player) {
console.log('Name:', player.name)
console.log('User ID:', player.userId)
console.log('Is guest:', player.isGuest)
}
}- — the player's Ethereum wallet address (or guest ID)
userId - —
isGuestif the player hasn't connected a wallettrue
Check before attempting any wallet-dependent feature (guests have no on-chain identity).
isGuest获取玩家的名称、钱包地址和访客状态:
typescript
import { getPlayer } from '@dcl/sdk/src/players'
function main() {
const player = getPlayer()
if (player) {
console.log('Name:', player.name)
console.log('User ID:', player.userId)
console.log('Is guest:', player.isGuest)
}
}- ——玩家的 Ethereum 钱包地址(或访客 ID)
userId - ——如果玩家尚未连接钱包,则为
isGuesttrue
尝试使用任何依赖钱包的功能之前,请先检查 (访客没有链上身份)。
isGuestProfile Data for Off-Scene Users (Catalyst)
场景外用户的个人资料数据(Catalyst)
getPlayer(userId)GET https://peer.decentraland.org/lambdas/profile/<wallet-address>- Always use — it is the canonical catalyst regardless of realm/world. Worlds servers do NOT expose
peer.decentraland.org, so do not blindly read/lambdas.realmInfo.baseUrl - Response shape: (NOT the
json.avatars[0].avatar.{ bodyShape, wearables, eyes:{color}, hair:{color}, skin:{color} }shape from older docs).json[0].metadata.avatars... - Unknown address returns — handle the empty array.
{ avatars: [], timestamp: 0 } - Colors come as floats in
{ r, g, b, a }. Build a[0,1]and pass it directly toColor3/AvatarShape.skinColor/hairColor— these fields take a raweyeColor, NOTColor3(wrapping causes TS2322).{ color: Color3 }
AvatarShape.create({ id: address })idbodyShapewearablesWhich API to use:
- Local or in-scene player → (sync, includes wearables/emotes).
getPlayer(userId) - Off-scene address → (async HTTP).
fetchAvatarFromCatalyst(address)
For the full helper (), end-to-end usage example, and gotchas, see .
fetchAvatarFromCatalyst{baseDir}/references/catalyst-profile-fetch.mdgetPlayer(userId)GET https://peer.decentraland.org/lambdas/profile/<wallet-address>- 始终使用 ——无论 realm 或 world 如何,它都是标准 Catalyst。Worlds 服务器不提供
peer.decentraland.org,因此不要直接读取/lambdas。realmInfo.baseUrl - 响应结构:(不是旧版文档中的
json.avatars[0].avatar.{ bodyShape, wearables, eyes:{color}, hair:{color}, skin:{color} }结构)。json[0].metadata.avatars... - 未知地址会返回 ——请处理空数组的情况。
{ avatars: [], timestamp: 0 } - 颜色以 范围内的浮点数
[0,1]返回。创建{ r, g, b, a }并直接传给Color3/AvatarShape.skinColor/hairColor——这些字段接收原始eyeColor,而不是Color3(包装后会引发 TS2322)。{ color: Color3 }
仅使用 调用 不会自动获取 wearables——除非显式提供 、 和颜色字段,否则 Avatar 会以未穿戴装备的状态渲染。
idAvatarShape.create({ id: address })bodyShapewearables应使用的 API:
- 本地玩家或场景内玩家 → (同步,包含 wearables 和 Emotes)。
getPlayer(userId) - 场景外地址 → (异步 HTTP)。
fetchAvatarFromCatalyst(address)
有关完整辅助函数 、端到端使用示例和注意事项,请参阅 。
fetchAvatarFromCatalyst{baseDir}/references/catalyst-profile-fetch.mdAvatar Attachments
Avatar 附件
Attach 3D objects to a player's avatar:
typescript
import {
engine,
Transform,
GltfContainer,
AvatarAttach,
AvatarAnchorPointType,
} from '@dcl/sdk/ecs'
const hat = engine.addEntity()
GltfContainer.create(hat, { src: 'models/hat.glb' })
Transform.create(hat, {})
// Attach to the local player's avatar
AvatarAttach.create(hat, {
anchorPointId: AvatarAnchorPointType.AAPT_NAME_TAG,
})AvatarAttachBefore picking, decide whether the item is cosmetic or aim-critical. Bone anchors inherit avatar skeleton animation (idle bob, walk cycle, gesture) — great for hats/backpacks/halos, bad for held weapons, aiming reticles, or anything where relative position must stay stable. See Held items vs cosmetic items below.AvatarAttach
将 3D 对象附加到玩家的 Avatar:
typescript
import {
engine,
Transform,
GltfContainer,
AvatarAttach,
AvatarAnchorPointType,
} from '@dcl/sdk/ecs'
const hat = engine.addEntity()
GltfContainer.create(hat, { src: 'models/hat.glb' })
Transform.create(hat, {})
// Attach to the local player's avatar
AvatarAttach.create(hat, {
anchorPointId: AvatarAnchorPointType.AAPT_NAME_TAG,
})AvatarAttach选择前,先判断物品是装饰性的还是对瞄准敏感的。 骨骼锚点会继承 Avatar 骨骼动画(待机起伏、行走循环、手势),非常适合帽子、背包、光环等物品,不适合手持武器、瞄准准星或任何需要保持相对位置稳定的内容。详见下文的“手持物品与装饰性物品”。AvatarAttach
Held items vs cosmetic items — AvatarAttach
vs parenting to engine.CameraEntity
/ engine.PlayerEntity
AvatarAttachengine.CameraEntityengine.PlayerEntity手持物品与装饰性物品——AvatarAttach
与父级设为 engine.CameraEntity
/ engine.PlayerEntity
AvatarAttachengine.CameraEntityengine.PlayerEntitySDK7 gives you three distinct mechanisms for "an entity that follows the player". They are not interchangeable — picking the wrong one is the single most common mistake when porting "held item" patterns from SDK6, and the most common subtle failure is parenting an aim-sensitive item (gun, reticle, flashlight) to and discovering the item does not track camera pitch when the player looks up or down.
engine.PlayerEntityDefault for any aim-sensitive held item: . Use only when you specifically want yaw-only / no-pitch behavior (a body-fixed item the player carries but never aims with).
Transform.parent = engine.CameraEntityengine.PlayerEntity| Goal | Use | Tracks | Reason |
|---|---|---|---|
| Aim-sensitive held item — gun, aiming reticle, flashlight, anything pointed by looking around. Recommended default for held gameplay items. | | Camera yaw + pitch | Follows the camera's full transform, so the item points where the player is looking — including up/down. This is the SDK7 analogue of SDK6's |
| Yaw-only / body-fixed item — a held shield the player doesn't aim, a static torch, a fixed-position carry item that should stay level regardless of where the player looks. | | Player root: feet position + body yaw only (no pitch) | Follows the player's root transform. Stable (no animation), but stays level when the player looks up/down — wrong default for guns/aim items, correct for items meant to ride the body orientation only. |
| Cosmetic item — hat, halo, backpack, name plate, glow effect, torch visible to other players riding the avatar. | | The actual animated bone | Item moves naturally with idle bob, walk cycle, and gestures — visually correct for cosmetics attached to the body. Not for aim — animation jitter makes aim-sensitive items unusable. |
Why is the right default for aim-sensitive items: only tracks the player's root (foot position + body yaw). Body yaw is NOT camera pitch — when the player tilts the camera up to aim at a flying target, the player root rotation does not change, so a gun parented to stays flat and the muzzle doesn't track the look direction. Parenting to inherits both yaw and pitch, so the gun aims where the camera looks. This matches the SDK6 behavior creators expect when porting.
engine.CameraEntityengine.PlayerEntityPlayerEntityengine.CameraEntityAttachable.FIRST_PERSON_CAMERAWhy bone anchors break aim: anchor points like , , are positions on the animated avatar skeleton. Every frame the engine pulls the bone's current world transform — which includes the procedural idle bob and any active animation clip. An entity parented there inherits all of that motion. For a weapon, this reads as jitter and makes aiming feel uncontrollable.
AAPT_RIGHT_HANDAAPT_SPINEAAPT_HEADSDK7 提供了三种不同的“跟随玩家的实体”机制。它们不可互换——将 SDK6 的“手持物品”模式迁移过来时,选错机制是最常见的错误;而更隐蔽的常见错误是把对瞄准敏感的物品设为 的子实体,却没有发现该物品无法跟随相机的俯仰角度。
engine.PlayerEntity任何对瞄准敏感的手持物品都应默认使用:。 只有当你明确需要仅跟随偏航角或完全不跟随俯仰角时,才使用 (即物品固定在身体上,但不会随视线瞄准)。
Transform.parent = engine.CameraEntityengine.PlayerEntity| 目标 | 使用方式 | 跟随内容 | 原因 |
|---|---|---|---|
| 对瞄准敏感的手持物品——枪械、瞄准准星、手电筒,以及任何需要随视线指向的物品。持有类游戏物品的推荐默认方案。 | | 相机偏航 + 俯仰 | 跟随相机的完整变换,因此物品会指向玩家所看的方向,包括向上和向下。它是 SDK6 中 |
| 仅跟随偏航角/固定于身体的物品——玩家不会瞄准的手持盾牌、静态火炬,或无论视线朝何处都应保持水平的固定位置携带物品。 | | 玩家根节点:脚部位置 + 身体偏航角(不含俯仰角) | 跟随玩家根节点的变换。没有动画,因此十分稳定,但玩家向上或向下看时物品仍会保持水平。对于枪械和瞄准物品,这是错误方案;对于仅跟随身体朝向的物品则是正确方案。 |
| 装饰性物品——帽子、光环、背包、名称牌、发光效果,以及其他玩家也能看到的 Avatar 随身火炬。 | 使用 | 实际动画骨骼 | 物品会自然跟随待机起伏、行走循环和手势动画——作为身体装饰品时视觉效果正确。不适用于瞄准——动画抖动会使对瞄准敏感的物品无法正常使用。 |
为什么 是对瞄准敏感物品的正确默认方案: 只跟踪玩家根节点(脚部位置 + 身体偏航角)。身体偏航角不是相机俯仰角——当玩家将相机向上倾斜以瞄准飞行目标时,玩家根节点的旋转不会改变,因此设为 子实体的枪械会保持平直,枪口也不会跟踪视线方向。将实体设为 的子实体会同时继承偏航角和俯仰角,因此枪械始终瞄准相机所看的方向。这与创作者迁移时预期的 SDK6 行为一致。
engine.CameraEntityengine.PlayerEntityPlayerEntityengine.CameraEntityAttachable.FIRST_PERSON_CAMERA为什么骨骼锚点会破坏瞄准: 、、 等锚点位于 Avatar 的动画骨骼上。引擎每帧都会获取骨骼的当前世界变换,其中包括程序化待机起伏和当前播放的动画片段。设为这些锚点子实体的物品会继承所有运动。对于武器而言,这会表现为抖动,让瞄准难以控制。
AAPT_RIGHT_HANDAAPT_SPINEAAPT_HEADExample — gun held in first person (aim follows camera pitch)
示例——第一人称手持枪械(瞄准跟随相机俯仰角)
typescript
import { engine, Transform, GltfContainer, CameraModeArea, CameraType } from '@dcl/sdk/ecs'
import { Vector3, Quaternion } from '@dcl/sdk/math'
const gun = engine.addEntity()
GltfContainer.create(gun, { src: 'assets/Models/blaster.glb' })
Transform.create(gun, {
parent: engine.CameraEntity, // gun follows camera (yaw + pitch) — aim tracks where you look
position: Vector3.create(0.25, -0.2, 0.5), // right, down, forward of camera
rotation: Quaternion.fromEulerDegrees(0, 0, 0),
scale: Vector3.One(),
})If the user does NOT want the item to track pitch (e.g. a held torch that should stay level, not point up when looking up), swap for . Do not pick for a gun — the result is a flat-pointing weapon that ignores look direction.
engine.CameraEntityengine.PlayerEntityPlayerEntityPair with or a forced camera mode when equipping a held gun, so the player is in first-person while aiming. See [[camera-control]].
CameraModeAreatypescript
import { engine, Transform, GltfContainer, CameraModeArea, CameraType } from '@dcl/sdk/ecs'
import { Vector3, Quaternion } from '@dcl/sdk/math'
const gun = engine.addEntity()
GltfContainer.create(gun, { src: 'assets/Models/blaster.glb' })
Transform.create(gun, {
parent: engine.CameraEntity, // gun follows camera (yaw + pitch) — aim tracks where you look
position: Vector3.create(0.25, -0.2, 0.5), // right, down, forward of camera
rotation: Quaternion.fromEulerDegrees(0, 0, 0),
scale: Vector3.One(),
})如果用户不希望物品跟随俯仰角(例如,手持火炬应在玩家向上看时保持水平,而不是一同向上),请将 替换为 。枪械不要使用 ——这样武器会始终平直指向,无法跟踪视线方向。
engine.CameraEntityengine.PlayerEntityPlayerEntity装备手持枪械时,请搭配使用 或强制相机模式,让玩家在瞄准时处于第一人称视角。参阅 [[camera-control]]。
CameraModeAreaAnti-pattern (what NOT to do for a held weapon)
反模式(手持武器不应采用的方式)
typescript
// WRONG — gun jitters with every idle/walk/gesture animation frame
AvatarAttach.create(gun, {
anchorPointId: AvatarAnchorPointType.AAPT_RIGHT_HAND,
})This looks like the right SDK7 way to "put a gun in the avatar's hand" because the API name reads that way — but the hand bone is animated, so the gun is unaimable in practice. Use parenting instead.
typescript
// SUBTLY WRONG for a gun — looks correct in hip-fire, fails the moment the player aims up
Transform.create(gun, { parent: engine.PlayerEntity, position: ... })engine.PlayerEntityengine.CameraEntityPlayerEntitytypescript
// WRONG — gun jitters with every idle/walk/gesture animation frame
AvatarAttach.create(gun, {
anchorPointId: AvatarAnchorPointType.AAPT_RIGHT_HAND,
})这看起来是 SDK7 中把“枪械放到 Avatar 手中”的正确方式——毕竟 API 名称看起来就是如此——但手部骨骼带有动画,因此枪械在实际使用中无法稳定瞄准。请改用父级关系。
typescript
// SUBTLY WRONG for a gun — looks correct in hip-fire, fails the moment the player aims up
Transform.create(gun, { parent: engine.PlayerEntity, position: ... })engine.PlayerEntityengine.CameraEntityPlayerEntitySDK6 porting note
SDK6 迁移说明
In SDK7 you have a choice that didn't exist in SDK6. SDK6's / mapped to coarse follow modes only. If you are porting a held item from an SDK6 scene that used , the SDK7 equivalent is parenting to (NOT to a hand anchor, and NOT — loses camera pitch). See [[migrate-sdk6-to-sdk7]].
Attachable.FIRST_PERSON_CAMERAAttachable.AVATARAttachable.FIRST_PERSON_CAMERAengine.CameraEntityAvatarAttachengine.PlayerEntityPlayerEntitySDK7 提供了 SDK6 中不存在的选择。SDK6 的 / 只映射到粗粒度的跟随模式。如果正在迁移使用 的 SDK6 场景中的手持物品,SDK7 中的等效方案是将实体设为 的子实体,而不是使用 连接到手部锚点,也不是使用 (因为 会丢失相机俯仰角)。参阅 [[migrate-sdk6-to-sdk7]]。
Attachable.FIRST_PERSON_CAMERAAttachable.AVATARAttachable.FIRST_PERSON_CAMERAengine.CameraEntityAvatarAttachengine.PlayerEntityPlayerEntityAnchor Points
锚点
typescript
AvatarAnchorPointType.AAPT_NAME_TAG // Above the head
AvatarAnchorPointType.AAPT_RIGHT_HAND // Right hand
AvatarAnchorPointType.AAPT_LEFT_HAND // Left hand
AvatarAnchorPointType.AAPT_POSITION // [DEPRECATED] Avatar root position — protocol recommends parenting to `engine.PlayerEntity` (body-fixed) or `engine.CameraEntity` (aim-sensitive) instead
AvatarAnchorPointType.AAPT_HEAD
AvatarAnchorPointType.AAPT_NECK
AvatarAnchorPointType.AAPT_SPINE
AvatarAnchorPointType.AAPT_SPINE1
AvatarAnchorPointType.AAPT_SPINE2
AvatarAnchorPointType.AAPT_HIP
AvatarAnchorPointType.AAPT_LEFT_SHOULDER
AvatarAnchorPointType.AAPT_LEFT_ARM
AvatarAnchorPointType.AAPT_LEFT_FOREARM
AvatarAnchorPointType.AAPT_LEFT_HAND_INDEX
AvatarAnchorPointType.AAPT_RIGHT_SHOULDER
AvatarAnchorPointType.AAPT_RIGHT_ARM
AvatarAnchorPointType.AAPT_RIGHT_FOREARM
AvatarAnchorPointType.AAPT_RIGHT_HAND_INDEX
AvatarAnchorPointType.AAPT_LEFT_UP_LEG
AvatarAnchorPointType.AAPT_LEFT_LEG
AvatarAnchorPointType.AAPT_LEFT_FOOT
AvatarAnchorPointType.AAPT_LEFT_TOE_BASE
AvatarAnchorPointType.AAPT_RIGHT_UP_LEG
AvatarAnchorPointType.AAPT_RIGHT_LEG
AvatarAnchorPointType.AAPT_RIGHT_FOOT
AvatarAnchorPointType.AAPT_RIGHT_TOE_BASE
AvatarAnchorPointType.AAPT_NAME_TAGAnchor points inherit bone animation. Bone-targeted anchors (, , , etc.) follow the animated skeleton — idle bob, walk cycle, and gesture animations all propagate to the attached entity. This is correct for cosmetic items (hats, halos, backpacks) and wrong for gameplay items where aim stability matters (guns, reticles). For aim-sensitive items parent to (yaw + pitch — the default for guns/reticles/flashlights); for yaw-only body-fixed items parent to . See "Held items vs cosmetic items" above.
AAPT_RIGHT_HANDAAPT_SPINEAAPT_HEADengine.CameraEntityengine.PlayerEntitytypescript
AvatarAnchorPointType.AAPT_NAME_TAG // Above the head
AvatarAnchorPointType.AAPT_RIGHT_HAND // Right hand
AvatarAnchorPointType.AAPT_LEFT_HAND // Left hand
AvatarAnchorPointType.AAPT_POSITION // [DEPRECATED] Avatar root position — protocol recommends parenting to `engine.PlayerEntity` (body-fixed) or `engine.CameraEntity` (aim-sensitive) instead
AvatarAnchorPointType.AAPT_HEAD
AvatarAnchorPointType.AAPT_NECK
AvatarAnchorPointType.AAPT_SPINE
AvatarAnchorPointType.AAPT_SPINE1
AvatarAnchorPointType.AAPT_SPINE2
AvatarAnchorPointType.AAPT_HIP
AvatarAnchorPointType.AAPT_LEFT_SHOULDER
AvatarAnchorPointType.AAPT_LEFT_ARM
AvatarAnchorPointType.AAPT_LEFT_FOREARM
AvatarAnchorPointType.AAPT_LEFT_HAND_INDEX
AvatarAnchorPointType.AAPT_RIGHT_SHOULDER
AvatarAnchorPointType.AAPT_RIGHT_ARM
AvatarAnchorPointType.AAPT_RIGHT_FOREARM
AvatarAnchorPointType.AAPT_RIGHT_HAND_INDEX
AvatarAnchorPointType.AAPT_LEFT_UP_LEG
AvatarAnchorPointType.AAPT_LEFT_LEG
AvatarAnchorPointType.AAPT_LEFT_FOOT
AvatarAnchorPointType.AAPT_LEFT_TOE_BASE
AvatarAnchorPointType.AAPT_RIGHT_UP_LEG
AvatarAnchorPointType.AAPT_RIGHT_LEG
AvatarAnchorPointType.AAPT_RIGHT_FOOT
AvatarAnchorPointType.AAPT_RIGHT_TOE_BASE
AvatarAnchorPointType.AAPT_NAME_TAG锚点会继承骨骼动画。 面向骨骼的锚点(、、 等)会跟随动画化的骨架——待机起伏、行走循环和手势动画都会传递给附加实体。对于装饰性物品(帽子、光环、背包)这是正确行为,但对于需要瞄准稳定性的游戏物品(枪械、准星)则是错误行为。对于对瞄准敏感的物品,请将实体设为 的子实体(偏航角 + 俯仰角,也是枪械和准星的默认方案);对于仅跟随偏航角、固定于身体的物品,请设为 的子实体。参阅上文“手持物品与装饰性物品”。
AAPT_RIGHT_HANDAAPT_SPINEAAPT_HEADengine.CameraEntityengine.PlayerEntityAttach to a Specific Player
附加到特定玩家
typescript
AvatarAttach.create(hat, {
avatarId: '0x123...abc', // Target player's wallet address
anchorPointId: AvatarAnchorPointType.AAPT_RIGHT_HAND,
})avatarIdengine.getEntitiesWith(PlayerIdentityData)player.addresstypescript
import { PlayerIdentityData } from '@dcl/sdk/ecs'
engine.addSystem(() => {
for (const [entity, player] of engine.getEntitiesWith(PlayerIdentityData)) {
// player.address is the wallet address to pass as avatarId
}
})To attach the local player's own held item, get the address from / (). Omitting attaches to the local player. For multiplayer visibility of a held/attached item, sync the anchor entity's component (see [[multiplayer-sync]]).
getPlayer()await getPlayer().userIdavatarIdAvatarAttachtypescript
AvatarAttach.create(hat, {
avatarId: '0x123...abc', // Target player's wallet address
anchorPointId: AvatarAnchorPointType.AAPT_RIGHT_HAND,
})avatarIdengine.getEntitiesWith(PlayerIdentityData)player.addresstypescript
import { PlayerIdentityData } from '@dcl/sdk/ecs'
engine.addSystem(() => {
for (const [entity, player] of engine.getEntitiesWith(PlayerIdentityData)) {
// player.address is the wallet address to pass as avatarId
}
})若要附加本地玩家自己的手持物品,请从 / 获取地址()。省略 会将物品附加到本地玩家。若要让其他玩家也能看到手持或附加物品,请同步锚点实体的 组件(参阅 [[multiplayer-sync]])。
getPlayer()await getPlayer().userIdavatarIdAvatarAttachTriggering Emotes
触发 Emote
Default Emotes
默认 Emote
typescript
import { triggerEmote } from '~system/RestrictedActions'
// Play a built-in emote
triggerEmote({ predefinedEmote: 'robot' })
triggerEmote({ predefinedEmote: 'wave' })
triggerEmote({ predefinedEmote: 'clap' })typescript
import { triggerEmote } from '~system/RestrictedActions'
// Play a built-in emote
triggerEmote({ predefinedEmote: 'robot' })
triggerEmote({ predefinedEmote: 'wave' })
triggerEmote({ predefinedEmote: 'clap' })Custom Scene Emotes
自定义场景 Emote
⚠️ CRITICAL FILE NAMING REQUIREMENT: The emotefile MUST end with.glb(case-insensitive). This is not optional and not just a convention — the runtime rejects files that don't match this suffix._emote.glbWhy this matters: Scenes with incorrectly named emote files often work fine inpreview but silently fail in production once deployed. Preview is more permissive; the deployed runtime is strict. Always rename the file on disk (e.g.npm run start→SnowballThrow.glb) before deploying.SnowballThrow_emote.glbValid:,wave_emote.glb,Snowball_Throw_emote.glbInvalid:dance_EMOTE.GLB,wave.glb,emote_wave.glbwave_emote_v2.glb
typescript
import { triggerSceneEmote } from '~system/RestrictedActions'
// File MUST end with _emote.glb — rename it on disk if it doesn't
triggerSceneEmote({
src: 'animations/Snowball_Throw_emote.glb',
loop: false,
})Notes:
- Emotes play only while the player is standing still — walking or jumping interrupts them
- If you don't want a player to interrupt an emote, use the component to freeze the player for the duration of the emote
InputModifier - Both and
triggerEmoterequire the scene to declare thetriggerSceneEmotepermission inALLOW_TO_TRIGGER_AVATAR_EMOTEscene.json.requiredPermissions - Both accept an optional (upper-body-only animation) — see "Emote masks" below.
mask
⚠️ 关键的文件命名要求: Emote 的文件必须以.glb结尾(不区分大小写)。这不是可选项,也不只是约定——运行时会拒绝不符合此后缀的文件。_emote.glb为什么这很重要: 文件命名错误的场景在预览中往往运行正常,但部署到生产环境后会静默失败。预览环境更宽松,而已部署的运行时执行严格校验。部署前,务必在磁盘上重命名文件(例如npm run start→SnowballThrow.glb)。SnowballThrow_emote.glb
有效:、、
无效:、、
wave_emote.glbSnowball_Throw_emote.glbdance_EMOTE.GLB无效:
wave.glbemote_wave.glbwave_emote_v2.glbtypescript
import { triggerSceneEmote } from '~system/RestrictedActions'
// File MUST end with _emote.glb — rename it on disk if it doesn't
triggerSceneEmote({
src: 'animations/Snowball_Throw_emote.glb',
loop: false,
})注意:
- Emote 仅在玩家站立不动时播放——行走或跳跃会中断 Emote。
- 如果不希望玩家中断 Emote,请使用 组件在 Emote 期间冻结玩家。
InputModifier - 和
triggerEmote都要求场景在triggerSceneEmote的scene.json中声明requiredPermissions权限。ALLOW_TO_TRIGGER_AVATAR_EMOTE - 两者都接受可选的 (仅上半身动画),详见下文的“Emote mask”。
mask
Stopping an emote
停止 Emote
stopEmote({})~system/RestrictedActionstriggerSceneEmote({ src, loop: true })typescript
import { stopEmote } from '~system/RestrictedActions'
stopEmote({})~system/RestrictedActionsstopEmote({})triggerSceneEmote({ src, loop: true })typescript
import { stopEmote } from '~system/RestrictedActions'
stopEmote({})Emote masks (upper-body only)
Emote mask(仅上半身)
triggerEmotetriggerSceneEmotemaskAvatarMask@dcl/sdk/ecstypescript
import { AvatarMask } from '@dcl/sdk/ecs'
import { triggerSceneEmote } from '~system/RestrictedActions'
triggerSceneEmote({ src: 'animations/Carry_emote.glb', loop: true, mask: AvatarMask.AM_UPPER_BODY })- Only value: (= 0). Omitting
AvatarMask.AM_UPPER_BODYplays the full-body animation (the default) — there is nomaskvalue in the enum.AM_FULL_BODY - applies to
maskandtriggerEmoteonly.triggerSceneEmotetakes no arguments (stopEmote({})is empty).StopEmoteRequest - Loop + mask interaction: with
loop: falseplays the upper-body animation exactly once, then returns the upper body to locomotion.mask: AM_UPPER_BODYwith the mask repeats untilloop: trueis called. The loop flag is respected regardless of the mask. Verified against sdk7-test-scenesstopEmote({})and88,-13-avatar-masks(commit80,-1-scene-emotes).1c0f394 - Verified against protocol /
restricted_actions.proto(pinned incommon/avatar_mask.protovia protocol@dcl/sdk) and sdk7-test-scenes0010e70(2026-07-16). Earlier speculative names88,-13-avatar-masks/AvatarEmoteMask/AEM_UPPER_BODYwere never released — do not use them.AEM_FULL_BODY
triggerEmotetriggerSceneEmotemaskAvatarMask@dcl/sdk/ecstypescript
import { AvatarMask } from '@dcl/sdk/ecs'
import { triggerSceneEmote } from '~system/RestrictedActions'
triggerSceneEmote({ src: 'animations/Carry_emote.glb', loop: true, mask: AvatarMask.AM_UPPER_BODY })- 唯一有效值:(= 0)。省略
AvatarMask.AM_UPPER_BODY会播放全身动画(默认行为);枚举中没有mask值。AM_FULL_BODY - 仅适用于
mask和triggerEmote。triggerSceneEmote不接受参数(stopEmote({})为空)。StopEmoteRequest - 循环 + mask 的交互: 与
loop: false搭配时,上半身动画会精确播放一次,然后上半身恢复移动状态。mask: AM_UPPER_BODY与 mask 搭配时,会持续重复,直到调用loop: true。无论是否设置 mask,loop 标志都会被正确遵守。已对照 sdk7-test-scenes 的stopEmote({})和88,-13-avatar-masks(提交80,-1-scene-emotes)进行验证。1c0f394 - 已对照协议 /
restricted_actions.proto(通过协议common/avatar_mask.proto固定在0010e70中)以及 sdk7-test-scenes 的@dcl/sdk(2026-07-16)进行验证。早期推测的名称88,-13-avatar-masks/AvatarEmoteMask/AEM_UPPER_BODY从未发布——请勿使用。AEM_FULL_BODY
NPC Avatars
NPC Avatar
For creating NPCs (characters, shopkeepers, guards, etc.), see the npcs skill. It covers both the NPC Toolkit library (GLB-based, with dialogue and movement) and -based avatar NPCs.
AvatarShape如需创建 NPC(角色、店主、守卫等),请参阅 npcs Skill。该 Skill 涵盖 NPC Toolkit 库(基于 GLB,支持对话和移动)以及基于 的 Avatar NPC。
AvatarShapeAvatar Modifier Areas
Avatar Modifier Area
Modify how avatars appear or behave in a region.
typescript
import {
engine,
Transform,
AvatarModifierArea,
AvatarModifierType,
} from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const modifierArea = engine.addEntity()
Transform.create(modifierArea, {
position: Vector3.create(8, 1.5, 8),
scale: Vector3.create(4, 3, 4),
})
AvatarModifierArea.create(modifierArea, {
area: Vector3.create(4, 3, 4),
modifiers: [AvatarModifierType.AMT_HIDE_AVATARS],
excludeIds: ['0x123...abc'], // Optional: exclude specific players
})修改某个区域内 Avatar 的外观或行为。
typescript
import {
engine,
Transform,
AvatarModifierArea,
AvatarModifierType,
} from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const modifierArea = engine.addEntity()
Transform.create(modifierArea, {
position: Vector3.create(8, 1.5, 8),
scale: Vector3.create(4, 3, 4),
})
AvatarModifierArea.create(modifierArea, {
area: Vector3.create(4, 3, 4),
modifiers: [AvatarModifierType.AMT_HIDE_AVATARS],
excludeIds: ['0x123...abc'], // Optional: exclude specific players
})Available Modifiers
可用的 Modifier
typescript
AvatarModifierType.AMT_HIDE_AVATARS // Hide all avatars in the area
AvatarModifierType.AMT_DISABLE_PASSPORTS // Disable clicking on avatars to see profiles
AvatarModifierType.AMT_HIDE_NAMETAGS // Hide the name tag above avatars in the areamodifiers[AMT_HIDE_NAMETAGS, AMT_DISABLE_PASSPORTS]AvatarModifierAreaarea: Vector3Transform.scaleexcludeIdsAvatarModifierArea.getMutable(entity).excludeIds = [...]AMT_HIDE_AVATARSAMT_HIDE_NAMETAGSAMT_HIDE_NAMETAGSAMT_HIDE_NAMETAGSAMT_DISABLE_PASSPORTSNametag hiding is head/torso based: the nametag is hidden only while the player's head or torso is inside the area. If the area is too short, a player who double-jumps above it will have their nametag briefly reappear. Make the area tall enough to cover the expected range of movement.
typescript
AvatarModifierType.AMT_HIDE_AVATARS // Hide all avatars in the area
AvatarModifierType.AMT_DISABLE_PASSPORTS // Disable clicking on avatars to see profiles
AvatarModifierType.AMT_HIDE_NAMETAGS // Hide the name tag above avatars in the areamodifiers[AMT_HIDE_NAMETAGS, AMT_DISABLE_PASSPORTS]AvatarModifierAreaarea: Vector3Transform.scaleexcludeIdsAvatarModifierArea.getMutable(entity).excludeIds = [...]AMT_HIDE_AVATARSAMT_HIDE_NAMETAGSAMT_HIDE_NAMETAGSAMT_HIDE_NAMETAGSAMT_DISABLE_PASSPORTS名称标签的隐藏基于头部/躯干位置: 只有当玩家的头部或躯干位于区域内时,名称标签才会隐藏。如果区域高度太低,进行双重跳跃的玩家其名称标签可能会短暂重新出现。请将区域设置得足够高,以覆盖预期的移动范围。
Avatar Locomotion Settings
Avatar 移动设置
Adjust the player's movement speed and jump height:
typescript
import { engine, AvatarLocomotionSettings } from '@dcl/sdk/ecs'
// Modify run speed and jump height (set only the fields you want to change)
AvatarLocomotionSettings.createOrReplace(engine.PlayerEntity, {
runSpeed: 14, // default is 10
jumpHeight: 3, // default is 1
})Fields (all , optional) with client defaults — verified against unity-explorer : (1.5), (8, the default movement speed), (10), (1), (1.5), (2), (6), (1), (0.75s). See .
floatorigin/mainCharacterControllerSettings.assetwalkSpeedjogSpeedrunSpeedjumpHeightrunJumpHeightdoubleJumpHeightglidingSpeedglidingFallingSpeedhardLandingCooldownreferences/avatar-apis.mdglidingFallingSpeedplayer-physics调整玩家的移动速度和跳跃高度:
typescript
import { engine, AvatarLocomotionSettings } from '@dcl/sdk/ecs'
// Modify run speed and jump height (set only the fields you want to change)
AvatarLocomotionSettings.createOrReplace(engine.PlayerEntity, {
runSpeed: 14, // default is 10
jumpHeight: 3, // default is 1
})字段均为可选的 类型,并具有以下客户端默认值——已对照 unity-explorer 中的 验证:(1.5)、(8,默认移动速度)、(10)、(1)、(1.5)、(2)、(6)、(1)、(0.75 秒)。参阅 。
floatorigin/mainCharacterControllerSettings.assetwalkSpeedjogSpeedrunSpeedjumpHeightrunJumpHeightdoubleJumpHeightglidingSpeedglidingFallingSpeedhardLandingCooldownreferences/avatar-apis.mdglidingFallingSpeedplayer-physicsRestrict Locomotion (InputModifier)
限制移动(InputModifier)
Use on to freeze or selectively restrict the player's movement — useful for cutscenes, locked interactions, or controlled game mechanics.
InputModifierengine.PlayerEntitytypescript
import { InputModifier, engine } from '@dcl/sdk/ecs'
// Freeze all movement
InputModifier.create(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({ disableAll: true }),
})
// Remove restrictions
InputModifier.deleteFrom(engine.PlayerEntity)Behavior when frozen: gravity and external forces still apply, camera rotation stays available, global input events are still detectable, restrictions lift automatically when the player leaves scene bounds.
Standard-mode flags (all boolean, on ): , , , , , . Protocol also defines and . Note is separate from / — jog is the default movement speed, so disabling only walk+run still lets the player jog.
InputModifier.Mode.Standard({...})disableAlldisableWalkdisableJogdisableRundisableJumpdisableEmotedisableDoubleJumpdisableGlidingdisableJogdisableWalkdisableRunThe can be built two equivalent ways — the helper, or the raw discriminated union .
modeInputModifier.Mode.Standard({...}){ $case: 'standard', standard: {...} }Tip: Combine with — freeze the player during an animation, then remove InputModifier when it ends.
triggerSceneEmoteFor the cutscene pattern, see the advanced-input skill.
在 上使用 可冻结或选择性地限制玩家移动——适用于过场动画、锁定的交互或受控游戏机制。
engine.PlayerEntityInputModifiertypescript
import { InputModifier, engine } from '@dcl/sdk/ecs'
// Freeze all movement
InputModifier.create(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({ disableAll: true }),
})
// Remove restrictions
InputModifier.deleteFrom(engine.PlayerEntity)冻结时的行为: 重力和外部力仍会生效,相机旋转仍可使用,全局输入事件仍可检测;当玩家离开场景边界时,限制会自动解除。
Standard 模式标志(均为布尔值,位于 上):、、、、、。协议还定义了 和 。请注意, 与 / 相互独立——jog 是默认移动速度,因此仅禁用 walk 和 run 仍允许玩家以 jog 速度移动。
InputModifier.Mode.Standard({...})disableAlldisableWalkdisableJogdisableRundisableJumpdisableEmotedisableDoubleJumpdisableGlidingdisableJogdisableWalkdisableRunmodeInputModifier.Mode.Standard({...}){ $case: 'standard', standard: {...} }提示: 将其与 结合使用——在动画期间冻结玩家,动画结束后再移除 InputModifier。
triggerSceneEmote有关过场动画模式,请参阅 advanced-input Skill。
Teleporting the Player
传送玩家
movePlayerTo~system/RestrictedActionsTransform.getMutable(engine.PlayerEntity).positionplayer-physicsmovePlayerTomovePlayerTo- — where to move the player (scene-relative
newRelativePosition)Vector3 - (optional) — a point in space for the camera to face after moving
cameraTarget - (optional) — a point in space for the avatar to face after moving
avatarTarget - (optional) — transition time in seconds; if provided, movement can be awaited
duration
Constraints:
- The player must already be inside the scene's bounds for this to work
- The target position must also be within the scene's bounds
- During the transition the avatar passes through colliders (verified: a obstacle placed in the path is passed through)
CL_PHYSICS - Requires the permission in
ALLOW_TO_MOVE_PLAYER_INSIDE_SCENEscene.jsonrequiredPermissions - All fields except are optional —
newRelativePositionandcameraTargetmay each be omitted or used independentlyavatarTarget - The target Y may be elevated (e.g. ) to place the player on a raised platform, not just ground level
y: 12
~system/RestrictedActionsmovePlayerToTransform.getMutable(engine.PlayerEntity).positionplayer-physicsmovePlayerTomovePlayerTo- ——玩家要移动到的位置(相对于场景的
newRelativePosition)Vector3 - (可选)——移动后相机要朝向的空间点
cameraTarget - (可选)——移动后 Avatar 要朝向的空间点
avatarTarget - (可选)——过渡时间,单位为秒;如果提供该值,可以等待移动完成
duration
限制:
- 玩家必须已经位于场景边界内,该操作才能生效
- 目标位置也必须位于场景边界内
- 过渡期间,Avatar 会穿过碰撞体(已验证:路径中的 障碍物会被穿过)
CL_PHYSICS - 需要在 的
scene.json中声明requiredPermissions权限ALLOW_TO_MOVE_PLAYER_INSIDE_SCENE - 除 外,所有字段均为可选——
newRelativePosition和cameraTarget可以分别省略或独立使用avatarTarget - 目标 Y 值可以更高(例如 ),用于将玩家放置在抬高的平台上,而不仅仅限于地面高度
y: 12
Instant teleport
立即传送
typescript
import { movePlayerTo } from '~system/RestrictedActions'
void movePlayerTo({
newRelativePosition: Vector3.create(8, 0, 8),
cameraTarget: Vector3.create(8, 1, 12),
avatarTarget: Vector3.create(8, 1, 12),
})typescript
import { movePlayerTo } from '~system/RestrictedActions'
void movePlayerTo({
newRelativePosition: Vector3.create(8, 0, 8),
cameraTarget: Vector3.create(8, 1, 12),
avatarTarget: Vector3.create(8, 1, 12),
})Smooth transition with duration
带时长的平滑过渡
When is set, is awaitable. The resolved value has a boolean — if the player interrupted the movement with input.
durationmovePlayerTosuccessfalsetypescript
import { movePlayerTo } from '~system/RestrictedActions'
async function teleport() {
const result = await movePlayerTo({
newRelativePosition: Vector3.create(1, 0, 1),
cameraTarget: Vector3.create(8, 1, 8),
duration: 2,
})
if (!result.success) {
console.log('Movement was interrupted by the player')
}
}设置 后,可以等待 完成。解析后的结果包含一个 布尔值——如果玩家通过输入中断了移动,则为 。
durationmovePlayerTosuccessfalsetypescript
import { movePlayerTo } from '~system/RestrictedActions'
async function teleport() {
const result = await movePlayerTo({
newRelativePosition: Vector3.create(1, 0, 1),
cameraTarget: Vector3.create(8, 1, 8),
duration: 2,
})
if (!result.success) {
console.log('Movement was interrupted by the player')
}
}Prevent the player from interrupting a transition
防止玩家中断过渡
Combine with to lock movement for the duration:
InputModifiermovePlayerTotypescript
import { movePlayerTo } from '~system/RestrictedActions'
import { InputModifier, engine } from '@dcl/sdk/ecs'
async function lockedTeleport() {
InputModifier.create(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({ disableAll: true }),
})
await movePlayerTo({
newRelativePosition: Vector3.create(1, 0, 1),
cameraTarget: Vector3.create(8, 1, 8),
duration: 2,
})
InputModifier.deleteFrom(engine.PlayerEntity)
}将 与 结合使用,在过渡期间锁定移动:
InputModifiermovePlayerTotypescript
import { movePlayerTo } from '~system/RestrictedActions'
import { InputModifier, engine } from '@dcl/sdk/ecs'
async function lockedTeleport() {
InputModifier.create(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({ disableAll: true }),
})
await movePlayerTo({
newRelativePosition: Vector3.create(1, 0, 1),
cameraTarget: Vector3.create(8, 1, 8),
duration: 2,
})
InputModifier.deleteFrom(engine.PlayerEntity)
}Avatar Change Listeners
Avatar 变化监听器
React to avatar changes in real-time:
typescript
import {
AvatarEmoteCommand,
AvatarBase,
AvatarEquippedData,
} from '@dcl/sdk/ecs'
// Detect when the Explorer reports an emote playing on a player.
// AvatarEmoteCommand is written BY THE EXPLORER to report emote playback
// TO the scene -- it is NOT a signal from scene to renderer. It is appended
// to every player entity (local and remote alike).
AvatarEmoteCommand.onChange(engine.PlayerEntity, (cmd) => {
if (cmd) console.log('Emote played:', cmd.emoteUrn)
})
// Detect avatar appearance changes (wearables, skin color, etc.)
AvatarBase.onChange(engine.PlayerEntity, (base) => {
if (base) console.log('Avatar name:', base.name)
})
// Detect equipment changes
AvatarEquippedData.onChange(engine.PlayerEntity, (equipped) => {
if (equipped) console.log('Wearables changed:', equipped.wearableUrns)
})实时响应 Avatar 变化:
typescript
import {
AvatarEmoteCommand,
AvatarBase,
AvatarEquippedData,
} from '@dcl/sdk/ecs'
// Detect when the Explorer reports an emote playing on a player.
// AvatarEmoteCommand is written BY THE EXPLORER to report emote playback
// TO the scene -- it is NOT a signal from scene to renderer. It is appended
// to every player entity (local and remote alike).
AvatarEmoteCommand.onChange(engine.PlayerEntity, (cmd) => {
if (cmd) console.log('Emote played:', cmd.emoteUrn)
})
// Detect avatar appearance changes (wearables, skin color, etc.)
AvatarBase.onChange(engine.PlayerEntity, (base) => {
if (base) console.log('Avatar name:', base.name)
})
// Detect equipment changes
AvatarEquippedData.onChange(engine.PlayerEntity, (equipped) => {
if (equipped) console.log('Wearables changed:', equipped.wearableUrns)
})Additional Anchor Points
其他锚点
Beyond the commonly used anchor points, the full list includes:
- — avatar feet position
AvatarAnchorPointType.AAPT_POSITION - — above the name tag
AvatarAnchorPointType.AAPT_NAME_TAG - /
AvatarAnchorPointType.AAPT_LEFT_HANDAAPT_RIGHT_HAND - — head bone
AvatarAnchorPointType.AAPT_HEAD - — neck bone
AvatarAnchorPointType.AAPT_NECK
Need to check the player's wallet before showing avatar items? See the nft-blockchain skill for wallet checks withandgetPlayer().isGuest
除常用锚点外,完整列表还包括:
- ——Avatar 脚部位置
AvatarAnchorPointType.AAPT_POSITION - ——名称标签上方
AvatarAnchorPointType.AAPT_NAME_TAG - /
AvatarAnchorPointType.AAPT_LEFT_HANDAAPT_RIGHT_HAND - ——头部骨骼
AvatarAnchorPointType.AAPT_HEAD - ——颈部骨骼
AvatarAnchorPointType.AAPT_NECK
需要在显示 Avatar 物品前检查玩家钱包吗? 有关使用和getPlayer()进行钱包检查,请参阅 nft-blockchain Skill。isGuest
Example scenes
示例场景
Engine-team test scenes (exercised against the real engine):
- 100,102-avatar-attach-test — on multiple anchor points; enumerates every player via
AvatarAttachand attaches toPlayerIdentityData; a follower entity reconstructs the attached world position fromplayer.address+ attached Transform.PlayerEntity - 80,-1-scene-emotes — ,
triggerEmote(with a deliberately mis-named non-triggerSceneEmotefile shown NOT playing),_emote.glb,stopEmote, plusmask: AvatarMask.AM_UPPER_BODY+ mask (plays once, returns to locomotion) andloop: false+ mask (repeats until stopped).loop: true - 11,0-move-player-to-duration — with
movePlayerTo, readingdurationviaresult.success,.then()locking input during the slide, and aInputModifierobstacle the avatar passes through mid-transition.CL_PHYSICS - 9,99-modifier-areas — (
AvatarModifierArea) with runtime-mutatedAMT_HIDE_AVATARS, alongsideexcludeIds.CameraModeArea - 10,99-avatar-modifier-hide-nametags — with
AvatarModifierArea: hides nametags while keeping avatars visible.AMT_HIDE_NAMETAGS - 0,1-input-modifier — toggling every Standard flag (
InputModifier), both via the helper and the rawdisableAll/Walk/Jog/Run/Jump/Emoteform.$case - 80,-4-restricted-actions — (incl. elevated
movePlayerTo,y-only turns),avatarTarget,triggerEmote,triggerSceneEmote,teleportTo.openExternalUrl - 88,-13-avatar-masks — emote masks: looping scene emote +
AvatarMask.AM_UPPER_BODYanchor to hold a synced crate,AvatarAttachto release. Also includesstopEmote+ mask pair (plays once then returns upper body to locomotion) andloop: false+ mask pair (repeats until stopped) for verifying the masked-emote loop flag is respected.loop: true
For component field details, see .
For anchor points, emote names, and event callbacks, see .
{baseDir}/../sdk-scenes/references/components-reference.md{baseDir}/references/avatar-apis.md引擎团队测试场景(已在真实引擎中验证):
- 100,102-avatar-attach-test——在多个锚点上使用 ;通过
AvatarAttach枚举每位玩家,并附加到PlayerIdentityData;跟随实体根据player.address和附加的 Transform 重建所附加对象的世界位置。PlayerEntity - 80,-1-scene-emotes——、
triggerEmote(包含一个故意命名错误且不以triggerSceneEmote结尾的文件,用于演示其无法播放)、_emote.glb、stopEmote,以及mask: AvatarMask.AM_UPPER_BODY+ mask(播放一次后恢复移动)和loop: false+ mask(重复播放直到停止)的组合。loop: true - 11,0-move-player-to-duration——带 的
duration,通过movePlayerTo读取.then(),使用result.success在滑动期间锁定输入,并包含一个 Avatar 在过渡期间会穿过的InputModifier障碍物。CL_PHYSICS - 9,99-modifier-areas——(
AvatarModifierArea)及运行时修改的AMT_HIDE_AVATARS,并与excludeIds一同使用。CameraModeArea - 10,99-avatar-modifier-hide-nametags——(
AvatarModifierArea):隐藏名称标签,同时保持 Avatar 可见。AMT_HIDE_NAMETAGS - 0,1-input-modifier——使用辅助函数和原始 形式切换所有 Standard 标志(
$case)。disableAll/Walk/Jog/Run/Jump/Emote - 80,-4-restricted-actions——(包括升高的
movePlayerTo和仅使用y转向)、avatarTarget、triggerEmote、triggerSceneEmote、teleportTo。openExternalUrl - 88,-13-avatar-masks——Emote mask:循环播放带 的场景 Emote,并通过
AvatarMask.AM_UPPER_BODY锚点持有同步木箱,使用AvatarAttach释放。还包含stopEmote+ mask 组合(播放一次后恢复上半身移动)和loop: false+ mask 组合(重复播放直到停止),用于验证带 mask 时循环标志是否得到正确遵守。loop: true
有关组件字段详情,请参阅 。
有关锚点、Emote 名称和事件回调,请参阅 。
{baseDir}/../sdk-scenes/references/components-reference.md{baseDir}/references/avatar-apis.md