クッキー設定を管理します。以下で異なるタイプのクッキーを有効または無効にできます。詳細については、プライバシーポリシーをご覧ください。

Wonderland Engine 1.0.0 JavaScript Migration

Wonderland Engineは、JavaScriptコードの処理、インタラクション、および配布方法に大幅な改善を加えています。

この記事では、それらの変更について説明します。また、移行セクションでは、バージョン1.0.0に向けたプロジェクトの具体的な移行手順を詳細に解説します。

動機 

これまでは、Wonderland Engineのデフォルトバンドラはローカルスクリプトを連結する方式でした。ユーザーはスクリプトを作成し、エディタがそれを認識して最終的なアプリケーションとしてバンドルします。 外部ライブラリは予めバンドルして、プロジェクトフォルダ構造に配置する必要がありました。

また、NPMプロジェクトを手動で設定することも可能でしたが、この作業は面倒であり、チームのアーティストもNodeJSのセットアップや依存関係のインストール手順を踏む必要がありました。

新しいシステムのメリット:

  • デフォルトでNPMパッケージ依存をサポート
  • IDEからの補完提案が大幅に向上
  • TypeScript などの高度なツールとの簡単な統合
  • 他のWebAssemblyライブラリとの互換性
  • ページごとに複数のWonderland Engineインスタンスが可能
  • 非開発チームメンバーのためのNPMプロジェクトの自動管理

好きなツールをシームレスに使用できるJavaScriptエコシステムを整備中です。

エディタコンポーネント 

以前にNPMを使用していた場合、次のようなことを経験したかもしれません:

Wonderland Engine 1.0.0 JavaScript Migration

バージョン1.0.0以降、エディタはアプリケーションバンドルからコンポーネントタイプを取得しなくなりました。この変更により、最終的なアプリケーションに登録される可能性がある以上のコンポーネントをエディタで確認できるようになります。 これにより、高度なユーザーはストリーム可能なコンポーネントで複雑なプロジェクトを設定できるようになります。

この変化により、エディタは以下のことが必要となります:

  • Views > Project Settings > JavaScript > sourcePaths にコンポーネントまたはフォルダをリストすること
  • ルート package.json ファイルに依存関係を追加すること

コンポーネントを公開するライブラリを含む package.json の例:

1{
2  "name": "my-wonderful-project",
3  "version": "1.0.0",
4  "description": "My Wonderland project",
5  "dependencies": {
6    "@wonderlandengine/components": "^1.0.0-rc.5"
7  }
8}

エディタは package.json を読み込むことで、コンポーネントを見つけて開発時間を短縮し、共有性を向上させるための改善を行います。 詳しい情報については、Writing JavaScript Libraries チュートリアルをぜひご覧ください。

バンドリング 

新しい設定により、バンドリングプロセスを変更できます:

Views > Project Settings > JavaScript > bundlingType

Wonderland Engine 1.0.0 JavaScript Migration

各オプションを見ていきましょう:

esbuild 

あなたのスクリプトは、esbuild バンドラを使用してバンドルされます。

これはデフォルトの選択で、パフォーマンス上の理由からこの設定を使用することをお勧めします。

npm 

あなたのスクリプトは、独自の npm スクリプトを用いてバンドルされます。

カスタム build スクリプトを持つ package.json の例:

 1{
 2  "name": "MyWonderfulProject",
 3  "version": "1.0.0",
 4  "description": "My Wonderland project",
 5  "type": "module",
 6  "module": "js/index.js",
 7  "scripts": {
 8    "build": "esbuild ./js/index.js --bundle --format=esm --outfile=\"deploy/MyWonderfulProject-bundle.js\""
 9  },
10  "devDependencies": {
11    "esbuild": "^0.15.18"
12  }
13}

npm スクリプト名はエディタの設定で設定できます:

Views > Project Settings > JavaScript > npmScript

Wonderland Engine 1.0.0 JavaScript Migration

このスクリプトは、最終アプリケーションバンドルを生成する限り、任意のコマンドを実行できます。

お好みのバンドラ、例えばWebpackRollupを使用できます。しかし、反復時間を短縮するためにesbuildのようなツールを使用することをお勧めします。

アプリケーションエントリポイント 

コンポーネントは、ランタイム、つまりブラウザで実行中に異なる方法で登録されます。

ランタイムの際、エディタはあなたのアプリケーションのエントリポイント、つまり index.js ファイルを自動的に管理できます。

エディタで使用されるテンプレートは以下のようになります:

 1/* wle:auto-imports:start */
 2/* wle:auto-imports:end */
 3
 4import {loadRuntime} from '@wonderlandengine/api';
 5
 6/* wle:auto-constants:start */
 7/* wle:auto-constants:end */
 8
 9const engine = await loadRuntime(RuntimeBaseName, {
10    physx: WithPhysX,
11    loader: WithLoader,
12});
13
14// ...
15
16/* wle:auto-register:start */
17/* wle:auto-register:end */
18
19engine.scene.load(`${ProjectName}.bin`);
20
21/* wle:auto-benchmark:start */
22/* wle:auto-benchmark:end */

このテンプレートは、新しく作成されたプロジェクトや古いバージョン1.0.0以前のプロジェクトに自動的にコピーされます。

テンプレートには以下のタグがあります:

  • wle:auto-imports: インポート文を記述するべき場所を示します
  • wle:auto-register: 登録文を記述するべき場所を示します
  • wle:auto-constants: エディタが定数を記述する場所を示しており、例えば:
    • ProjectName: プロジェクトの .wlp ファイルに記載された名前
    • WithPhysX: 物理エンジンが有効な場合のブール値
    • WithLoader: ランタイムでのglTFのロードをサポートすべき場合のブール値

例:

 1/* wle:auto-imports:start */
 2import {Forward} from './forward.js';
 3/* wle:auto-imports:end */
 4
 5import {loadRuntime} from '@wonderlandengine/api';
 6
 7/* wle:auto-constants:start */
 8const ProjectName = 'MyWonderland';
 9const RuntimeBaseName = 'WonderlandRuntime';
10const WithPhysX = false;
11const WithLoader = false;
12/* wle:auto-constants:end */
13
14const engine = await loadRuntime(RuntimeBaseName, {
15  physx: WithPhysX,
16  loader: WithLoader
17});
18
19// ...
20
21/* wle:auto-register:start */
22engine.registerComponent(Forward);
23/* wle:auto-register:end */
24
25engine.scene.load(`${ProjectName}.bin`);
26
27// ...

このインデックスファイルは、js/forward.js に定義された単一のコンポーネント Forward を持つプロジェクト用に自動生成されます。 重要なのは、エディタはシーンで使用されている、つまりオブジェクトにアタッチされているコンポーネントのみをインポートし、登録することです。

アプリケーションがランタイムでのみ使用するコンポーネントを持つ場合、次のいずれかを行う必要があります:

単純なアプリケーションの場合、このテンプレートファイルは十分であり、変更を加える必要はないでしょう。より複雑な場合は、 タグコメントを削除し、自分で index.js ファイルを作成して管理することが可能です。

インデックスファイルを手動で管理することで、複数のエントリポイントを持つアプリケーションを作成したり、 エディタが認識していないコンポーネントを登録することが可能になります。

JavaScriptクラス 

Wonderland Engine 1.0.0は、ES6クラスを使用した新しいコンポーネントの宣言方法を導入しました。

 1import {Component, Property} from '@wonderlandengine/api';
 2
 3class Forward extends Component {
 4    /* コンポーネントの登録名 */
 5    static TypeName = 'forward';
 6    /* エディタで公開されるプロパティ */
 7    static Properties = {
 8        speed: Property.float(1.5)
 9    };
10
11    _forward = new Float32Array(3);
12
13    update(dt) {
14        this.object.getForward(this._forward);
15        this._forward[0] *= this.speed;
16        this._forward[1] *= this.speed;
17        this._forward[2] *= this.speed;
18        this.object.translate(this._forward);
19    }
20}

注意すべき点:

  • もはやグローバルWLシンボルを使用せず、@wonderlandengine/apiからAPIを使用します
  • API Component クラスを継承するクラスを作成します
  • コンポーネントの登録名は現在静的プロパティとして指定されます
  • プロパティはクラスで指定します

JavaScriptプロパティ 

オブジェクトリテラルプロパティはファンクタに置き換えられました:

 1import {Component, Property} from '@wonderlandengine/api';
 2
 3class MyComponent extends Component {
 4    /* コンポーネントの登録名 */
 5    static TypeName = 'forward';
 6    /* エディタで公開されるプロパティ */
 7    static Properties = {
 8        myFloat: Property.float(1.0),
 9        myBool: Property.bool(true),
10        myEnum: Property.enum(['first', 'second'], 'second'),
11        myMesh: Property.mesh()
12    };
13}

コンポーネント依存関係 

依存関係とは、あなたのコンポーネントが登録されるときに自動的に登録されるコンポーネントのことです。

次に、 Forward にコンポーネント Speed を追加してみましょう:

 1import {Component, Type} from '@wonderlandengine/api';
 2
 3class Speed extends Component {
 4    static TypeName = 'speed';
 5    static Properties = {
 6        value: Property.float(1.5)
 7    };
 8}
 9
10class Forward extends Component {
11    static TypeName = 'forward';
12    static Dependencies = [Speed];
13
14    _forward = new Float32Array(3);
15
16    start() {
17      this._speed = this.object.addComponent(Speed);
18    }
19
20    update(dt) {
21        this.object.getForward(this._forward);
22        this._forward[0] *= this._speed.value;
23        this._forward[1] *= this._speed.value;
24        this._forward[2] *= this._speed.value;
25        this.object.translate(this._forward);
26    }
27}

Forward が登録されるとき、Speed は依存関係としてリストされ、自動的に登録されます。

この動作は、index.js で作成された WonderlandEngine オブジェクト上のブール値 autoRegisterDependencies によって管理されます。

イベント 

Wonderland Engineは以前、配列リスナーを使ってイベントを処理していました。例えば:

  • WL.onSceneLoaded
  • WL.onXRSessionStart
  • WL.scene.onPreRender
  • WL.scene.onPreRender

このエンジンは現在、イベントでのインタラクションを容易にするための Emitter クラスを提供しています:

1engine.onXRSessionStart.add((session, mode) => {
2    console.log(`Start session '${mode}'!`);
3})

識別子を使ってリスナーを管理することができます:

1engine.onXRSessionStart.add((session, mode) => {
2    console.log(`Start session '${mode}'!`);
3}, {id: 'my-listener'});
4
5// 完了したら、`id`で簡単に削除できます。
6engine.onXRSessionStart.remove('my-listener');

詳しくは、Emitter API ドキュメントを参照してください。

オブジェクト 

Object クラスもこの再設計から除外されていません。APIをより一貫性があり、安全にするための変更が加えられました。

エクスポート名 

Object クラスは現在、Object3Dとしてエクスポートされています。この変更は、JavaScriptのObjectコンストラクターを覆い隠すことを防ぐために行われました。

移行を円滑にするために、Object は引き続きエクスポートされますが、将来の移行を円滑にするために、今後は

1import {Object3D} from '@wonderlandengine/api';

を使用してください。

変換 

変換APIも変更されています。エンジンは現在、ゲッターおよびセッター(アクセサー)の使用を変換において非推奨としています:

1this.object.translationLocal;
2this.object.translationWorld;
3this.object.rotationLocal;
4this.object.rotationWorld;
5this.object.scalingLocal;
6this.object.scalingWorld;
7this.object.transformLocal;
8this.object.transformWorld;

これらのゲッター / セッターの欠点は以下の通りです:

  • 一貫性: 他の変換APIと不一致
  • パフォーマンス: 各呼び出しで Float32Array が割り当てられる
  • 安全性: メモリビューが他のコンポーネントによって変更される可能性
    • 後で読み出しのために Float32Array 参照を保存したときにバグが発生する可能性

新しいAPIについては、オブジェクト変換セクションを参照してください。

JavaScriptのアイソレーション 

内部バンドラを使用している場合、以下のようなコードを見たことがあるかもしれません:

component-a.js

1var componentAGlobal = {};
2
3WL.registerComponent('component-a', {}, {
4    init: function() {
5        componentAGlobal.init = true;
6    },
7});

component-b.js

1WL.registerComponent('component-b', {}, {
2    init: function() {
3        if(componentAGlobal.init) {
4              console.log('Component A has been initialized before B!');
5        }
6    },
7});

上記のコードは、componentAGlobal 変数に依存しています。component-a が最初に登録され、バンドルにプレフィックスが付けられることを期待しています。

これはWonderland Engineの内部バンドラがアイソレーションを行わなかったため、動作していました。

1.0.0では、esbuild または npm を使用してもこれらのコードは動作しません。バンドラは component-a で使用される componentAGlobalcomponent-b で使用されるものをリンクすることができません。

一般的なルールとして: バンドラを使用する場合、各ファイルを分離されたものと考えてください。

移行 

プロジェクトが以前npmを使用していたかどうかに応じて、いくつかの手動の移行手順が必要です。

各セクションでは、以前の設定に基づいた必要な適切な手順を説明します。

エディタコンポーネント (#migration-editor-components) 

内部バンドラ 

以前、内部バンドラを使用していたユーザー、つまり useInternalBundler チェックボックスが有効になっている:

Views > Project Settings > JavaScript > useInternalBundler

特別な手順は必要ありません**。

Npm 

npmユーザーの場合、スクリプトが sourcePaths 設定にリストされていることを確認する必要があります。

ライブラリを使用している場合、そのライブラリがWriting JavaScript Librariesチュートリアルに従ってWonderland Engine 1.0.0 に移行されていることを確認してください。

依存しているライブラリが最新でない場合、sourcePaths 設定に node_modules フォルダへのローカルパスを追加することができます。例:

Wonderland Engine 1.0.0 JavaScript Migration

生成されたバンドルが npm または esbuild を使用しても、エディタでコンポーネントを見つけるために使用されないことを常に覚えておいてください。これはアプリケーションの実行時にのみ使用されます。

バンドリング 

特別な手順は必要ありません**。プロジェクトは自動的に移行されるはずです。

JavaScriptクラス、プロパティ、およびイベント 

このセクションは、すべてのユーザーに共通であり、以前に useInternalBundler を有効にしていたかどうかに関係なく同じです。

次に、旧方法と新方法のコードの比較を見てみましょう。

バージョン1.0.0以前

 1WL.registerComponent('player-height', {
 2    height: {type: WL.Type.Float, default: 1.75}
 3}, {
 4    init: function() {
 5        WL.onXRSessionStart.push(this.onXRSessionStart.bind(this));
 6        WL.onXRSessionEnd.push(this.onXRSessionEnd.bind(this));
 7    },
 8    start: function() {
 9        this.object.resetTranslationRotation();
10        this.object.translate([0.0, this.height, 0.0]);
11    },
12    onXRSessionStart: function() {
13        if(!['local', 'viewer'].includes(WebXR.refSpace)) {
14            this.object.resetTranslationRotation();
15        }
16    },
17    onXRSessionEnd: function() {
18        if(!['local', 'viewer'].includes(WebXR.refSpace)) {
19            this.object.resetTranslationRotation();
20            this.object.translate([0.0, this.height, 0.0]);
21        }
22    }
23});

バージョン1.0.0以降

 1/* npm依存関係をお忘れなく */
 2import {Component, Property} from '@wonderlandengine/api';
 3
 4export class PlayerHeight extends Component {
 5    static TypeName = 'player-height';
 6    static Properties = {
 7        height: Property.float(1.75)
 8    };
 9
10    init() {
11        /* Wonderland Engine 1.0.0 はグローバル
12         * インスタンスを排除しました。現在は `this.engine` 経由で
13         * 現在のエンジンインスタンスにアクセスできます。 */
14        this.engine.onXRSessionStart.add(this.onXRSessionStart.bind(this));
15        this.engine.onXRSessionEnd.add(this.onXRSessionEnd.bind(this));
16    }
17    start() {
18        this.object.resetTranslationRotation();
19        this.object.translate([0.0, this.height, 0.0]);
20    }
21    onXRSessionStart() {
22        if(!['local', 'viewer'].includes(WebXR.refSpace)) {
23            this.object.resetTranslationRotation();
24        }
25    }
26    onXRSessionEnd() {
27        if(!['local', 'viewer'].includes(WebXR.refSpace)) {
28            this.object.resetTranslationRotation();
29            this.object.translate([0.0, this.height, 0.0]);
30        }
31    }
32}

これらの2つの例は等価であり、同じ結果をもたらします。

5行目の違いに注目してください:

1WL.onXRSessionStart.push(this.onXRSessionStart.bind(this));

1this.engine.onXRSessionStart.add(this.onXRSessionStart.bind(this));

npm依存関係と標準バンドリングに移行したため、もはやグローバル WL 変数は必要ありません。

グローバルエンジンを公開することには次の2つの制約がありました:

  • コンポーネントの共有が難しい
  • 複数のエンジンインスタンスを実行できない

2番目のポイントは一般的な使用例ではありませんが、ユーザーがスケーラビリティについて制限されることを望んでいません。

オブジェクト変換 

新しいAPIは、Wonderland Engine全体での使用を意図して通常のパターンに基づいています:

1getValue(out) { ... }
2setValue(v) { ... }

翻訳回転、およびスケールのコンポーネントも、このパターンに従います:

1const translation = this.object.getTranslationLocal();
2this.object.setTranslationLocal([1, 2, 3]);
3
4const rot = this.object.getRotationLocal();
5this.object.setRotationLocal([1, 0, 0, 1]);
6
7const scaling = this.object.getScalingLocal();
8this.object.setScalingLocal([2, 2, 2]);

他のAPIと同様に、ゲッターout パラメータを空で使用すると、出力配列が作成されます。再利用可能な配列を使用することが常に望ましいです(パフォーマンスの理由で)。

ローカルスペースでのオブジェクト変換を読み書きできるだけでなく、ワールドスペースで直接操作することも可能です:

1const translation = this.object.getTranslationWorld();
2this.object.setTranslationWorld([1, 2, 3]);
3
4const rot = this.object.getRotationWorld();
5this.object.setRotationWorld([1, 0, 0, 1]);
6
7const scaling = this.object.getScalingWorld();
8this.object.setScalingWorld([2, 2, 2]);

詳しくは、Object3D API ドキュメントをご覧ください。

JavaScriptのアイソレーション 

このセクションは、全てのユーザーに共通で、以前に useInternalBundler が有効だったかどうかに関係なく同じです。

コンポーネント間でデータを共有する方法は多数あり、アプリケーションの開発者が最も適した方法を選択します。

ここでは、グローバル変数に依存しないデータ共有方法のいくつかの例を紹介します。

コンポーネント内の状態 

コンポーネントは、他のコンポーネントによってアクセスされるデータの集合体です。

したがって、アプリケーションの状態を保持するためのコンポーネントを作成できます。たとえば、3つの状態があるゲームを作成している場合:

  • 実行中
  • 勝利
  • 敗北

次の形のシングルトンコンポーネントを作成できます:

game-state.js

 1import {Component, Type} from '@wonderlandengine/api';
 2
 3export class GameState extends Component {
 4  static TypeName = 'game-state';
 5  static Properties = {
 6    state: {
 7      type: Type.Enum,
 8      values: ['running', 'won', 'lost'],
 9      default: 0
10    }
11  };
12}

GameState コンポーネントは管理オブジェクトに追加され、ゲームの状態を変更するコンポーネントによって参照されるべきです。

プレイヤーが死亡したときにゲームの状態を変更するコンポーネントを作成しましょう:

player-health.js

 1import {Component, Type} from '@wonderlandengine/api';
 2
 3import {GameState} from './game-state.js';
 4
 5export class PlayerHealth extends Component {
 6  static TypeName = 'player-health';
 7  static Properties = {
 8    manager: {type: Type.Object},
 9    health: {type: Type.Float, default: 100.0}
10  };
11  update(dt) {
12    /* プレイヤーが死亡した場合、状態を変更します。 */
13    if(this.health <= 0.0) {
14      const gameState = this.manager.getComponent(GameState);
15      gameState.state = 2; // 状態を`lost`に設定します。
16    }
17  }
18}

この方法は、前 1.0.0 アプリケーションでグローバルを置き換えることを示しています。

エクスポート 

また、変数をインポートおよびエクスポートすることで共有することも可能です。ただし、このオブジェクトはバンドル全体で同一であることに注意してください。

前述の例をエクスポートで実行してみましょう:

game-state.js

1export const GameState = {
2  state: 'running'
3};

player-health.js

 1import {Component, Type} from '@wonderlandengine/api';
 2
 3import {GameState} from './game-state.js';
 4
 5export class PlayerHealth extends Component {
 6  static TypeName = 'player-health';
 7  static Properties = {
 8    manager: {type: Type.Object},
 9    health: {type: Type.Float, default: 100.0}
10  };
11  update(dt) {
12    if(this.health <= 0.0) {
13      GameState.state = 'lost';
14    }
15  }
16}

この方法は機能しますが、完全ではありません。

以下は機能しない例の1つです。このコードが gamestate というライブラリにあると想像してください。

  • あなたのアプリケーションは gamestate バージョン 1.0.0 に依存しています
  • あなたのアプリケーションはライブラリ A に依存しています
  • ライブラリ Agamestate バージョン 2.0.0 に依存しています

アプリケーションは2つの gamestate ライブラリのコピーを持つことになります。なぜなら、両バージョンは互換性がないからです。

ライブラリ AGameState オブジェクトを更新すると、それは自身のこのエクスポートのインスタンスを変更しています。これは、両方のバージョンが互換性がないため、アプリケーションが2つの異なるインスタンスのライブラリをバンドルしているので起こります。

最後に 

このガイドにより、あなたはWonderland Engine 1.0.0へのプロジェクトの移行を行うための準備ができました!

何か問題が発生した場合は、Discordサーバーでコミュニティに連絡してください。

Last Update: March 28, 2025

最新情報をお届けします。