player-avatar
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' })
Transform.crete(hat, {})
// 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
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
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
- If you don't want a player to interrupt an emote, use the
InputModifiercomponent to freeze the player for the duration of the emote - Custom emote files must have the
_emote.glbsuffix
NPC Avatars
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 AvatarShape-based avatar NPCs.
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
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
})
Restrict Locomotion (InputModifier)
Use InputModifier on engine.PlayerEntity to freeze or selectively restrict the player's movement — useful for cutscenes, locked interactions, or controlled game mechanics.
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.
Tip: Combine with triggerSceneEmote — freeze the player during an animation, then remove InputModifier when it ends.
For all available flags (disableWalk, disableRun, disableJump, etc.) and the cutscene pattern, see the advanced-input skill.
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.
movePlayerTo accepts:
newRelativePosition— where to move the player (scene-relativeVector3)cameraTarget(optional) — a point in space for the camera to face after movingavatarTarget(optional) — a point in space for the avatar to face after movingduration(optional) — transition time in seconds; if provided, movement can be awaited
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
Instant teleport
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 duration is set, movePlayerTo is awaitable. The resolved value has a success boolean — false if the player interrupted the movement with input.
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 InputModifier with movePlayerTo to lock movement for the duration:
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
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
Need to check the player's wallet before showing avatar items? See the nft-blockchain skill for wallet checks with
getPlayer()andisGuest.
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 single-player 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}/../sdk-scenes/references/components-reference.md.
For anchor points, emote names, and event callbacks, see {baseDir}/references/avatar-apis.md.
More from decentraland/sdk-skills
decentraland-sdk-skills
Build, extend, and deploy Decentraland SDK7 scenes. Contains agent behavioral guidelines, the composite-first rule, and an index of all topic skills with reference files. Install with a single command — no flags needed.
18build-ui
Build 2D screen-space UI for Decentraland scenes using React-ECS (JSX). Create HUDs, menus, health bars, scoreboards, dialogs, buttons, inputs, and dropdowns. Use when the user wants screen overlays, on-screen UI, HUD elements, menus, or form inputs. Do NOT use for 3D in-world text (see advanced-rendering) or clickable 3D objects (see add-interactivity).
3advanced-input
System-level input polling and player movement control in Decentraland. Covers inputSystem (isTriggered/isPressed for held keys, WASD polling), InputModifier (freeze/restrict player movement), PointerLock (cursor capture detection), PrimaryPointerInfo (cursor screen coords and world ray), and number-key action bar patterns. Use when the user wants continuous key polling, WASD-controlled entities, to freeze the player during a cutscene, FPS-style cursor lock, or multi-key combo patterns. For event-driven clicks and hover on entities see add-interactivity.
3nft-blockchain
NFT display and blockchain interaction in Decentraland. NftShape (framed NFT artwork), wallet checks (getPlayer, isGuest), signedFetch (authenticated requests), smart contract interaction (eth-connect, createEthereumProvider), and RPC calls. Use when the user wants NFTs, blockchain, wallet, smart contracts, Web3, crypto, or token gating. Do NOT use for player avatar data or emotes (see player-avatar).
3player-physics
Apply physics forces to the player in Decentraland scenes. Impulses (one-shot pushes), knockback (push away from a point with falloff), continuous forces (wind tunnels), timed forces, and repulsion fields. Use when the user wants launch pads, knockback on hit, wind zones, gravity fields, or any scene-applied force on the player. Do NOT use for player movement speed (see player-avatar) or platform movement (see animations-tweens).
3camera-control
Control camera behavior in Decentraland scenes. CameraMode detection (first/third person, onChange listener), CameraModeArea (force a mode inside a box), VirtualCamera (cinematic scripted cameras with Speed/Time transitions and lookAtEntity), MainCamera (activate/deactivate virtual cameras), and camera vs collider interactions (CL_PHYSICS + CL_POINTER). Use when the user wants camera control, cutscenes, cinematic views, forced camera modes, or camera tracking. Do NOT use for input restriction during cutscenes (see advanced-input for InputModifier) or cursor lock detection (see advanced-input for PointerLock).
3