返回 Skill 列表
extension
分类: 开发与工程无需 API Key

player-avatar

在Decentraland场景中处理玩家头像。读取玩家位置和资料数据,使用AvatarBase自定义外观,通过triggerEmote/triggerSceneEmote触发表情,通过AvatarEquippedData读取已装备的可穿戴设备,使用AvatarAttach将对象附加到玩家身上,使用AvatarShape创建NPC头像,并在区域内修改头像。当用户需要玩家数据、表情、可穿戴设备、头像附件或NPC时使用。

person作者: jakexiaohubgithub

Player and Avatar System in Decentraland

Authoring split

AvatarShape (the component used for NPCs and pre-placed avatars) is supported in main-entities.ts — declare the NPC fully there with id, name, wearables, etc.:

// main-entities.ts
shopkeeper: {
  components: {
    Transform: { position: { x: 8, y: 0, z: 8 } },
    AvatarShape: {
      id: 'shopkeeper-1',
      name: 'Shopkeeper',
      bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale',
      wearables: [],
      emotes: []
    }
  }
}

AvatarAttach, AvatarModifierArea, AvatarBase, AvatarEquippedData are not in the supported list — they're runtime by design (they bind to the live player, or apply to entities you create on-the-fly). Add them in src/index.ts and attach to entities looked up via getEntityOrNullByName (for static placement) or runtime-created entities (for player-bound effects).

The reserved engine.PlayerEntity is engine-managed and has no representation in main-entities.ts.

Player Position and Movement

Access the player's position via the reserved engine.PlayerEntity:

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)

Distance-Based Logic

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:

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)
  }
}
  • userId — the player's Ethereum wallet address (or guest ID)
  • isGuesttrue if the player hasn't connected a wallet

Fetch Full Avatar Profile from the Catalyst

getPlayer() returns the local view of the player. For full avatar data (wearables list, body shape, skin/hair/eye colors), fetch from the Catalyst:

import { executeTask, signedFetch } from '@dcl/sdk/network'
import { getPlayer } from '@dcl/sdk/players'

executeTask(async () => {
  const player = getPlayer()
  if (!player || player.isGuest) return

  const res = await fetch(`https://peer.decentraland.org/lambdas/profiles/${player.userId}`)
  const body = await res.json()

  // Response is an array of profiles; the first entry holds the active avatar.
  const profile = body?.avatars?.[0]
  if (!profile) return

  console.log('name:', profile.name)
  console.log('wearables:', profile.avatar.wearables)
  console.log('skin color:', profile.avatar.skin.color)  // { r, g, b } — already unwrapped, NOT { color: { r,g,b } }
})

Gotchas:

  • The response is { avatars: [...] }, not a flat profile object. Always read body.avatars[0].
  • Color fields (skin.color, hair.color, eyes.color) are already { r, g, b } objects — don't unwrap one more level.
  • The fetch is unauthenticated; for endpoints that need the player's signed identity, use signedFetch from @dcl/sdk/network instead of plain fetch.

Avatar Attachments

Attach 3D objects to a player's avatar:

import { engine, Transform, GltfContainer, AvatarAttach, AvatarAnchorPointType } from '@dcl/sdk/ecs'

const hat = engine.addEntity()
GltfContainer.create(hat, { src: 'models/hat.glb' })

// Attach to the local player's avatar
AvatarAttach.create(hat, {
  anchorPointId: AvatarAnchorPointType.AAPT_NAME_TAG
})

Anchor Points

Full enum of bones / positions an attachment can track. Inside main-entities.ts use the integer value (left). In src/index.ts use AvatarAnchorPointType.<NAME>.

| value | enum | location | |---|---|---| | 0 | AAPT_POSITION | avatar feet (deprecated — prefer parent: engine.PlayerEntity) | | 1 | AAPT_NAME_TAG | above the name tag | | 2 | AAPT_LEFT_HAND | left hand | | 3 | AAPT_RIGHT_HAND | right hand | | 4 | AAPT_HEAD | head bone | | 5 | AAPT_NECK | neck | | 6 | AAPT_SPINE | spine root | | 7 | AAPT_SPINE1 | spine mid | | 8 | AAPT_SPINE2 | spine top | | 9 | AAPT_HIP | hip | | 10 | AAPT_LEFT_SHOULDER | left shoulder | | 11 | AAPT_LEFT_ARM | left upper arm | | 12 | AAPT_LEFT_FOREARM | left forearm | | 13 | AAPT_LEFT_HAND_INDEX | left index finger | | 14 | AAPT_RIGHT_SHOULDER | right shoulder | | 15 | AAPT_RIGHT_ARM | right upper arm | | 16 | AAPT_RIGHT_FOREARM | right forearm | | 17 | AAPT_RIGHT_HAND_INDEX | right index finger | | 18 | AAPT_LEFT_UP_LEG | left thigh | | 19 | AAPT_LEFT_LEG | left calf | | 20 | AAPT_LEFT_FOOT | left foot | | 21 | AAPT_LEFT_TOE_BASE | left toes | | 22 | AAPT_RIGHT_UP_LEG | right thigh | | 23 | AAPT_RIGHT_LEG | right calf | | 24 | AAPT_RIGHT_FOOT | right foot | | 25 | AAPT_RIGHT_TOE_BASE | right toes |

Attach to a Specific Player

AvatarAttach.create(hat, {
  avatarId: '0x123...abc',  // Target player's wallet address
  anchorPointId: AvatarAnchorPointType.AAPT_RIGHT_HAND
})

Triggering Emotes

Default Emotes

import { triggerEmote } from '~system/RestrictedActions'

// Play a built-in emote
triggerEmote({ predefinedEmote: 'robot' })
triggerEmote({ predefinedEmote: 'wave' })
triggerEmote({ predefinedEmote: 'clap' })

Custom Scene Emotes

import { triggerSceneEmote } from '~system/RestrictedActions'

// Play a custom emote animation (file must end with _emote.glb)
triggerSceneEmote({
  src: 'animations/Snowball_Throw_emote.glb',
  loop: false
})

Notes:

  • Emotes play only while the player is standing still — walking or jumping interrupts them
  • Custom emote files must have the _emote.glb suffix

NPC Avatars

Create avatar-shaped NPCs using AvatarShape:

import { engine, Transform, AvatarShape } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

const npc = engine.addEntity()
Transform.create(npc, { position: Vector3.create(8, 0, 8) })

AvatarShape.create(npc, {
  id: 'npc-1',
  name: 'Guard',
  bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale',  // or BaseFemale
  wearables: [
    'urn:decentraland:off-chain:base-avatars:eyebrows_00',
    'urn:decentraland:off-chain:base-avatars:mouth_00',
    'urn:decentraland:off-chain:base-avatars:eyes_00',
    'urn:decentraland:off-chain:base-avatars:blue_tshirt',
    'urn:decentraland:off-chain:base-avatars:brown_pants',
    'urn:decentraland:off-chain:base-avatars:classic_shoes',
    'urn:decentraland:off-chain:base-avatars:short_hair'
  ],
  hairColor: { r: 0.92, g: 0.76, b: 0.62 },  // RGB values 0-1
  skinColor: { r: 0.94, g: 0.85, b: 0.6 },   // RGB values 0-1
  emotes: []
})

Mannequin (Show Only Wearables)

Display just the wearables without a full avatar body:

AvatarShape.create(mannequin, {
  id: 'mannequin-1',
  name: 'Display',
  wearables: [
    'urn:decentraland:matic:collections-v2:0x90e5cb2d673699be8f28d339c818a0b60144c494:0'
  ],
  show_only_wearables: true
})

NPC avatars are static — they display the avatar model but don't move or animate on their own. Combine with Animator or Tween for movement.

Avatar Modifier Areas

Modify how avatars appear or behave in a region:

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: { box: Vector3.create(4, 3, 4) },
  modifiers: [AvatarModifierType.AMT_HIDE_AVATARS],
  excludeIds: ['0x123...abc']  // Optional: exclude specific players
})

Available Modifiers

AvatarModifierType.AMT_HIDE_AVATARS       // Hide all avatars in the area
AvatarModifierType.AMT_DISABLE_PASSPORTS  // Disable clicking on avatars to see profiles
AvatarModifierType.AMT_DISABLE_JUMPING    // Prevent jumping in the area

Movement Constraints

// Prevent jumping in a specific area
const constraintArea = engine.addEntity()
Transform.create(constraintArea, {
  position: Vector3.create(8, 5, 8),
  scale: Vector3.create(6, 10, 6)
})

AvatarModifierArea.create(constraintArea, {
  area: { box: Vector3.create(6, 10, 6) },
  modifiers: [AvatarModifierType.AMT_DISABLE_JUMPING]
})

Avatar Locomotion Settings

Adjust the player's movement speed and jump height:

import { engine, AvatarLocomotionSettings } from '@dcl/sdk/ecs'

// Modify run speed and jump height
AvatarLocomotionSettings.createOrReplace(engine.PlayerEntity, {
  runSpeed: 8,    // default is ~6
  jumpHeight: 3   // default is ~1.5
})

InputModifier — Restrict Player Movement

Disable specific movement modes for the local player. Useful for cutscenes, dialogue freezes, traversal puzzles. Applies to engine.PlayerEntity.

import { engine, InputModifier } from '@dcl/sdk/ecs'

InputModifier.createOrReplace(engine.PlayerEntity, {
  mode: InputModifier.Mode.Standard({
    disableWalk: false,
    disableJog: false,
    disableRun: false,
    disableJump: false,
    disableDoubleJump: false,
    disableGliding: false
  })
})

Set any field to true to disable that mode. While disabled:

  • Gravity still applies (the player still falls).
  • The camera can still rotate freely.
  • The player can still trigger pointer / proximity events.
  • All restrictions are auto-lifted when the player leaves the scene.

Freeze All Movement

InputModifier.createOrReplace(engine.PlayerEntity, {
  mode: InputModifier.Mode.Standard({ disableAll: true })
})

To release: InputModifier.deleteFrom(engine.PlayerEntity) — or createOrReplace with all flags false.

Teleporting the Player

You MUST use movePlayerTo from ~system/RestrictedActions to move or teleport the player. Setting Transform.getMutable(engine.PlayerEntity).position does NOT work — the runtime ignores direct writes to the player transform. Transform on engine.PlayerEntity is read-only; the same applies to engine.CameraEntity.

movePlayerTo only teleports the player within the same scene. Cross-scene teleports require explicit player consent and a different API.

import { movePlayerTo } from '~system/RestrictedActions'

// Move player to a position
void movePlayerTo({
  newRelativePosition: Vector3.create(8, 0, 8)
})

// Move player with camera direction
void movePlayerTo({
  newRelativePosition: Vector3.create(8, 0, 8),
  cameraTarget: Vector3.create(8, 1, 12)
})

movePlayerTo returns a Promise — await it if you need to chain actions:

import { executeTask } from '@dcl/sdk/ecs'

executeTask(async () => {
  await movePlayerTo({ newRelativePosition: Vector3.create(8, 0, 8) })
  console.log('teleported, continuing flow')
})

Avatar Change Listeners

React to avatar changes in real-time:

import { AvatarEmoteCommand, AvatarBase, AvatarEquippedData } from '@dcl/sdk/ecs'

// Detect when any player triggers an emote
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:

  • AvatarAnchorPointType.AAPT_POSITION — avatar feet position
  • AvatarAnchorPointType.AAPT_NAME_TAG — above the name tag
  • AvatarAnchorPointType.AAPT_LEFT_HAND / AAPT_RIGHT_HAND
  • AvatarAnchorPointType.AAPT_HEAD — head bone
  • AvatarAnchorPointType.AAPT_NECK — neck bone

Need to check the player's wallet before showing avatar items? See the nft-blockchain skill for wallet checks with getPlayer() and isGuest.

Best Practices

  • Always check Transform.has(engine.PlayerEntity) before reading player data — it may not be ready on the first frame
  • Use getPlayer() to check isGuest before attempting wallet-dependent features
  • AvatarAttach requires the target player to be in the same scene — attachments disappear when the player leaves
  • Custom emote files must use the _emote.glb naming convention
  • Use AvatarModifierArea with AMT_HIDE_AVATARS for private rooms or puzzle areas
  • Add excludeIds to modifier areas when you want specific players (like the scene owner) to remain visible
  • Never use Transform.getMutable(engine.PlayerEntity) to move the player — it does not work. Always use movePlayerTo from ~system/RestrictedActions
  • Transform.get(engine.PlayerEntity) is valid for reading position only

For component field details, see {baseDir}/../../context/components-reference.md. For full AvatarShape fields, wearable URNs, anchor points, emote names, and event callbacks, see {baseDir}/references/avatar-apis.md.