メインコンテンツまでスキップ

ESM リファレンス

注記

このページでは、推奨される ESM スクリプト システムのスクリプト属性について説明します。

従来のスクリプト属性については、こちらをクリックしてください。

アトリビュートとは?

アトリビュートは、特定のパラメータをエディターに公開できる強力な機能です。

これにより、一度コードを書けば、異なるインスタンスで値を調整して異なるプロパティを与えることができます。アーティスト、デザイナー、その他のプログラマーではないチームメンバーは、コードを書かずにそれらを調整・修正することができます。

簡単な回転スクリプトの例から始めましょう。

import { Script } from 'playcanvas';

export class Rotator extends Script {
static scriptName = 'rotator';

/**
* エディターで `speed` プロパティを動的に設定できるようになりました。
*
* @attribute
*/
speed = 2;

update(dt){
this.entity.rotateLocal(0, this.speed * dt, 0);
}
}

この例では、スクリプトはエンティティをその速度に応じて回転させるだけですが、速度の値は何でしょうか?

speed メンバーの上の @attribute タグは、それをアトリビュートに昇格させます。エンティティにアタッチされると、エディターは speed の値を実行時に、それがアタッチされている各エンティティに対して動的に設定できるコントロールを作成します。

これが実際に意味することは、スクリプトの様々なメンバーをエディターに公開し、それらの値を実行時に編集するためのコントロールを作成できるということです。

アトリビュート

speed は単なるクラスメンバーであるため、他のメンバーと同じようにアクセスできます。

update(dt) {
this.entity.rotateLocal(0, this.speed, 0);
}

エディターでのアトリビュート

スクリプト属性

アトリビュートを宣言したら、エディターはスクリプトアトリビュートを公開するためにコードをパースする必要があります。アトリビュートが変更された場合、アトリビュートを手動で更新する必要があります。パースボタンをクリックしてください。

パースボタン

アトリビュート情報

アトリビュートをエディターに公開する際、コンテキストを提供し、より具体的なコントロールを提示するのに役立つ追加情報を表示することもできます。これにより、スクリプトのユーザーエクスペリエンスを向上させることができます。

アトリビュートの説明

@attribute コメントブロックの最初の文は、エディターで説明として使用されます。これは、アトリビュートが何であり、どのように動作するかについての文脈情報を表示するのに役立つ方法です。

/**
* @attribute
* Y軸回転の速度を度単位で設定します。
*/
speed = 2;

エディターでは、これはツールチップとして利用できます。

アトリビュートの説明

アトリビュートの制約

速度の適切な範囲値を定義したい場合はどうでしょうか。これは @range タグで行うことができます。

/** 
* @attribute
* @range [0, 10]
*/
speed = 10;

これは単に、速度がアトリビュートであり、その値が0から10の範囲内であるべきことをエディターに伝えます。エディターは、この範囲にマッピングされた数値スライダーを作成します。

アトリビュートの説明

エディターが可能な値のセットを制限するのに役立つ、追加の数値制約を設定できます。

/** 
* @attribute
* @range [0, 10]
* @precision 0.1
* @step 0.05
*/
speed = 10;

アトリビュートの型

スクリプトメンバーをアトリビュートとして公開すると、エディターはそのアトリビュートの型に関連するコントロールを表示します。アトリビュートが数値であれば数値入力、ブール値であればチェックボックスが表示されます。

アトリビュートは numberstringbooleanVec2Vec3Vec4EntityAsset または Color にすることができます。

@type タグ

属性の初期値が事前にわからない場合があります。例えば、スクリプト上でアセット属性を定義したい場合、必ずしも初期値があるとは限りません。このような、値が事前に不明でも型はわかっている状況では、jsdocの@typeタグを使用できます。

/**
* @attribute
* @type {Asset}
*/
myTexture;
警告

属性は、speed = 10のように値で初期化されるか、@type {number}のようなjsdoc型を持つ必要があります。どちらも存在しない場合、その属性は無視されます。

Entity属性

Entity型を使用すると、ヒエラルキー内の別のエンティティを参照できます。2つのエンティティをリンクさせる素晴らしい方法です。

/**
* @attribute
* @type {Entity}
*/
target;
important

属性が正しく解析されるためには、playcanvasからEntityをインポートする必要があります。

Asset属性

Asset属性を使用すると、スクリプト内でプロジェクトのアセットを参照できます。Asset属性は、@resourceタグもサポートしており、これにより属性を特定のタイプ(例: 'texture'、'material'、'model')のアセットに限定できます。

Asset属性のランタイム型はAssetです。ランタイムでAsset属性のリソースを参照するには、次のようにします。

/**
* @attribute
* @type {Asset}
* @resource texture
*/
texture;

initialize() {
console.log('これはテクスチャアセットです', this.texture);
console.log('これはテクスチャリソースです', this.texture.resource);
}
important

属性が正しく解析されるためには、playcanvasからAssetをインポートする必要があります。

Color属性

/** @attribute */
color = new Color();
important

属性が正しく解析されるためには、playcanvasからColorをインポートする必要があります。

カラー属性は、エディタで公開されるとカラーピッカーを表示します。アルファチャンネルも公開したいかどうかに応じて、rgbrgbaの2つのオプションがあります。

Vector属性

/** @attribute */
position = new Vec3();
important

属性が正しく解析されるためには、playcanvasからVec2/Vec3/Vec4をインポートする必要があります。

ベクトル属性は、2、3、または4次元にすることができます。エディタでは、各コンポーネントに数値入力が表示され、それぞれを独立して設定できます。

属性ベクトル

Curve属性

/**
* @attribute
* @type {Curve}
* @color rgba
*/
wave;
important

属性が正しく解析されるためには、playcanvasからCurveをインポートする必要があります。

カーブ属性は、時間期間にわたって変化する値を表現するために使用されます。すべてのカーブは0.0〜1.0の期間で定義されます。複数のカーブを定義できます。例えば、curvesプロパティを使用してx、y、z用に3つのカーブを定義することで、カーブから3D位置を得ることができます。colorプロパティを使用して色を変更するための特別なカーブエディタもあります。

属性配列

場合によっては、グループ化された属性のリストをまとめて公開したい場合があります。例えば、グラデーションを生成するスクリプトがあり、開始点と終了点を持つ代わりに、ユーザーがグラデーションに任意の数の「カラーストップ」を設定できるようにしたいとします。この場合、@typeタグに配列修飾子を使用します。

/**
* @attribute
* @type {Color[]}
*/
gradientStops;

Color[]宣言は、jsdoc type tagを使用して、gradientStopsColorsの配列であることを宣言します。エディタはこのように解釈し、リスト内で複数のColor値を設定できるコントローラを作成します。

属性配列

initializeまたはupdateループで、gradientStopsを配列として反復処理できます。

initialize() {
this.gradientStops.forEach(color => {
console.log('これはColorクラスです', color);
});
}

列挙型

属性を可能な値のセットに制約したい場合があります。この状況では、@enumタグを使用できます。これにより、属性の値として列挙型が使用され、エディターが可能な値のリストに制約されたコンボボックスを表示するようになります。


/** @enum {number} */
const Lights = {
ON: 1,
OFF: 0,
UNKNOWN: 0.5
};

class MyScript extends Script {
static scriptName = 'myScript';

/**
* @attribute
* @type {Lights}
*/
ambient = Lights.OFF;
}

これは、Lightsクラスを可能な値の列挙型として使用しています。@type {Lights}は、ambientLightsにリストされている値のみを持つべきであることを示します。オーサータイムで、エディターはLights列挙型のキー(ON/OFF/UNKNOWN)をラベルとして使用し、ambientにそれに対応する値を設定するドロップダウンコントロールを生成します。列挙型の値は、数値、文字列、またはブール値のみである必要があります。

属性の列挙

条件付き属性

スクリプト内のすべての属性は、エディターに対応するUIコントロールを作成します。場合によっては、他の属性の値に基づいて特定のコントロールを非表示にしたり、無効にしたりしたいことがあります。

例を見てみましょう:

export class Delorean extends Script {
static scriptName = 'delorean';

/**
* @attribute
*/
power = false;

/**
* @attribute
*/
speed = 10;
}

これにより、power用のチェックボックスとspeed用のスライダーが作成されます。しかし、powerがオンになっていない限り、ユーザーがspeedを調整できないようにしたい場合はどうでしょうか。

これは@enabledifタグを使用することで実現できます。

export class Delorean extends Script {
static scriptName = 'delorean';

/**
* @attribute
*/
power = false;

/**
* @attribute
* @enabledif {power}
*/
speed = 10;
}

これで、speedスライダーはpowertrueの場合にのみ有効になります。

式ベースの条件

より表現力豊かな条件を使用することもできます。条件がtruthyな値に評価された場合、コントロールが有効になります。

export class Delorean extends Script {
static scriptName = 'delorean';

/**
* @attribute
*/
power = false;

/**
* @attribute
* @enabledif {power}
*/
speed = 10;

/**
* @attribute
* @visibleif {speed > 88.8}
*/
enableFluxCapacitor = true;
}

この場合:

  • speedスライダーは、powerがオンの場合にのみ有効になります。
  • enableFluxCapacitorチェックボックスは、speed88.8より大きい場合にのみ表示されます。

これにより、スクリプトの状態に基づいた、リッチで動的なエディターインターフェースが可能になります。

動作中の例

属性のグループ化

状況によっては、属性を論理的にグループ化したい場合があります。例えば、敵の速度とパワーを持つGameLogic Scriptがあるとします。属性を個別に宣言するのではなく、それらを1つのenemy属性の下にグループ化する方が理にかなっています。これはAttribute Groupsを使用して行うことができます。

属性グループは、本質的にサブ属性を含むオブジェクトです。

class GameLogic extends Script {
static scriptName = 'gameLogic';

/**
* @attribute
* `power`と`speed`はサブ属性として公開されます
*/
enemy = { power: 10, speed: 3 };

initialize() {
console.log(this.enemy.speed); // 3
console.log(this.enemy.power); // 10
}
}

これにより、enemy が属性グループとして定義されます。エディターは、制御可能なpowerとspeedのネストされたサブ属性を持つenemy属性を公開します。これは、属性を論理的にグループ化するためのより柔軟な方法を提供します。

ヒント

属性グループを使用すると、関連する属性をオブジェクトベースの構造に論理的にグループ化できます。

属性グループを宣言するにはさまざまな方法があります。インライン属性グループまたはTypeDefグループを使用できます。

インライングループ

属性グループを宣言するシンプルなインライン方法

class GameLogic extends Script {
static scriptName = 'gameLogic';

/** @attribute */
enemy = { power: 10, speed: 3 };
}

TypeDefグループ

これは、属性グループを宣言するよりモジュール化された方法です。インラインバージョンを使用するよりも冗長ですが、typedefバージョンはよりモジュール化されており、複数のスクリプトや属性で使用できます。

/**
* @typedef {Object} Enemy
* @prop {number} speed - 敵のスピード
* @prop {number} power - 敵のパワー
*/

class GameLogic extends Script {
static scriptName = 'gameLogic';

/**
* @attribute
* @type {Enemy}
*/
enemy;
}

インターフェース属性

属性をグループ化し、そのメンバーに個別の制約を設定したい場合は、インターフェース属性を使用できます。これは、属性をグループ化するより柔軟な方法を提供します。

/** @interface */
class Enemy {
/**
* @range [0, 11]
*/
power = 10;

speed = 3;
}

class GameLogic extends Script {
static scriptName = 'gameLogic';

/**
* @attribute
* @type {Enemy}
*/
enemy;
}

上記の例では、powerメンバーが0 - 11の範囲内に制約された新しいEnemyインターフェースを作成しました。また、GameLogicスクリプトがEnemy型のenemy属性を持つことを宣言しました。

ヒント

インターフェース属性を使用すると、属性を論理的にグループ化し、個々のサブ属性に制約を設定することができます。また、コードをモジュール化することもできます。

インターフェース属性のルール

インターフェース属性を使用するには、いくつかの要件があります。

  • インターフェース属性は、クラス宣言の前に/** @interface */ブロックコメントを持つ必要があります。
  • スクリプト属性は、@type {InterfaceAttribute}タグを使用してインターフェース属性を使用する必要があります。
  • インターフェース属性のすべてのパブリックメンバーはエディターで利用可能であり、使用されます。各メンバーに@attributeタグを使用する必要はありません。
  • ネストされたインターフェース属性を持つことはできません。

インターフェース属性配列

インターフェース属性は、通常の属性と同様に配列として使用できます。これは、GameLogicスクリプトが敵の配列を使用できることを意味し、それぞれが独自の制御可能なpowerとspeedプロパティを持ちます。

class GameLogic extends Script {
static scriptName = 'gameLogic';

/**
* @attribute
* @type {Enemy[]}
*/
enemies;

update(dt) {
this.enemies.forEach(({ power, speed }) => {
this.updateEnemy(power, speed);
});
}
}

これにより、エディターにEnemyコントロールの配列が作成され、それぞれがサブ属性の独自の数値コントロールを持ちます。

Attribute Complex Arrays