mcpField
N
npcs

npcs

github.com/decentraland開発4277 回インストール今日更新
リポジトリ

npcs について

Decentraland のシーン制作者向けに、GLB ベースの NPC Toolkit と AvatarShape による NPC 作成を案内するスキルです。会話、移動、アバターの外見や表情、および実装時の注意点を扱います。

主な機能

  1. 01NPC Toolkit の会話と移動
  2. 02経路とステートマシンの動作
  3. 03AvatarShape の外見と表情
  4. 04AvatarNametag のラベルプレート
  5. 05NPC のアニメーションと移動の注意点

活用シーン

  1. 01会話と経路動作を備えた GLB NPC の作成
  2. 02Decentraland のウェアラブルを着たアバター NPC の作成
  3. 03NPC への表情やネームラベルの追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/decentraland/sdk-skills --skill npcs

インストール後、Claude Code で/npcsを入力して読み込まれたか確認します。

同梱ファイルreferences/npc-library.mdc

詳細

Decentraland の NPC

2 つの方法があります。NPC に必要な機能に応じて選んでください。

方法使用する場面
NPC Toolkit (dcl-npc-toolkit)GLB モデルで、会話、歩行、ステートマシンによる動作が必要な場合
AvatarShapeDecentraland のアバターのような見た目(ウェアラブル、表情)が必要な場合

方法 1 — NPC Toolkit(GLB ベース)

このツールキットでは、会話 UI、経路に沿った移動、アニメーション、インタラクションをすぐに利用できます。

インストール:

npm i dcl-npc-toolkit

基本的な使い方:

import * as npc from 'dcl-npc-toolkit'
import { Vector3, Quaternion } from '@dcl/sdk/math'

const dialogs: npc.Dialog[] = [{ text: 'Hello there!', isEndOfDialog: true }]

const npcEntity = npc.create(
  { position: Vector3.create(8, 0, 8), rotation: Quaternion.fromEulerDegrees(0, 180, 0) },
  {
    type: npc.NPCType.CUSTOM,
    model: { src: 'models/guard.glb' },
    idleAnim: 'Idle',
    walkingAnim: 'Walk',
    hoverText: 'Talk',
    onlyExternalTrigger: false,
    onActivate: () => {
      // called when player activates the NPC
      npc.talk(npcEntity, dialogs)
    },
  }
)

会話スクリプト、移動経路、ステートマシン、すべての設定オプションについては、{baseDir}/references/npc-library.mdc を参照してください。次の内容を扱っています。

  • 会話の種類(会話、ボタンの選択肢、NPC の応答)
  • 位置までの歩行と経路の追従
  • 状態管理(クエストの依頼人、衛兵、ショップのパターン)
  • マルチプレイヤーに関する考慮事項
  • パフォーマンスの最適化
注意点(NPC Toolkit)
  • ボタンのラベルは表示時に切り詰められます。 ダイアログのボタンラベルは、固定幅の領域(デフォルトのフォントサイズは 16、領域はスケーリング後で約 217px)で textWrap: 'nowrap' を使って描画されます。約 15 文字を超える部分は、省略記号も付かずに切り詰められます。"Yes"、"No thanks"、"Tell me more"、"Decline" のような短いラベルを使ってください。文章全体や末尾の句読点は避けてください(例:"I'm not interested." は "I'm not interes" と表示されます)。長いテキストを収めるには、fontSize を下げる(例:12)か、ボタンの size を設定してください。references/npc-library.mdc の「ButtonData fields」を参照してください。

  • npc.create で作成されていないエンティティでダイアログを開くには、addDialog(entity) と最小限の npcDataComponent.set(entity, ...) が必要です。完全な設定については、リファレンスを参照してください。

  • 吹き出しを使うには、talkBubble の前に createDialogBubble(entity) が必要です。吹き出しには質問ボタンは表示されず、質問は HUD にのみ表示されます。

  • npcDataComponent も設定しないと、createDialogWindow() によってダイアログ UI がクラッシュします。 症状:ウィンドウが開くときに "Cannot read properties of undefined (reading 'theme')" が発生します。理由(dcl-npc-toolkit/dist を確認):createDialogWindow(portrait, sound) は addDialog(...)(npcDialogComponent を設定)を呼び出すだけで、npcDataComponent は設定しません。その後、openDialogWindow が activeNPC を設定し、npcDialogComponent のガード(isActiveNpcSet())を通過して、getTheme() が npcDataComponent.get(activeNPC).theme にアクセスしますが、スタンドアロンのウィンドウでは値が未定義です。create()/createNPC で作成した NPC には npcDataComponent が設定されているため、この問題は発生しません。修正方法 — createDialogWindow の後に、有効なテーマと最小限のフィールドを指定して npcDataComponent を設定します。

    import { createDialogWindow } from 'dcl-npc-toolkit'
    import { npcDataComponent } from 'dcl-npc-toolkit/dist/npc'
    import { lightTheme } from 'dcl-npc-toolkit/dist/ui'
    
    const window = createDialogWindow(portrait, sound)
    npcDataComponent.set(window, {
      introduced: false, inCooldown: false, coolDownDuration: 5,
      faceUser: undefined, walkingSpeed: 2, walkingAnim: undefined,
      pathData: undefined, currentPathData: [], manualStop: false,
      pathIndex: 0, state: 'standing', idleAnim: 'Idle', hasBubble: false,
      turnSpeed: 2, theme: lightTheme, bubbleXOffset: 0, bubbleYOffset: 0,
      lastPlayedAnim: 'Idle', volume: 0.5,
    })
    
  • faceUser: true — 固定ワールドオブジェクトを faceUser NPC の子にしないでください。 理由(確認済み — dcl-npc-toolkit/dist/faceUserSystem.js):faceUser は毎フレーム(TrackUserFlag と faceUserSystem を介して)NPC エンティティの Transform.rotation を書き換え、プレイヤーの方を向かせます。ローカルオフセットに配置した子エンティティはその回転を引き継ぎ、プレイヤーの移動に合わせて NPC の周囲を旋回するため、意図しない場所(ドアの裏、遮蔽物の向こう、クリックできない位置など)に移動してしまいます。修正方法:そのようなオブジェクトは親を設定せず、計算したワールド座標に生成します(ドアなど回転しない参照オブジェクトから安定したワールド座標を求め、NPC の位置を加算します)。同じ注意点は、毎フレーム Transform を回転させるすべてのエンティティに当てはまります。

  • dcl-npc-toolkit はエントリーポイントから静的にインポートする必要があります。 このツールキットはモジュールの読み込み時に engine.defineComponent(...) を呼び出します。エンジンがシールされた後に実行すると、engine.defineComponent は "Engine is already sealed. No components can be added at this stage" をスローします(確認済み — @dcl/ecs/dist/engine/index.js)。動的な await import('./client-setup') を使うと、コンポーネントの登録がシール後まで遅れます。そのため、ツールキットをインポートするモジュールは、await import() で後から読み込むのではなく、index.ts から静的に import する必要があります。権威サーバー方式のシーンでは、クライアント設定(ツールキットをインポートするもの)は静的に保ち、サーバー専用の分岐のみを動的にインポートしてください。[[authoritative-server]] を参照してください。

アプローチ 2 — AvatarShape(Decentraland のアバターの見た目)

任意のウェアラブルを着用した、Decentraland のプレイヤーアバターのような見た目の 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: 'guard-0001',          // unique; its last 4 characters show beside the name
  name: 'Guard',             // display name shown above head
  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 0–1
  skinColor: { r: 0.94, g: 0.85, b: 0.6 },
})

注意事項:

  • 眉、口、目のウェアラブルは必ず含めてください。これらがないと、アバターに顔のパーツが表示されません。
  • id は必須で、AvatarShape エンティティごとに一意である必要があります。Explorer では、名前の後に id の末尾 4 文字が薄いテキストで表示されます。未取得のプレイヤー名にアドレスが表示されるのと同じです。 NPC にはプロフィールがないため、常にこの接尾辞が付き、オフにするフィールドはありません。たとえば、id: 'guard-1' と name: 'Guard' を設定すると、「Guard」の後に薄い「rd-1」が表示されます。guard-0001 のように、末尾 4 文字が意図的に見える ID を選んでください(4 文字未満の ID では 0000 が表示されます)。接尾辞を消すには、タグ全体を削除するしかありません(unity-explorer NametagPlacementSystem、NametagElement.BuildName、main 2026-10)。
  • NPC の頭上にネームタグを表示しないようにするには、name: ''(空文字列)を設定します。接尾辞も表示されなくなります。
  • Transform の位置を変更すると、NPC は目的地まで歩いたり走ったりします(テレポートはしません)。この変更は反映されます。読み取り専用のプレイヤーの Transform とは異なります。また、スクリプトで経路を設定するには、Tween(Tween.Mode.Move)と TweenSequence を使用できます。アバターは tween に沿って歩行アニメーションを再生します。NPC をプレイヤー(または別の移動するターゲット)に継続的に追従させるには、Tween.setMoveContinuous を使用してください。毎フレーム setMove tween を作り直すと、目に見えるちらつきが発生するため、絶対に避けてください。animations-tweens スキルの「PITFALL — a CONSTANTLY changing target」と、79,-4-tween-following-cube テストシーンを参照してください。
  • 同じエモートを再生するには、expressionTriggerTimestamp にラマポートのタイムスタンプを設定します。1 回目の再生は 0、2 回目は 1、というように増やします。
  • 現在のプレイヤーの見た目を複製する: getPlayer() を読み取り、userData.wearables と userData.emotes をそのまま AvatarShape.create に渡します。最初の数フレームでは getPlayer() が null を返したり、エモートが空だったりすることがあります。スポーンする前に、userData.emotes.length > 0 になるまでシステム内でポーリングしてください(テストシーン 4,21 で確認済みのパターンです)。
NPC の名前の上に表示するラベルプレート(AvatarNametag)

AvatarNametag はプレイヤーだけでなく、AvatarShape を持つすべてのエンティティで機能します。同じエンティティにアタッチしてください。

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

AvatarNametag.create(npcEntity, { label: 'Shopkeeper' })
  • AvatarShape に name: '' を設定すると、ネイティブの空の名前ボックスは表示されず、プレートだけが表示されます。タイトルだけで NPC にラベルを付けられます。
  • label は必須ですが、空白文字だけでもかまいません。label: ' ' にすると、テキストなしの色付きプレートが表示されます(スペースを増やすとプレートの幅が広がります)。たとえば、タイトルのないチームカラーのバッジを表示できます。
  • labelColor、backgroundColor、borderColor は省略可能な Color3 フィールドで、省略した場合はネイティブのネームタグの色が使われます。
  • モバイルでは実装されていません。モバイルのプレイヤーにはプレートなしで NPC が表示されるため、タイトルが重要な場合は、別の方法(例:TextShape)で表示してください。

フィールド一覧、例外ケース、注意点については、player-avatar スキルの「Custom Nametag Plates」と {baseDir}/../player-avatar/references/avatar-nametag.md を参照してください。

AvatarShape NPC の表情を再生する
AvatarShape.getMutable(npc).expressionTriggerId = 'wave'
AvatarShape.getMutable(npc).expressionTriggerTimestamp = 1

expressionTriggerId には、組み込みエモート名('robot'、'wave'、など)または シーンエモートの .glb パス(例:'animations/Snowball_Throw_emote.glb')を指定できます。triggerSceneEmote で使うものと同じ、_emote.glb で終わるファイルです。これを使うと、NPC でカスタムエモートを再生できます(AvatarShape エンティティには triggerSceneEmote に相当するものがありません)。プレイヤー自身のエモートを順番に再生するには、expressionTriggerId = userData.emotes[i] を設定します。 NPC のポーズ(座る、アイドル状態のバリエーション)用にカスタムクリップを作成する前に、まず組み込みエモートを確認してください。 Explorer には 21 種類のエモートが含まれており、expressionTriggerId では名前で指定できます。座るエモートはループ再生され、再トリガーしなくてもポーズを保ちます(ワールド内で AvatarShape、SDK 7.30.1、2026-10-12 に確認済み)。ID とループフラグを含む全リスト(唯一の参照元):{baseDir}/../avatar-animations/references/embedded-emotes.md。組み込みの座るエモートのほうが、手作りのエモートより自然に見えることがよくあります。次に、Creator Hub の Avatar Animations パックには、ダウンロード可能な _emote.glb 形式のクリップが 45 種類あります(スツール、クッション、ビーズクッション、縁、チェス、釣り、飲み物、クラフトなど)。これらは expressionTriggerId でファイルパスを指定して再生でき、プレビュー付きのカタログが {baseDir}/../avatar-animations/references/animation-catalog.md にあります。どちらも適さない場合に限り、カスタムクリップ(avatar-animations)を作成してください。

注:同じ expressionTriggerId の値を連続して再生するには、expressionTriggerTimestamp を更新する必要があります。毎回異なる ID を設定すれば、タイムスタンプに触れずに再生できます。

カスタムクリップは AvatarShape 上でループ再生されません。 Explorer は、クリップ自体のフラグに関係なく、AvatarShape に対するすべてのシーンエモート要求を loop: false で生成します(AvatarShapeHandlerSystem には「SDK AvatarShapes でのシーンエモートのループ再生は、まだサポートされていません」というコメントがあります — unity-explorer main 2026-10)。ポーズを維持できるのは、前述の組み込みループエモートだけです。エモートを作成すること、triggerSceneEmote({ loop: true }) で実際のプレイヤーにループ再生させること、AvatarShape でトリガーすることは、それぞれ異なる再生経路です。カスタムポーズを維持するには、長い静止クリップを作成し、終了直前に expressionTriggerTimestamp を更新して再トリガーしてください。継ぎ目が目立たないか、ワールド内で確認してください。再トリガー時に目に見えるカクつきが起きないかは未確認です。

AvatarShape を隠しても、位置は変わりません。 視界の外(たとえば地下 60 m)に移動させると、Explorer が AvatarShape の移動を補間するため、目に見える状態でそこまで歩いていきます(「NPC を動かすとエモートがキャンセルされます」を参照)。うまくいった方法は、Transform.scale をほぼゼロ(0.001)にして、後で元に戻すことです(ワールド内、Unity Explorer 0.186、2026-10。AvatarShape の VisibilityComponent は未テスト)。プリミティブ、GLB、AvatarShape、ネームタグ、ライト、コライダーでは、それぞれ非表示にする方法が異なります。「このエンティティを隠す」方法ひとつですべてに対応できると思い込まないでください。

NPC を動かすとエモートがキャンセルされます。 エモートの再生中に AvatarShape の Transform.position を書き換えたり、Tween を実行したりすると、エモートが中断され、アバターは歩行サイクルに戻ります。これは、Explorer が毎フレームの Transform の差分から、各フレームで移動ブレンドを再計算するためです(SDKAvatarShapesMotionSystem.UpdateAnimations。unity-explorer Explorer/Assets/DCL/Character/CharacterMotion/Systems/SDKAvatarShapesMotionSystem.cs と照合して確認済み。症状はオーナーから報告、動作はワールド内で確認済み。Unity Explorer v0.186.0-alpha、SDK 7.30.1、2026-10-10)。エモート中に NPC を移動させる(叫びながら床に沈む、横に崩れ落ちるなど)には、_emote.glb 内のルートまたは腰の動きとして、クリップに移動を組み込んでください。Explorer はそれを AvatarShape 上で再生します(ワールド内での観察、同一ビルド/日付)。

NPC ではマスク付きエモートを使用できません。 mask?: AvatarMask(上半身のみ)は TriggerSceneEmoteRequest、つまりプレイヤー自身の triggerSceneEmote にしかありません。PBAvatarShape にマスクフィールドはないため、expressionTriggerId は常に全身に適用されます。@dcl/protocol の decentraland/kernel/apis/restricted_actions.gen.d.ts と @dcl/ecs の decentraland/sdk/components/avatar_shape.gen.d.ts(7.30.1)で確認済みです。

同じセッション中にオーナーから報告されたものの、未検証です。これを前提に設計する前に確認してください。シーンの _emote.glb を指す最初の expressionTriggerId では、NPC 上で目に見える遅延が発生します(クリップの読み込み)。回避策として、シーン開始時に画面外の AvatarShape で一度再生してキャッシュを温める方法を試しましたが、効果は未確認です。

プレイヤーのコピー

プレイヤーにそっくりな姿――「あなた」が並んだり、店でクローンがポーズを取ったり、双子が手を振り返したりするものは、ライブの getPlayer() データを渡した AvatarShape です(フィールドの対応付けは player-avatar の上記クローン項目を参照)。そのため、プレイヤーが着ているものをそのまま身に着けます。

AvatarShape はフレームごとに操れません。 移動はエンジンによって制御されます(SDKAvatarShapesMotionSystem.cs、unity-explorer main 2026-10)。毎フレーム、現在位置から設定済みの Transform.position に向かって 4 m/s で歩き、目標までの距離が 1.5 m 以上になると、さらに速くなります。速度は毎フレーム 4 にリセットされ、100·dt ずつ加算されるため、フレームレートに依存します(60 fps では約 5.7 m/s、30 fps では 7.3 m/s、名目上の 10 m/s になるのは約 17 fps 未満のみです)。また、移動方向を向くように回転します(slerp、6.5/s)。設定済みの回転が適用されるのは、到着した後だけです。エンティティの位置に Tween を設定するとエンジンによる移動が抑制され、回転に Tween を設定すると自動回転が抑制されますが、エンティティが保持できる Tween は 1 つだけなので、両方を制御することはできません。プレイヤーのミラー位置を渡すと、分身は歩く速度で遅れてついてきて、プレイヤーが後ろ向きに進むと回転してしまいます。これは、ミラー分身に関する所有者からの報告です。2026-10-09 に、毎フレームの Transform 書き込みと、ミラー位置での速度を指定した継続的な移動 Tween の両方を試した後、「遅れがあるとすごく違和感がある」「後ろ向きに歩くと逆を向いてしまう」と報告されています。ミラー分身、影、リプレイ用のゴーストなど、毎フレームプレイヤーのポーズと一致させる必要があるものには、Animator を備えたリグ付きヒューマノイド GLBを使い、プレイヤーの速度に応じてアイドル/歩行クリップを切り替え、毎フレーム Transform を直接書き込んでください。プレイヤーの見た目は反映されませんが、それがトレードオフです。クリップ付きのリグ付きヒューマノイドについては、add-3d-models → references/model-catalog.md の、[anim: …] に idle と walk がある character/… の行を参照してください(例:base-character、berserker-npc。arissa3 には idle と running があります)。mannequin はクリップのない展示用ダミーを返します。

コピーの見た目に違いを出せるかどうかは、カタリストから確認できるウェアラブルのカテゴリによって決まります。

https://peer.decentraland.org/lambdas/collections/wearables?wearableId=<urn>&wearableId=<urn>

各エントリは data.category、data.hides、data.replaces、および data.representations[].overrideHides/.overrideReplaces を返します(2026-10-12 に curl で確認:urn:decentraland:off-chain:base-avatars:red_bandana → category: "tiara"、replaces: ["mask","hat","helmet"])。これを使って、実際に表示されるアイテムの差し替えを選んでください。

同じセッション中に所有者から報告された内容であり、ソースでは未検証です。手がかりとして扱い、これを前提に設計する前にワールド内で確認してください。

  • 着用中のスキンは全身を置き換えるため、追加したウェアラブル、髪、肌の色、目の色はコピーに表示されません。
  • ヘルメットは、髪、ヘッドウェア、イヤリング、アイウェア、ひげを隠します。
  • AvatarShape では、隠されたウェアラブルを強制的に描画できません。

常に表示される代替案: コピーの Transform スケールを変えます。たとえば、背を高く 1.07、低く 0.93、幅広く 1.10 にします。ウェアラブルに左右されず、並んだときにすぐ見分けられます。

マネキンモード(身体なしでウェアラブルを表示)

店舗やウェアラブルの展示に便利です。

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

AvatarShape の全フィールドリファレンス、ボディシェイプの URN、よく使われるベースウェアラブルの URN については、{baseDir}/../player-avatar/references/avatar-apis.md を参照してください。


AvatarShape NPC へのインタラクティブ機能の追加

AvatarShape エンティティはクリックできません。コライダーがないため、ポインターイベントを直接登録できません。プレイヤーが AvatarShape NPC とインタラクションできるようにするには、次のいずれかの方法を使ってください。

オプション A — クリック操作用の MeshCollider を追加する

同じエンティティに不可視のコライダーを追加して、pointerEventsSystem がクリックを検出できるようにします(add-interactivity スキルを参照)。

import { MeshCollider, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'

// invisible cylinder collider roughly matching avatar size.
// Pointer clicks require the CL_POINTER collider layer; MeshCollider defaults to
// all layers (which includes CL_POINTER), so an explicit layer is optional here.
// If you set a layer, include ColliderLayer.CL_POINTER or clicks won't register.
MeshCollider.setCylinder(npc)

pointerEventsSystem.onPointerDown(
  { entity: npc, opts: { button: InputAction.IA_POINTER, hoverText: 'Talk' } },
  () => {
    console.log('Player clicked NPC')
  }
)
オプション B — 近接ベースのインタラクション

クリックを求めず、プレイヤーが NPC の近くを歩いたときにインタラクションを開始します。

import { engine, Transform } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'

const INTERACT_DISTANCE = 4

engine.addSystem(() => {
  const playerPos = Transform.get(engine.PlayerEntity).position
  const npcPos = Transform.get(npc).position
  const dist = Vector3.distance(playerPos, npcPos)
  if (dist < INTERACT_DISTANCE) {
    // start dialogue or other interaction
  }
})

サンプルシーン

AvatarShape アプローチを検証するためのエンジンチームのテストシーンです(実際のエンジンでテスト済み):

  • 4,20-avatar-shape — ベースアバターのウェアラブルとオンチェーンコレクションのウェアラブル URN を使って AvatarShape NPC をスポーンします。name: '' を指定すると、ネームタグが非表示になります。
  • 4,19-avatar-shape-movement — Transform の変更(ターゲットまで歩行)と Tween/TweenSequence による AvatarShape の移動、および id を介した NPC の手への AvatarAttach ボックスの取り付けを確認できます。
  • 4,21-avatar-shape-emotes — getPlayer().wearables/.emotes を使ってローカルプレイヤーを NPC に複製し、expressionTriggerId を通じてエモートを順番に再生します。
  • 4,22-avatar-shape-scene-emotes — expressionTriggerId に _emote.glb のパスを設定して、カスタムのシーンエモートを NPC で再生します。

これらは AvatarShape のみを使用するシーンであり、dcl-npc-toolkit は使用しません。

コメント