Loading...
Loading...
The live player in a Decentraland scene. Use when the user wants to read player position or profile, fetch avatar appearance for off-scene addresses (parcel owners, NFT holders), trigger emotes, read equipped wearables, attach items to players/avatar (cosmetic vs held gameplay items), hide avatars or disable passports in zones (AvatarModifierArea), adjust locomotion speed, teleport the player (movePlayerTo), or listen for scene entry/exit. Do NOT use for NPC characters (see npcs), wallet/blockchain checks (see nft-blockchain), freezing player movement (see advanced-input for InputModifier), or camera mode (see camera-control).
npx skill4agent add decentraland/sdk-skills player-avatarTransformengine.PlayerEntity// 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, { ... }) // ignoredTransform...PlayerEntity| 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.PlayerEntityimport { 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)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)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)
}
}userIdisGuesttrueisGuestgetPlayer(userId)GET https://peer.decentraland.org/lambdas/profile/<wallet-address>peer.decentraland.org/lambdasrealmInfo.baseUrljson.avatars[0].avatar.{ bodyShape, wearables, eyes:{color}, hair:{color}, skin:{color} }json[0].metadata.avatars...{ avatars: [], timestamp: 0 }{ r, g, b, a }[0,1]Color3AvatarShape.skinColorhairColoreyeColorColor3{ color: Color3 }AvatarShape.create({ id: address })idbodyShapewearablesgetPlayer(userId)fetchAvatarFromCatalyst(address)fetchAvatarFromCatalyst{baseDir}/references/catalyst-profile-fetch.mdimport {
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
AvatarAttachengine.CameraEntityengine.PlayerEntityengine.PlayerEntityTransform.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. |
engine.CameraEntityengine.PlayerEntityPlayerEntityengine.CameraEntityAttachable.FIRST_PERSON_CAMERAAAPT_RIGHT_HANDAAPT_SPINEAAPT_HEADimport { 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.PlayerEntityPlayerEntityCameraModeArea// WRONG — gun jitters with every idle/walk/gesture animation frame
AvatarAttach.create(gun, {
anchorPointId: AvatarAnchorPointType.AAPT_RIGHT_HAND,
})// 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.CameraEntityPlayerEntityAttachable.FIRST_PERSON_CAMERAAttachable.AVATARAttachable.FIRST_PERSON_CAMERAengine.CameraEntityAvatarAttachengine.PlayerEntityPlayerEntityAvatarAnchorPointType.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_TAGAAPT_RIGHT_HANDAAPT_SPINEAAPT_HEADengine.CameraEntityengine.PlayerEntityAvatarAttach.create(hat, {
avatarId: '0x123...abc', // Target player's wallet address
anchorPointId: AvatarAnchorPointType.AAPT_RIGHT_HAND,
})avatarIdengine.getEntitiesWith(PlayerIdentityData)player.addressimport { 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
}
})getPlayer()await getPlayer().userIdavatarIdAvatarAttachimport { triggerEmote } from '~system/RestrictedActions'
// Play a built-in emote
triggerEmote({ predefinedEmote: 'robot' })
triggerEmote({ predefinedEmote: 'wave' })
triggerEmote({ predefinedEmote: 'clap' })⚠️ 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
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,
})InputModifiertriggerEmotetriggerSceneEmoteALLOW_TO_TRIGGER_AVATAR_EMOTEscene.jsonrequiredPermissionsmaskstopEmote({})~system/RestrictedActionstriggerSceneEmote({ src, loop: true })import { stopEmote } from '~system/RestrictedActions'
stopEmote({})triggerEmotetriggerSceneEmotemaskAvatarMask@dcl/sdk/ecsimport { AvatarMask } from '@dcl/sdk/ecs'
import { triggerSceneEmote } from '~system/RestrictedActions'
triggerSceneEmote({ src: 'animations/Carry_emote.glb', loop: true, mask: AvatarMask.AM_UPPER_BODY })AvatarMask.AM_UPPER_BODYmaskAM_FULL_BODYmasktriggerEmotetriggerSceneEmotestopEmote({})StopEmoteRequestloop: falsemask: AM_UPPER_BODYloop: truestopEmote({})88,-13-avatar-masks80,-1-scene-emotes1c0f394restricted_actions.protocommon/avatar_mask.proto@dcl/sdk0010e7088,-13-avatar-masksAvatarEmoteMaskAEM_UPPER_BODYAEM_FULL_BODYAvatarShapeimport {
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
})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_PASSPORTSimport { 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
})floatorigin/mainCharacterControllerSettings.assetwalkSpeedjogSpeedrunSpeedjumpHeightrunJumpHeightdoubleJumpHeightglidingSpeedglidingFallingSpeedhardLandingCooldownreferences/avatar-apis.mdglidingFallingSpeedplayer-physicsInputModifierengine.PlayerEntityimport { 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)InputModifier.Mode.Standard({...})disableAlldisableWalkdisableJogdisableRundisableJumpdisableEmotedisableDoubleJumpdisableGlidingdisableJogdisableWalkdisableRunmodeInputModifier.Mode.Standard({...}){ $case: 'standard', standard: {...} }triggerSceneEmotemovePlayerTo~system/RestrictedActionsTransform.getMutable(engine.PlayerEntity).positionplayer-physicsmovePlayerTomovePlayerTonewRelativePositionVector3cameraTargetavatarTargetdurationCL_PHYSICSALLOW_TO_MOVE_PLAYER_INSIDE_SCENEscene.jsonrequiredPermissionsnewRelativePositioncameraTargetavatarTargety: 12import { movePlayerTo } from '~system/RestrictedActions'
void movePlayerTo({
newRelativePosition: Vector3.create(8, 0, 8),
cameraTarget: Vector3.create(8, 1, 12),
avatarTarget: Vector3.create(8, 1, 12),
})durationmovePlayerTosuccessfalseimport { 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')
}
}InputModifiermovePlayerToimport { 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)
}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)
})AvatarAnchorPointType.AAPT_POSITIONAvatarAnchorPointType.AAPT_NAME_TAGAvatarAnchorPointType.AAPT_LEFT_HANDAAPT_RIGHT_HANDAvatarAnchorPointType.AAPT_HEADAvatarAnchorPointType.AAPT_NECKNeed to check the player's wallet before showing avatar items? See the nft-blockchain skill for wallet checks withandgetPlayer().isGuest
AvatarAttachPlayerIdentityDataplayer.addressPlayerEntitytriggerEmotetriggerSceneEmote_emote.glbstopEmotemask: AvatarMask.AM_UPPER_BODYloop: falseloop: truemovePlayerTodurationresult.success.then()InputModifierCL_PHYSICSAvatarModifierAreaAMT_HIDE_AVATARSexcludeIdsCameraModeAreaAvatarModifierAreaAMT_HIDE_NAMETAGSInputModifierdisableAll/Walk/Jog/Run/Jump/Emote$casemovePlayerToyavatarTargettriggerEmotetriggerSceneEmoteteleportToopenExternalUrlAvatarMask.AM_UPPER_BODYAvatarAttachstopEmoteloop: falseloop: true{baseDir}/../sdk-scenes/references/components-reference.md{baseDir}/references/avatar-apis.md