Player and Avatar System in Decentraland
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)isGuest—trueif the player hasn't connected a wallet
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
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
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.glbsuffix
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]
})
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.
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)
})
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 positionAvatarAnchorPointType.AAPT_NAME_TAG— above the name tagAvatarAnchorPointType.AAPT_LEFT_HAND/AAPT_RIGHT_HANDAvatarAnchorPointType.AAPT_HEAD— head boneAvatarAnchorPointType.AAPT_NECK— neck bone
Best Practices
- Always check
Transform.has(engine.PlayerEntity)before reading player data — it may not be ready on the first frame - Use
getPlayer()to checkisGuestbefore attempting wallet-dependent features AvatarAttachrequires the target player to be in the same scene — attachments disappear when the player leaves- Custom emote files must use the
_emote.glbnaming convention - Use
AvatarModifierAreawithAMT_HIDE_AVATARSfor private rooms or puzzle areas - Add
excludeIdsto 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 usemovePlayerTofrom~system/RestrictedActions Transform.get(engine.PlayerEntity)is valid for reading position only
For component field details, see {baseDir}/../../context/components-reference.md.