mcpField
S
script-components

script-components

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

script-components について

Creator Hubのシーン内エンティティ向けに、TypeScriptのスクリプトコンポーネントを書く方法を説明するスキルです。ファイル配置、クラス構造、設定可能なパラメーター、実行順序、アセット参照、アクション連携を扱います。

主な機能

  1. 01スクリプトクラスとライフサイクルメソッドを定義
  2. 02エディターでエンティティのパラメーターを設定
  3. 03priorityで更新順序を制御
  4. 04layout JSONのパラメーターを解決
  5. 05アセットと子エンティティを参照
  6. 06他のシーンアイテムにアクションを公開

活用シーン

  1. 01Creator Hubエンティティ用のスクリプトを作成
  2. 02シーンアイテムごとに再利用可能なスクリプトを設定
  3. 03スクリプトの実行順序を設定
  4. 04スクリプトのアクションを他のスマートアイテムに接続

MCP 対応

`set_component`や`attach_script`などのCreator Hub MCPツールを使って、スクリプトをプログラムから設定できると説明しています。シーンが開いている場合は、スクリプトファイルの作成後に`attach_script`を使うことを推奨します。

インストール

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

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

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

詳細

Creator Hub 向けスクリプトコンポーネントの作成

このドキュメントでは、Creator Hub シーン内のエンティティにある Script コンポーネントで使用する .tsx ファイルの作成方法を説明します。これらのスクリプトは、個々のエンティティにアタッチされる自己完結型のクラスとして実行されます。

スクリプトファイルの配置場所

Script コンポーネントが参照するスクリプトファイルは、必ず assets/ フォルダー内に配置してください。assets/scripts/ を使用します(例:assets/scripts/MyScript.ts)。ルート階層の scripts/ フォルダーには配置しないでください。

理由:シーンの tsconfig.json には src/**/* と assets/**/* のみが含まれています。

"include": ["src/**/*.ts", "src/**/*.tsx", "assets/**/*.ts", "assets/**/*.tsx"]

ルート階層の scripts/ フォルダー内のファイルはこれらの glob の対象外となるため、TypeScript の型チェッカー(およびビルドの型チェック段階)でチェックされません。Script コンポーネントの path フィールドはプロジェクトルートからの相対パスとして解決されるため、実際のファイルの場所と一致させる必要があります。たとえば、"path": "assets/scripts/SitChair.ts" のように指定します。

スクリプトの構造

各スクリプトは、次の要素を持つ単一のエクスポートされたクラスです。

  • 設定可能なパラメーター(Creator Hub の UI に表示されます)を受け取る コンストラクター。
  • シーンの読み込み時に一度だけ呼び出される任意の start() メソッド。
  • 毎フレーム呼び出される任意の update(dt: number) メソッド(約 30 FPS。「スクリプトの実行タイミング」を参照してください。実行順序については、デフォルトではすべての通常のシステムの後にスクリプトが更新されます)。

コンストラクターの最初の 2 つのパラメーターは、必ず public src: string と public entity: Entity にしてください。削除したり、順序を入れ替えたりしないでください。

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

export class MyScript {
  constructor(
    public src: string,
    public entity: Entity,
    public speed: number = 1
  ) {}

  start() {
    console.log('Script started on entity:', this.entity)
  }

  update(dt: number) {
    const transform = Transform.getMutable(this.entity)
    transform.rotation.y += this.speed * dt
  }
}

スクリプトの実行タイミング — priority フィールド(重要)

Creator Hub の Script コンポーネントには priority フィールドがあります(コンストラクターパラメーターとは別のもので、クラス内ではなくコンポーネントの UI で設定します)。デフォルト値は 0 であり、これには一見わかりにくい影響があります。

  • デフォルトの優先度 0 では、スクリプトは各フレームの最後に実行されます。 ビルド時に @dcl/sdk-commands はすべてのスクリプトを priority の値ごとにグループ化し、グループごとに engine.addSystem(updateLoop, Number(priority)) を使って 1 つのエンジンシステムを登録します。エンジンシステムは優先度の高い順に実行されるため(通常のシステムでは 100000、UI では 100000 が使われます)、優先度 0 はほかのすべての処理の後に実行されます。
  • 同じ priority を持つすべてのスクリプトは、1 つのシステムコールバックを共有し、その内部で順番に実行されます。 そのため、1 つの重いスクリプトの update() によって、同じ優先度グループに属するほかのすべてのスクリプトのそのフレームでの実行が遅れます。
  • スクリプトの update() を通常のシステムより前に実行するには、Script コンポーネントの priority を引き上げます(例:1000000)。これにより、そのスクリプト専用のシステムも割り当てられます。これは、結果を読み取るシステムより前に、update(dt) 内で物理ライブラリ(cannon.js)をステップ実行する場合の方法です。別個の物理ループはありません。

priority の値が大きいほど、実行順序は早くなります。「優先度 1 が最初」という想定とは逆です。根底にあるエンジンのルールについては、scene-runtime スキルの「システムの実行順序と優先度」セクションを参照してください。

コンストラクターパラメーター

コンストラクターで宣言されたパラメーターは Creator Hub の UI に表示され、エンティティごとに設定できます。使用できる型は次のとおりです。

型エディター UI
stringテキストフィールド
number数値フィールド
booleanチェックボックス
Entityエンティティピッカー(シーン内の別のエンティティを選択できます)
ActionCallbackアクションピッカー。「ActionCallback パラメーター」を参照してください
Slider<Min, Max, Step>スライダーと数値ボックス。以下を参照してください

public と private のどちらのパラメーターも Creator Hub に表示されます。コード内で値にアクセスするには、this.<paramName> を使用してください。

Slider<Min, Max, Step> — スライダーで編集する数値
public speed: Slider<0, 10, 0.5> = 1
  • 実行時の値は通常の number です — 型は純粋なエイリアス(type Slider<Min, Max, Step = 1> = number)なので、コードやシリアライズに変更はありません。エディターにスライダーを表示するよう指示するだけです。

  • Step のデフォルト値は 1 です — Slider<0, 100> のステップ幅は 1 です。

  • 無効な宣言は、エラーにならず通常の数値フィールドにフォールバックします(エラーは発生しません):境界値が指定されていない、min >= max、または step <= 0 の場合です。負の境界値や負のデフォルト値は問題ありません。

  • デフォルト値が範囲外の場合は範囲内に収まるよう調整されます。その後、境界値を広げたり狭めたりした場合も、スクリプトの再読み込み時に保存値が新しい範囲内に再調整されます。

  • @dcl/asset-packs からエクスポートされています。ActionCallback と同じ場所です。シーンからはまだインポートできないため、Creator Hub のスクリプトテンプレートではローカルエイリアスを宣言しています。この行をスクリプトファイルにコピーしてください。

    // A number edited with a slider in the Creator Hub UI: Slider<min, max, step>
    type Slider<Min extends number, Max extends number, Step extends number = 1> = number
    

    (最終的には ~sdk/script-utils に配置される予定です。それまでは、ローカルエイリアスがサポートされている方法です。)

デフォルト値

スクリプトがそのまま動作するよう、デフォルト値を指定してください。

constructor(
  public src: string,
  public entity: Entity,
  public radius: number = 5,
  public label: string = 'Hello',
  public enabled: boolean = true,
) {}
オプションのパラメーター

ユーザーが空欄のままにできるパラメーターには ? を使用してください。

constructor(
  public src: string,
  public entity: Entity,
  public targetEntity?: Entity,
  public message?: string,
) {}
パラメーターのツールチップ

コンストラクターの直前に JSDoc コメントブロックで @param アノテーションを追加すると、Creator Hub の UI にツールチップが表示されます。

/**
 * @param startDate - The start date of the campaign in YYYY-MM-DD format
 * @param endDate - The end date of the campaign in YYYY-MM-DD format
 * @param wearableYOffset - How many meters above the ground the wearable should be displayed
 */
constructor(
  public src: string,
  public entity: Entity,
  public startDate?: string,
  public endDate?: string,
  public wearableYOffset: number = 0.5,
) {}
layout フィールド — 実行時のパラメーターの保存と解決方法

asset-packs::Script コンポーネントの値は { value: [{ path: string, priority: number, layout?: string }] } の形式です。layout フィールドは JSON 文字列です。

{"params":{"paramName":{"type":"string|number|boolean|action","value":<value>}}}

layout には actions(@action メソッド由来)と events(@event タグ由来。下記参照)も含まれ、解析に失敗した場合は error 文字列も含まれます。コンストラクターに渡されるのは params だけですが、layout を書き換える際は、その他のキーも必ず保持してください。保持しないと、それらのキーが有効にするエディター機能が表示されなくなります。

実行時には、@dcl/sdk-commands/dist/logic/runtime-script.js が次のようにパラメーターを解決します。

  1. JSON.parse(layout) で ScriptLayout オブジェクトを取得します。
  2. Object.values(layout.params) でパラメーター値を挿入順に取り出します。
  3. 値をコンストラクターに位置引数として展開します:new ScriptClass(src, entity, ...params)。

重要な注意点 — パラメーターは名前ではなく位置で決まります。 layout JSON のキーの挿入順は、コンストラクターのパラメーター順(src と entity の後)と一致している必要があります。後のパラメーターだけを設定するために前のパラメーターを省略することはできません。src/entity 以降のすべてのパラメーターを順番どおりに含める必要があります。

type: "action" のパラメーターは value: {entity: Entity, action: string} を持ち、createActionCallback を介して ActionCallback 関数に変換されます。この関数が呼び出されると、getActionEvents(entity).emit(action, {}) が実行されます。

これは、asset-packs::Script に対する set_component を使って、エージェントや MCP ツールからスクリプトのパラメーターをプログラムで設定する場合に関係します。その際は、順序どおりのパラメーターオブジェクト全体を指定する必要があります。(シーンが Creator Hub で開かれている場合は、Creator Hub の MCP attach_script ツールを使用することをおすすめします。スクリプトファイルを書き込んでから、パスを指定して呼び出すと、コンポーネントが作成されます。詳しくは creator-hub-mcp を参照してください。)コンストラクターのパラメーターが 14 個(src/entity の後に続くもの)あるスクリプトの layout 例:

{"params":{"colorA":{"type":"string","value":"#ff33e6"},"colorB":{"type":"string","value":"#33e6ff"},"rate":{"type":"number","value":35},"lifetime":{"type":"number","value":2.5},"sizeMin":{"type":"number","value":0.3},"sizeMax":{"type":"number","value":0.6},"speedMin":{"type":"number","value":2.5},"speedMax":{"type":"number","value":4},"gravity":{"type":"number","value":-0.3},"spin":{"type":"number","value":0},"shape":{"type":"string","value":"sphere"},"shapeSize":{"type":"number","value":0.4},"maxParticles":{"type":"number","value":150},"startPlaying":{"type":"boolean","value":true}}}

検証済みの箇所: @dcl/sdk-commands/dist/logic/runtime-script.js — resolveScriptParams は Object.values(params).map(...) を使用し、その結果を new ScriptClass(src, entity, ...params) として展開します。

this.src を使ったアセットの参照

スクリプトが、コード経由でのみ読み込まれる追加アセット(音声ファイル、テクスチャ、モデルなど)を使用する場合、それらはカスタムアイテムのフォルダーに自動的には含まれません。それらのファイルは手動で追加する必要があります。

アイテムが別のシーンで使用されると実際のファイルの場所が異なる場合があるため、バンドルされたアセットファイルへのパスを作成する際は、必ず this.src を使用してください。

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

start() {
  AudioSource.create(this.entity, {
    audioClipUrl: this.src + '/sounds/click.mp3',
    playing: false
  })
}

子エンティティを参照する

同じカスタムアイテムに属するエンティティを、Entity 入力パラメーターとして渡さないでください。エンティティ ID はシーン間で安定しません。開発用シーンで有効なエンティティ ID が、ユーザーのシーンには存在しない場合があります。

代わりに、実行時にエンティティ階層を反復処理し、Name コンポーネントの値を部分一致で照合して、子エンティティを見つけてください(例:.startsWith(...) または .includes(...))。

ベストプラクティス:子の名前全体ではなく、その一部に一致させてください。また、インスタンスごとのコンストラクターパラメーターは使わないでください。 ユーザーがスマートアイテムを複製したり、シーンに複数のコピーを配置したりすると、Creator Hub エディターが複製された子エンティティに自動で番号を付けます(例:「Sit Spot」、「Sit Spot 2」、「Sit Spot 3」など)。部分一致(name.includes('Sit Spot'))なら、インスタンスごとの設定なしで、すべてのコピーから一致する子を自動的に見つけられます。

  • 名前全体の完全一致では、名前に自動で接尾辞が付くため、最初の複製以降はすべて失敗します。
  • 子の名前に対するコンストラクターの string/Entity パラメーターを使うと、ユーザーはコピーごとに手動で名前を変更するか、再配線する必要があり、再利用可能なスクリプトアイテムの目的に反します。

以下の例では、まさにこの理由から、部分一致形式の照合である .startsWith('Needle') を使用しています。

import { engine, Entity, Transform, Name } from '@dcl/sdk/ecs'

export class ClapMeter {
  private needleEntities: Entity[] = []

  constructor(
    public src: string,
    public entity: Entity,
  ) {}

  start() {
    for (const [childEntity, transform] of engine.getEntitiesWith(Transform)) {
      if (transform.parent === this.entity) {
        const nameComponent = Name.getOrNull(childEntity)
        if (nameComponent && nameComponent.value.startsWith('Needle')) {
          this.needleEntities.push(childEntity)
        }
      }
    }
  }
}

このパターンを使うと、スクリプトの移植性が保たれます。子エンティティの名前に想定される部分文字列が含まれていれば、どのシーンでも、いくつ複製されても動作します。

アクションを定義する(@action)

シーン内のほかのアイテムから呼び出すと便利な関数がスクリプトにある場合は、メソッドの直前に @action タグを含む JSDoc コメントブロック(/** ... */)を追加して、その関数をマークしてください。次に、エンティティに Action コンポーネントを追加し、対応するアクションを定義します。これにより、ほかのスマートアイテム(例:ボタン)から、このアクションを選択して実行できるようになります。

重要:JSDoc タグの @action のみを使用してください。デコレーター構文(メソッドの上に置く @action())は絶対に使用しないでください。Creator Hub のパーサーにはデコレーター用プラグインがないため、デコレーターを使うと解析に失敗し、すべてのパラメーターとアクションが UI から黙って消えてしまいます。

@action タグの前に任意で説明行を追加すると、Creator Hub UI にアクションの説明として表示されます。

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

export class TreasureChest {
  private isOpen = false

  constructor(
    public src: string,
    public entity: Entity,
  ) {}

  /**
   * Opens the chest
   * @action
   */
  open() {
    if (this.isOpen) return
    this.isOpen = true
    console.log('Chest opened!')
  }

  /**
   * Closes the chest
   * @action
   */
  close() {
    if (!this.isOpen) return
    this.isOpen = false
    console.log('Chest closed!')
  }
}

@action JSDoc タグを使うと、open と close が Actions コンポーネントのドロップダウンに表示され、ほかのスマートアイテムやスクリプトから実行できるようになります。

ActionCallback パラメーター

~sdk/script-utils の ActionCallback 型を使うと、エディターで設定したアクションをスクリプトのコールバックとしてユーザーが割り当てられるようになります。ユーザーは Creator Hub UI で、任意のアイテムの任意のアクションをそのコールバックに割り当てられます。

import { Entity } from '@dcl/sdk/ecs'
import type { ActionCallback } from '~sdk/script-utils'

export class Padlock {
  constructor(
    public src: string,
    public entity: Entity,
    public onUnlock: ActionCallback,
  ) {}

  /**
   * @action
   */
  solve() {
    this.onUnlock()
  }
}
未配線の ActionCallback パラメーターは undefined になります(sdk-commands の修正 8da580d5)

ユーザーがエディターで一度も割り当てていないオプションの ActionCallback は、undefined に解決されるようになりました。そのため、次のような分かりやすいガードが機能します。

constructor(
  public src: string,
  public entity: Entity,
  public onUnlock?: ActionCallback,   // ActionCallback | undefined
) {}

solve() {
  if (this.onUnlock) this.onUnlock()   // safe: no-op when unwired
}

以前は、未割り当てのパラメーターは真偽値として true になるプレースホルダー({ entity: 0, action: '' } を呼び出し可能な値でラップしたもの)に解決されていたため、if (this.onUnlock) は常に通過し、呼び出すと console.error が記録されていました。その回避策として entity !== 0 を確認したり、エラーを無視したりしていた場合は、削除してください。古い @dcl/sdk-commands では従来の動作が残っているため、真偽値のガードだけでは安全策になりません。

何も配線されていなくても動作させる場合は、これらのパラメーターをオプション(?)として宣言してください。オプションでない ActionCallback は、割り当てがないとアイテムが正常に動作しないことを意味します。

コードからほかのスクリプトを呼び出す

~sdk/script-utils のランタイムユーティリティを使って、ほかの Script コンポーネントのインスタンス上のメソッドを呼び出します。

import {
  callScriptMethod,
  getScriptInstance,
  getAllScriptInstances,
  getScriptInstancesByPath
} from '~sdk/script-utils'

callScriptMethod(entity, 'assets/scripts/Padlock.ts', 'solve', 123)

const instance = getScriptInstance(entity, 'assets/scripts/Padlock.ts')
const allOnEntity = getAllScriptInstances(entity)
const allByPath = getScriptInstancesByPath('assets/scripts/Padlock.ts')

スマートアイテムが Script ベースに移行しました(creator-hub 658f4daf、PR #1354)

カタログにあるスマートアイテムの大半は、アクション/トリガーによるノーコードグラフから、@action タグ付きメソッドを使う asset-packs::Script コードへ移行しました。これは、このスキルで説明しているものと同じ仕組みです。スマートアイテムを調べたり拡張したりする際に、これが意味することは次のとおりです。

  • 移行されたアイテムには asset-packs::Triggers がありません。 トリガーグラフを探さないでください。動作は、Script コンポーネントが参照する .ts ファイル(Door.ts、Seat.ts、Button.ts、Lever.ts、Bell.ts、Teleport.ts、Sign.ts、…)にあります。
  • 残っている Actions は call_script_method ポインターです。 古いパブリックアクション名が維持されるため、「Open」/「Close」/「Open or Close」/「Sit Here」を参照していた既存のシーンも引き続き動作します。jsonPayload は {"scriptPath": "{assetPath}/Door.ts", "methodName": "open", "params": {}} です。
  • Script の params が設定 UI です。 移行されたアイテムでは inspector::Config パネルが削除されています。移行されなかったアイテムには引き続き残っています。具体的には、ライト、スクリーン/メディア系、camera、admin_toolkit、image、nft、text、およびヘルス/戦闘系です。(Config がすべてのアイテムで削除されたとは書かないでください。そうではありません。)
  • 「X が起きたとき」のフックは、任意の ActionCallback params です。 提供されているアイテムで実際に使われている名前は、onClick、onActivate/onDeactivate、onReachStart/onReachEnd(最も一般的な2つで、可動プラットフォーム系にあります)、onRing(ベル)、onOpen/onClose(開閉サイン。ドアではありません)です。このスキルの ActionCallback セクションの説明に従って接続してください。
  • 状態は、core-schema::Sync-Components に記載された同期対象の asset-packs::States コンポーネントに保持されます({"componentIds": ["asset-packs::States"]})。たとえば、ドアは start() で value: ['Open','Closed'], defaultValue: 'Closed' の States を作成します。椅子は、各座席スポットに ['Free','Taken'] を保持します。状態の読み書きには、スクリプトのインスタンスフィールドではなく States を使ってください。そうしないと状態がレプリケートされません。

移行されていないもの(引き続き Actions/Triggers を使用):click_area、audio_stream、ヘルス/戦闘系(first_aid、healing_pad、health_bar、respawn_pad、robot、spikes、sword、barrel、wooden_wall)、および camera(Actions + Config、Triggers なし)。video_player は [DEPRECATED](deprecated カテゴリーに移動済み)です。代わりに、いずれかの動画スクリーンアイテムを使用してください。

新しいアイテムは、最初から Scripts として作成されます。 locomotion_settings(「Locomotion Settings」、utils、creator-hub cb097fff)は現在の参考例です。独自のものを書く前に読む価値があります。SDK のデフォルト値を初期値とする9つの Slider<Min,Max,Step> params と、それぞれに付けられた @param ツールチップ、engine.PlayerEntity にコンポーネントを書き込む start()(エンティティごとではなくグローバルな効果です。アイテムはワールド内に一切表示されません)、2つの @action メソッド(apply は値を書き直し、restoreDefaults は deleteFrom を呼び出します)、さらに、ワールド内では不可視のままビューポートで選択できるようにするエディター専用の asset-packs::Placeholder の立方体を備えています。Actions、Triggers、Config、States はありません。設定内容については player-avatar スキルを参照してください。

新しい参考例(packs/smart_items/assets/ 内。以下の動作は各アイテムの変更説明に基づくもので、ワールド内で再確認したものではありません):floor_pressure_button/FloorButton.ts — マルチプレイヤーで共有するボタンです。トリガー領域は円盤と同じ大きさです。デフォルトの all players モードでは、誰かが立っている間ボタンが押された状態になり、On Press/On Release アクションは、変化を起こしたプレイヤーのクライアントで一度だけ実行されます(最初に押したとき/最後に離れたとき)。press()/release() による押下状態は、asset-packs::States を通じて同期されます。my player にすると、プレイヤーごとのプライベートなボタンになります。water_plane/Water.ts — 実行時に、半透明の MeshRenderer レイヤーを5枚重ねて水を構築します(GLB はエディター専用の Placeholder です)。トリガー領域を再調整するのはスケール変更時のみで、シーン座標で水しぶきを生成します。lab_stair_platform/PistonPlatform.ts — startState は文字列リテラルのユニオン型です(エディターでは列挙型のドロップダウンになります)。Sync-Components には States のみを指定します。これは、update() が同期された状態から各クライアントで Animator/AudioSource を毎回再生するためです。 world_teleport は削除され、world パラメーターが追加された teleport に統合されました。 world が設定されている場合はそちらが優先されます。空でない world を指定するとレルムが切り替わり、それ以外の場合は x/y 座標にテレポートします。world_teleport を参照しているシーンや手順を更新してください。

[FIXED in @dcl/sdk 7.30.1] ライブプレビュー中にスマートアイテムを初めて追加する際、再起動が不要になりました。 7.30.0 以前では、esbuild コンテキストの作成前に isEditorScene() が一度だけ実行され、その判定結果が静的エントリーポイントに焼き込まれていました。npm run start/Creator Hub のプレビューがすでに実行中の状態で、シーンに初めてスマートアイテム(または初めての asset-packs::Script)を追加すると、アセットパックのブートストラップなしでアイテムの Script を実行するバンドルが再ビルドされました。そのため getComponents が Component asset-packs::Actions not found をスローし、プレビューを再起動するまで問題が解消されませんでした。js-sdk-toolchain 52fa56cf(PR #1644、タグ 7.30.1)では、ウォッチによる再ビルドのたびに isEditorScene() が再評価されます。

古い「プレビューを再起動する」という回避策を使っている場合は、7.30.1 以降では不要なので削除してください。ウォッチによる再ビルドで自動的に復旧します。古いバージョンを固定している場合は、引き続き再起動が解決策です。また、このエラーメッセージが手がかりになります。これはバンドルの不具合であり、複合エンティティ内にコンポーネントがないという問題ではないため、エンティティツリーで asset-packs::Actions を探さないでください。公開されている ~sdk/script-utils のインターフェイス(ActionCallback、getScriptInstance、getAllScriptInstances、callScriptMethod)は、この修正による変更はありません。

trigger_area も Script アイテムになりました。 以前の Actions/Triggers の trigger_area アセットは creator-hub e43441b5 で削除され、スクリプトベースの trigger_area_prompt(表示名 「Trigger Area」、ID e9fa0eab-44de-4efe-af77-a71171a1a73f)のみが残っています。暫定的な「Trigger Area (Script)」というラベルは廃止されました。独自の検知機能を書く前に、このアイテムの TriggerArea.tsx を読んでください。以下で説明する @event パターンの参考になります。TriggerArea コンポーネント自体については add-interactivity を参照してください。

@event — リアクションスクリプト用のフックを宣言する

クラス(またはstart()関数)のJSDocには、@paramや@actionと並べて@event <name>タグを記述できます。

/**
 * @event enter
 * @event exit
 */
export class TriggerAreaDetector { /* … */ }
  • インスペクターはこれらを解析し(ScriptInspector/parser.ts → extractEvents)、paramsやactionsと並べて、Scriptのlayout JSON内にevents: string[]キーとして保存します。eventsが空でなければ、インスペクターにリアクションセクションが表示され、イベントごとに1つのプロンプトボタンが用意されます。クリックすると、自然言語の文があらかじめ入力された状態でAIアシスタントが開きます。ボタンと入力済みの文が自然な表現になるよう、認識される9つのイベント名のいずれかを使用してください(creator-hub 0.52.0で確認済みのScriptInspector.tsx内のEVENT_LABEL / EVENT_CLAUSE):
@event名ボタンのラベル入力される節
enterプレイヤーが入ったとき…when a player enters <name>
exitプレイヤーが出たとき…when a player leaves <name>
clickクリックされたとき…when <name> is clicked
open / close開いたとき… / 閉じたとき…when <name> opens / closes
activate / deactivate有効になったとき… / 無効になったとき…when <name> is activated / deactivated
toggle切り替えられたとき…when <name> is toggled
unlockロックが解除されたとき…when <name> is unlocked

認識されない名前も機能しますが、汎用のWhen <event>… / when <name> fires "<event>"に置き換えられるため、クリエイターには不自然な表現になります。@eventを宣言するだけで、ノーコードのリアクションUIが使えるようになります。

  • タグは、エクスポート文、クラス宣言、コンストラクターのいずれかのJSDocで受け付けられます。
  • 名前は[A-Za-z0-9_-]+に一致し、重複はまとめられます。

これによって可能になる、検出とリアクションの分離。 SDKでは、(entity, event)の組み合わせごとにコールバックを1つだけ保持するため、同じエンティティに対して2つ目のスクリプトがtriggerAreaEventsSystem.onTriggerEnterを呼び出すと、最初のコールバックが通知なく置き換えられます。提供されているパターンではこれを回避します。1つのスクリプトがSDKコールバックを所有してonEvent(name, fn)を公開し、同じエンティティ上のリアクションスクリプトは~sdk/script-utilsのgetAllScriptInstances(entity)を使ってそのスクリプトを見つけ、登録します。リアクションスクリプトからtriggerAreaEventsSystemに直接触れてはいけません。TriggerArea.tsxでは、検出側がすでに領域内にいる対象について、後から登録されたリスナーにもenterを再通知します。また、「領域内にいる間」のロジック用にisInside()を公開します。

⚠️ layout JSONを手作業で書き換える場合は、編集のたびにeventsも引き継いでください。 paramsだけからレイアウトを再構築するとeventsが失われ、リアクションセクションが完全に消えてしまいます。これはcreator-hub e43441b5で修正されたバグそのものです(Trigger Areaのshapeを編集すると、プロンプトフィールドまで消えていました)。既存のレイアウトを展開し、変更するパラメーターだけを上書きしてください。

知っておくとよい動作の変更点: 座る人が1.5 m以上離れると、座席は自動的に空きます。sit()は最も近い空席を選びます。空席がない場合、アイテムにはtakenMessage(デフォルトは**"Seat is taken"**)が表示されます。座るエモートが再生される前にアバターが移動します。この順序を逆にすると、エモートがキャンセルされます。

コメント