mcpField
P
player-avatar

player-avatar

github.com/dcl-regenesislabs开发1737 次安装21 周前更新
代码仓库

关于 player-avatar

Decentraland SDK7 的玩家与 Avatar 开发参考,面向场景开发者,涵盖玩家状态、NPC、附件、动作和移动控制。

主要功能

  1. 01读取玩家位置与档案
  2. 02配置 NPC Avatar 与穿戴物
  3. 03将物体附加到玩家骨骼点
  4. 04触发默认及自定义表情动作
  5. 05设置区域修饰与移动限制
  6. 06通过受限操作传送玩家

适用场景

  1. 01按玩家与 NPC 的距离触发场景逻辑
  2. 02为玩家角色添加手持或头部物件
  3. 03在对话或过场中限制玩家移动

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dcl-regenesislabs/opendcl --skill player-avatar

安装后在 Claude Code 中输入/player-avatar确认已加载。

附带文件references/avatar-apis.md

详细介绍

Decentraland 中的玩家与 Avatar 系统

编写方式的划分

AvatarShape(用于 NPC 和预先放置的 Avatar 的组件)受 main-entities.ts 支持 — 在此处完整声明 NPC,包括 id、名称、可穿戴物品等:

// main-entities.ts
shopkeeper: {
  components: {
    Transform: { position: { x: 8, y: 0, z: 8 } },
    AvatarShape: {
      id: 'shopkeeper-1',
      name: 'Shopkeeper',
      bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale',
      wearables: [],
      emotes: []
    }
  }
}

AvatarAttach、AvatarModifierArea、AvatarBase、AvatarEquippedData 不在支持列表中 — 它们按设计用于运行时(绑定到实时玩家,或应用于动态创建的实体)。在 src/index.ts 中添加它们,并将其附加到通过 getEntityOrNullByName 查找的实体(用于静态放置),或运行时创建的实体(用于绑定到玩家的效果)。

预留的 engine.PlayerEntity 由引擎管理,在 main-entities.ts 中没有对应表示。

玩家位置与移动

通过预留的 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)

玩家资料数据

获取玩家的名称、钱包地址和访客状态:

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 — 玩家的 Ethereum 钱包地址(或访客 ID)
  • isGuest — 如果玩家尚未连接钱包,则为 true
从 Catalyst 获取完整的 Avatar 资料

getPlayer() 返回玩家的本地视图。要获取完整的 Avatar 数据(可穿戴物品列表、身体形状、皮肤/头发/眼睛颜色),请从 Catalyst 获取:

import { executeTask, signedFetch } from '@dcl/sdk/network'
import { getPlayer } from '@dcl/sdk/players'

executeTask(async () => {
  const player = getPlayer()
  if (!player || player.isGuest) return

  const res = await fetch(`https://peer.decentraland.org/lambdas/profiles/${player.userId}`)
  const body = await res.json()

  // Response is an array of profiles; the first entry holds the active avatar.
  const profile = body?.avatars?.[0]
  if (!profile) return

  console.log('name:', profile.name)
  console.log('wearables:', profile.avatar.wearables)
  console.log('skin color:', profile.avatar.skin.color)  // { r, g, b } — already unwrapped, NOT { color: { r,g,b } }
})

注意事项:

  • 响应格式为 { avatars: [...] },而非扁平的资料对象。始终读取 body.avatars[0]。
  • 颜色字段(skin.color、hair.color、eyes.color)已经是 { r, g, b } 对象 — 不要再多解开一层。
  • 此 fetch 请求无需身份验证;对于需要玩家签名身份的端点,请使用 @dcl/sdk/network 中的 signedFetch,而不是普通的 fetch。

Avatar 附件

将 3D 对象附加到玩家的 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
})
锚点

附件可跟踪的骨骼/位置的完整枚举。在 main-entities.ts 中使用整数值(左侧)。在 src/index.ts 中使用 AvatarAnchorPointType.<NAME>。

值枚举位置
0AAPT_POSITIONAvatar 的脚部(已弃用 — 建议使用 parent: engine.PlayerEntity)
1AAPT_NAME_TAG名牌上方
2AAPT_LEFT_HAND左手
3AAPT_RIGHT_HAND右手
4AAPT_HEAD头部骨骼
5AAPT_NECK颈部
6AAPT_SPINE脊柱根部
7AAPT_SPINE1脊柱中部
8AAPT_SPINE2脊柱顶部
9AAPT_HIP髋部
10AAPT_LEFT_SHOULDER左肩
11AAPT_LEFT_ARM左上臂
12AAPT_LEFT_FOREARM左前臂
13AAPT_LEFT_HAND_INDEX左手食指
14AAPT_RIGHT_SHOULDER右肩
15AAPT_RIGHT_ARM右上臂
16AAPT_RIGHT_FOREARM右前臂
17AAPT_RIGHT_HAND_INDEX右手食指
18AAPT_LEFT_UP_LEG左大腿
19AAPT_LEFT_LEG左小腿
20AAPT_LEFT_FOOT左脚
21AAPT_LEFT_TOE_BASE左脚趾
22AAPT_RIGHT_UP_LEG右大腿
23AAPT_RIGHT_LEG右小腿
24AAPT_RIGHT_FOOT右脚
25AAPT_RIGHT_TOE_BASE右脚趾
附加到特定玩家
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
})

注意:

  • 表情动作仅在玩家静止站立时播放 — 行走或跳跃会中断播放
  • 自定义表情动作文件必须使用 _emote.glb 后缀

NPC Avatar

使用 AvatarShape 创建 Avatar 形状的 NPC:

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: []
})
模特(仅展示可穿戴物品)

仅展示可穿戴物品,不显示完整的 Avatar 身体:

AvatarShape.create(mannequin, {
  id: 'mannequin-1',
  name: 'Display',
  wearables: [
    'urn:decentraland:matic:collections-v2:0x90e5cb2d673699be8f28d339c818a0b60144c494:0'
  ],
  show_only_wearables: true
})

NPC Avatar 是静态的 — 它们会显示 Avatar 模型,但不会自行移动或播放动画。可与 Animator 或 Tween 组合以实现移动。

Avatar 修饰区域

修改 Avatar 在某个区域内的外观或行为:

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]
})

Avatar 移动设置

调整玩家的移动速度和跳跃高度:

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
})

InputModifier — 限制玩家移动

为本地玩家禁用特定的移动模式。适用于过场动画、对话冻结和移动谜题。应用于 engine.PlayerEntity。

import { engine, InputModifier } from '@dcl/sdk/ecs'

InputModifier.createOrReplace(engine.PlayerEntity, {
  mode: InputModifier.Mode.Standard({
    disableWalk: false,
    disableJog: false,
    disableRun: false,
    disableJump: false,
    disableDoubleJump: false,
    disableGliding: false
  })
})

将任意字段设为 true 即可禁用对应模式。禁用期间:

  • 重力仍然生效(玩家仍会下落)。
  • 摄像头仍可自由旋转。
  • 玩家仍可触发指针/接近事件。
  • 玩家离开场景时,所有限制都会自动解除。
冻结所有移动
InputModifier.createOrReplace(engine.PlayerEntity, {
  mode: InputModifier.Mode.Standard({ disableAll: true })
})

要解除限制:InputModifier.deleteFrom(engine.PlayerEntity),或者使用所有标志均为 false 的参数调用 createOrReplace。

传送玩家

必须使用 ~system/RestrictedActions 中的 movePlayerTo 来移动或传送玩家。 设置 Transform.getMutable(engine.PlayerEntity).position 不起作用——运行时会忽略对玩家变换的直接写入。engine.PlayerEntity 上的 Transform 为只读;engine.CameraEntity 也一样。

movePlayerTo 只能在同一场景内传送玩家。跨场景传送需要获得玩家的明确同意,并使用不同的 API。

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)
})

movePlayerTo 会返回一个 Promise——如果需要串联操作,请使用 await:

import { executeTask } from '@dcl/sdk/ecs'

executeTask(async () => {
  await movePlayerTo({ newRelativePosition: Vector3.create(8, 0, 8) })
  console.log('teleported, continuing flow')
})
Avatar 变更监听器

实时响应 Avatar 变更:

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)
})
其他锚点

除了常用锚点外,完整列表还包括:

  • AvatarAnchorPointType.AAPT_POSITION — Avatar 脚部位置
  • AvatarAnchorPointType.AAPT_NAME_TAG — 名称标签上方
  • AvatarAnchorPointType.AAPT_LEFT_HAND / AAPT_RIGHT_HAND
  • AvatarAnchorPointType.AAPT_HEAD — 头部骨骼
  • AvatarAnchorPointType.AAPT_NECK — 颈部骨骼

显示 Avatar 道具前需要检查玩家的钱包? 请参阅 nft-blockchain 技能,了解如何使用 getPlayer() 和 isGuest 检查钱包。

最佳实践

  • 读取玩家数据前,始终检查 Transform.has(engine.PlayerEntity)——第一帧时数据可能尚未就绪
  • 尝试使用依赖钱包的功能前,使用 getPlayer() 检查 isGuest
  • AvatarAttach 要求目标玩家与当前玩家处于同一场景——玩家离开后,附加物会消失
  • 自定义表情文件必须遵循 _emote.glb 命名约定
  • 在私人房间或谜题区域中使用 AvatarModifierArea 和 AMT_HIDE_AVATARS
  • 如果希望特定玩家(例如场景所有者)保持可见,请在修改器区域中添加 excludeIds
  • 绝不要使用 Transform.getMutable(engine.PlayerEntity) 移动玩家——这不起作用。始终使用 ~system/RestrictedActions 中的 movePlayerTo
  • Transform.get(engine.PlayerEntity) 仅可用于读取位置

组件字段详情请参阅 {baseDir}/../../context/components-reference.md。 AvatarShape 的完整字段、可穿戴物品 URN、锚点、表情名称和事件回调请参阅 {baseDir}/references/avatar-apis.md。

评论

支持 Markdown