skills/npcs/SKILL.md
--- name: npcs description: Create NPCs (non-player characters) in Decentraland scenes. Two approaches: the NPC Toolkit library (dcl-npc-toolkit) for GLB-based NPCs with built-in dialogue, movement, and state machines; and AvatarShape for avatar-look NPCs dressed in wearables. Use when the user wants to add an NPC, character, shopkeeper, quest giver, guard, or any non-player entity with behavior or dialogue. For live player data (position, profile, wearables) see player-avatar instead. --- # NP
npx skillsauth add dcl-regenesislabs/opendcl skills/npcsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Two approaches — choose based on what the NPC needs to do:
| Approach | Use when |
|---|---|
| NPC Toolkit (dcl-npc-toolkit) | GLB model, needs dialogue, walking, state machine behavior |
| AvatarShape | Needs to look like a Decentraland avatar (wearables, expressions) |
main-entities.ts (AvatarShape is in the supported component list). Click handlers / proximity systems live in src/index.ts.createNPC(...) from the toolkit library, which is a runtime API — call it from src/index.ts. The placement entity itself (a marker for the spawn position) can still live in main-entities.ts if you want it editable in the gizmo.The toolkit handles dialogue UI, movement along paths, animations, and interaction out of the box.
Install:
npm i dcl-npc-toolkit
Basic usage:
// src/index.ts
import { createNPC, Dialog } from 'dcl-npc-toolkit'
import { Vector3, Quaternion } from '@dcl/sdk/math'
export function main() {
const npcEntity = createNPC(
{ position: Vector3.create(8, 0, 8), rotation: Quaternion.fromEulerDegrees(0, 180, 0) },
'models/guard.glb',
(entity) => {
// called when player clicks the NPC
startDialogue(entity)
},
{
idleAnim: 'Idle',
walkingAnim: 'Walk',
hoverText: 'Talk',
onlyExternalTrigger: false
}
)
}
textWrap: 'nowrap' in a fixed-width slot (~217px at default font 16). Anything past ~15 characters is silently clipped — no ellipsis. Use short labels like "Yes", "No thanks", "Tell me more", "Decline". To fit longer text, drop fontSize (e.g. 12) or set size on the button.createNPC requires addDialog(entity) and a minimal npcDataComponent.set(entity, ...) — see the toolkit reference for the full setup.createDialogBubble(entity) before talkBubble. Bubbles do not render question buttons; questions are HUD-only.Create an NPC that looks like a Decentraland player avatar, dressed in any wearables. Supported in main-entities.ts — declare the NPC declaratively:
// main-entities.ts
import type { Scene } from '@dcl/sdk/scene-types'
export const scene = {
guard: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 } },
AvatarShape: {
id: 'npc-1', // unique (required)
name: 'Guard', // shown above head
bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale',
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'
],
emotes: [],
hairColor: { r: 0.92, g: 0.76, b: 0.62 },
skinColor: { r: 0.94, g: 0.85, b: 0.6 }
}
}
}
} satisfies Scene
Notes:
Transform position causes the NPC to walk/run to the destination (it does not teleport).expressionTriggerTimestamp as a Lamport timestamp to replay the same emote: first play = 0, second play = 1, etc.// src/index.ts
const npc = engine.getEntityOrNullByName('guard')
if (npc) {
AvatarShape.getMutable(npc).expressionTriggerId = 'wave'
AvatarShape.getMutable(npc).expressionTriggerTimestamp = 1
}
Useful for storefronts and wearable displays:
// main-entities.ts
mannequin: {
components: {
Transform: { position: { x: 4, y: 0, z: 4 } },
AvatarShape: {
id: 'mannequin-1',
name: 'Display',
bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale',
wearables: ['urn:decentraland:matic:collections-v2:0x...:0'],
emotes: [],
show_only_wearables: true
}
}
}
For the full AvatarShape field reference and common base wearable URNs, see the player-avatar skill's references.
AvatarShape entities are not clickable by default — they have no collider, so pointer events won't register on them directly. Use one of:
Declare the collider in main-entities.ts alongside the AvatarShape:
// main-entities.ts (added to the guard entry above)
MeshCollider: { mesh: { $case: 'cylinder', cylinder: {} } }
// src/index.ts
import { engine, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
export function main() {
const npc = engine.getEntityOrNullByName('guard')
if (npc) pointerEventsSystem.onPointerDown(
{ entity: npc, opts: { button: InputAction.IA_POINTER, hoverText: 'Talk' } },
() => { console.log('Player clicked NPC') }
)
}
Trigger the interaction when the player walks near the NPC (no collider required):
import { engine, pointerEventsSystem } from '@dcl/sdk/ecs'
export function main() {
const npc = engine.getEntityOrNullByName('guard')
if (npc) pointerEventsSystem.onProximityEnter(
{ entity: npc, opts: { maxPlayerDistance: 4 } },
() => { /* start dialogue or other interaction */ }
)
}
See the add-interactivity skill for the full Proximity Events API.
development
Capture screenshots of the running Decentraland preview to verify scene changes visually. Covers camera movement, interaction actions, and visual debugging. Use when the preview is running and you need to check what the scene looks like, debug visual issues, or verify layout. Do NOT use for code changes (make changes first, then screenshot).
development
Cross-cutting runtime APIs for Decentraland SDK7 scenes. Use when the user needs async operations (executeTask), HTTP requests (fetch, signedFetch), WebSocket connections, timers, realm/scene detection, restricted actions (movePlayerTo, teleportTo, triggerEmote, openExternalUrl), portable experiences, or the testing framework. Do NOT use for UI (see build-ui), multiplayer sync (see multiplayer-sync), or avatar/player data (see player-avatar).
development
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, anti-gravity, lift, levitation, hover), timed forces, and repulsion fields. Use when the user wants launch pads, knockback on hit, wind zones, gravity fields, jumps, lifting/floating the player, pushing the player up/sideways/back, hover effects, or any scene-applied force on the player. THIS is also the right skill when an agent's first instinct is to mutate `Transform` on `engine.PlayerEntity` to move/lift/push the player — that does NOT work (the player Transform is engine-controlled and read-only); use the Physics API instead. Do NOT use for player movement speed (see player-avatar AvatarLocomotionSettings) or platform movement (see animations-tweens).
data-ai
Player and avatar system in Decentraland. Read player position/profile, customize appearance (AvatarBase), trigger emotes (triggerEmote/triggerSceneEmote), read equipped wearables (AvatarEquippedData), attach objects to players (AvatarAttach), create NPC avatars (AvatarShape), avatar modifier areas, and locomotion settings. Use when the user wants player data, emotes, wearables, NPC avatars, avatar attachments, or movement speed changes. Do NOT use for wallet/blockchain interactions (see nft-blockchain).