Work with player avatars in Decentraland scenes. Read player position and profile data, customize appearance with AvatarBase, trigger emotes with triggerEmote/triggerSceneEmote, read equipped wearables via AvatarEquippedData, attach objects to players with AvatarAttach, create NPC avatars with AvatarShape, and modify avatars in areas. Use when user wants player data, emotes, wearables, avatar attachments, or NPCs.
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)
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)
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)isGuest — true if the player hasn't connected a walletAttach 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
})
AvatarAnchorPointType.AAPT_NAME_TAG // Above the head
AvatarAnchorPointType.AAPT_RIGHT_HAND // Right hand
AvatarAnchorPointType.AAPT_LEFT_HAND // Left hand
AvatarAnchorPointType.AAPT_POSITION // Avatar root position
AvatarAttach.create(hat, {
avatarId: '0x123...abc', // Target player's wallet address
anchorPointId: AvatarAnchorPointType.AAPT_RIGHT_HAND
})
import { triggerEmote } from '~system/RestrictedActions'
// Play a built-in emote
triggerEmote({ predefinedEmote: 'robot' })
triggerEmote({ predefinedEmote: 'wave' })
triggerEmote({ predefinedEmote: 'clap' })
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:
_emote.glb suffixCreate 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: []
})
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.
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
})
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
// 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]
})
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.
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)
})
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)
})
Beyond the commonly used anchor points, the full list includes:
AvatarAnchorPointType.AAPT_POSITION — avatar feet positionAvatarAnchorPointType.AAPT_NAME_TAG — above the name tagAvatarAnchorPointType.AAPT_LEFT_HAND / AAPT_RIGHT_HANDAvatarAnchorPointType.AAPT_HEAD — head boneAvatarAnchorPointType.AAPT_NECK — neck boneTransform.has(engine.PlayerEntity) before reading player data — it may not be ready on the first framegetPlayer() to check isGuest before attempting wallet-dependent featuresAvatarAttach requires the target player to be in the same scene — attachments disappear when the player leaves_emote.glb naming conventionAvatarModifierArea with AMT_HIDE_AVATARS for private rooms or puzzle areasexcludeIds to modifier areas when you want specific players (like the scene owner) to remain visibleTransform.getMutable(engine.PlayerEntity) to move the player — it does not work. Always use movePlayerTo from ~system/RestrictedActionsTransform.get(engine.PlayerEntity) is valid for reading position onlyFor component field details, see {baseDir}/../../context/components-reference.md.