Wonderland Engine 1.0.0 JavaScript Migration
Wonderland Engineは、JavaScriptコードの処理、インタラクション、配布方法に大幅な改善を行っています。
このブログ記事では、それらの変更について説明します。また、Migrationsセクションでは、バージョン1.0.0以前のプロジェクトに対する各新機能の移行手順を詳細に説明します。
モチベーション
これまで、Wonderland Engineのデフォルトバンドラはローカルスクリプトの連結に依存していました。ユーザーはスクリプトを作成し、エディタがそれを取得して最終的なアプリケーションを作成するためにバンドルします。 外部ライブラリを予めバンドルし、プロジェクトフォルダ構造に配置する必要がありました。
また、NPMプロジェクトをセットアップすることも可能でしたが、セットアップは手動であり、チームのアーティストはNodeJSをセットアップし、依存関係をインストールする手順を踏む必要がありました。
新しいシステムの主な利点:
- デフォルトでのNPMパッケージ依存
- IDEからの非常に優れた補完提案
- TypeScript などの高度なツールとの統合が非常に簡単
- 他のWebAssemblyライブラリとの互換性
- ページごとに複数のWonderland Engineインスタンス
- 非開発チームメンバーのためのNPMプロジェクトの自動管理
お気に入りのツールをシームレスに使用できるJavaScriptエコシステムを構築中です。
エディタコンポーネント
以前NPMを使用していた場合、以下のようなことがあったかもしれません:

バージョン1.0.0から、エディタはアプリケーションバンドルからコンポーネントタイプを取得しなくなりました。上記のエラーを修正するだけでなく、 最終アプリケーションに登録される可能性がある以上のコンポーネントをエディタがリストするようになります。 これにより、高度なユーザーが将来、ストリーム可能なコンポーネントを使用して複雑なプロジェクトを設定できるようになります。
この変更により、エディタは以下が必要になります:
- コンポーネントまたはフォルダを
Views > Project Settings > JavaScript > sourcePaths
にリストする - ルート
package.json
ファイルに依存関係を追加する
ライブラリのコンポーネント公開の package.json
の例:
エディタは、package.json
を読み取ることでコンポーネントを見つけ、開発時間を短縮し、共有性を向上させることができます。
詳細については、Writing JavaScript Libraries チュートリアルをご覧ください。
バンドリング
新しい設定により、バンドリングプロセスを変更できます:
Views > Project Settings > JavaScript > bundlingType

それぞれのオプションを見てみましょう:
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

このスクリプトは、最終アプリケーションバンドルを生成する限り、任意のコマンドを実行できます。
お気に入りのバンドラ、Webpack や Rollup を使用できます。 しかし、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
という単一のコンポーネントを持つプロジェクトのために自動生成されます。
重要なのは、エディタはシーンで使用されている、つまりオブジェクトにアタッチされたコンポーネントのみをインポートし、登録することです。
もしあなたのアプリケーションがランタイムでのみ使用するコンポーネントを持つ場合、次のことをする必要があります:
- それらを依存関係としてマークする。Component Dependencies セクションで詳細を参照
index.js
ファイルに手動でインポートする
単純なアプリケーションの場合、このテンプレートファイルは十分で、修正は必要ないでしょう。より複雑な使用例の場合、
タグコメントを削除して自分自身で 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
クラスが付属しています:
リスナーを識別子を使用して管理することができます:
詳細については、Emitter API ドキュメントを参照してください。
オブジェクト
Object
はこの全体的な改訂にも置き去りにされませんでした。そのAPIをより一貫性があり、安全にするための変更が加えられました。
エクスポート名
Object
クラスは現在 Object3D
としてエクスポートされています。この変更は、JavaScriptのObjectコンストラクターを覆い隠すことを防ぐために行われました。
移行を円滑にするため、Object
は現在もエクスポートされますが、将来の移行を容易にするために今は次のように使用してください
1import {Object3D} from '@wonderlandengine/api';
変換
変換APIも暖まらせていません。エンジンは現在変換のゲッターおよびセッター(アクセス)使用を非推奨としています:
これらのゲッター / セッターにはいくつかの欠点がありました:
- 一貫性: 他の変換APIに従っていない
- パフォーマンス: 各呼び出しで
Float32Array
を割り当てる - 安全性: 他のコンポーネントによってメモリビューが変更される可能性がある
- 後で読み取り用に
Float32Array
参照を保存するときの潜在的なバグ
- 後で読み取り用に
新しいAPIを見るために段落を読み進めてください。Object Transform セクションを参照してください。
JavaScriptアイソレーション
内部バンドラを使用している方は、次のようなコードを見たことがあるかもしれません:
component-a.js
component-b.js
上記のコードは、componentAGlobal
変数に関していくつかの仮定をしています。それは component-a
が最初に登録され、バンドルにプレフィックスが追加されることを期待しています。
これはWonderland Engine内部バンドラがアイソレーションを行わなかったため、以前は機能していました。
1.0.0では、esbuildまたはnpmを使用しても、これでは機能しません。バンドラは component-a
で使用される componentAGlobal
と component-b
で使用されるもののリンクを作ることができません。
慣例として: 各ファイルを分離されたものとして考えてください。
移行
プロジェクトが以前npmを使用していたかどうかに応じて、いくつかの手動の移行手順が必要です。
各セクションでは、以前のセットアップに基づいて必要な適切な手順を説明します。
エディタコンポーネント (#migration-editor-components)
内部バンドラ
以前内部バンドラを使用していたユーザー、つまり useInternalBundler
チェックボックスが有効になっている:
Views > Project Settings > JavaScript > useInternalBundler
さらなる手順は必要ありません**。
Npm
npmユーザーに対しては、スクリプトが sourcePaths
設定にリストされていることを保証する必要があります。
ライブラリを使用している場合、それが Writing JavaScript Libraries チュートリアルに従ってWonderland Engine 1.0.0 に移行されていることを確認してください。
依存関係の1つが最新でない場合、node_modules
フォルダへのローカルパスを sourcePaths
設定に追加することができます。例:

生成されたバンドルが 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}
これらの例は等価であり、同じ結果につながります。
5行目の違いに注目してください:
1WL.onXRSessionStart.push(this.onXRSessionStart.bind(this));
対
1this.engine.onXRSessionStart.add(this.onXRSessionStart.bind(this));
標準バンドリングと npm依存関係 に移行したため、グローバル WL
変数は不要になりました。
グローバルにエンジンを露出させることには次の2つの制限がありました:
- コンポーネントの共有が難しくなる
- 複数のエンジンインスタンスを実行できない
2番目のポイントは一般的な使用ケースではありませんが、どのユーザーにもスケーラビリティの制限をかけたくありません。
オブジェクト変換
新しいAPIは、Wonderland Engine全体で使用される通常のパターンに基づいています:
翻訳、回転、スケーリングのコンポーネントも現在このパターンに従っています:
他のapiと同様に、ゲッターの空の
out
パラメータを使用すると、出力配列が作成されます。再利用可能な配列を使用する方が常に良いです(パフォーマンスの理由から)。
オブジェクトのローカル空間変換の読み書きが可能であるだけでなく、直接ワールド空間で操作することもできます:
詳細については、Object3D API ドキュメントを参照してください。(TODO:リンクを置き換えます)
JavaScriptアイソレーション
このセクションはすべてのユーザーに共通で、useInternalBundler
が有効かどうかに関わらず同じです。
コンポーネント間でデータを共有する方法は多数あり、アプリケーションの開発者が最も適切な方法を選択します。
ここでは、グローバル変数に依存しないデータ共有方法のいくつかの例を示します。
コンポーネント内の状態
コンポーネントは、他のコンポーネントによってアクセスされるデータの袋です。
したがって、アプリケーションの状態を保持するためのコンポーネントを作成できます。たとえば、3つの状態があるゲームを作成している場合:
- 実行中
- 勝利
- 敗北
次の形のシングルトンコンポーネントを作成できます:
game-state.js
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 アプリケーションでグローバルを置き換える方法の1つを示しています。
エクスポート
インポートおよびエクスポートを使用して変数を共有することも可能ですが、オブジェクトはバンドル全体で同一であることに注意してください。
上記の例をエクスポートで再訪してみましょう:
game-state.js
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}
このソリューションは機能しますが、万全ではありません。
この機能しない例を見てみましょう。このコードが gamestate
というライブラリにあると想像してください。
- アプリケーションは
gamestate
バージョン 1.0.0 を依存します - アプリケーションはライブラリ
A
に依存します - ライブラリ
A
はgamestate
バージョン 2.0.0 を依存します
あなたのアプリケーションは2つの gamestate
ライブラリのコピーを持つことになります、なぜなら両方がバージョンにおいて互換性がないからです。
ライブラリ A
が GameState
オブジェクトを更新すると、それは実際にはこのエクスポートの自身のインスタンスを変更しています。このことは、両方のバージョンが互換性がないために、アプリケーションが2つの異なるインスタンスのライブラリをバンドルしているので起こります。
終わりの言葉
このガイドを使って、あなたはWonderland Engine 1.0.0 へのプロジェクト移行を行う準備が整いました!
もし問題が発生した場合は、Discordサーバー でコミュニティに連絡してください。