このエンジンを選ぶ理由
機能の一覧ではありません。作れるものを変えてしまう、ごく少数の設計判断と、それが実務でなぜ効くのか という話です。
どこでも同じ 1 つのモデル
エディター、ウェブのプレイヤー、スマートフォンのアプリ、サーバー。そのすべてが同じシーンを 動かします。同じオブジェクト、同じ component、同じ挙動です。
エクスポート工程はなく、別の「ランタイム形式」もなく、片方からずれていく 2 つ目の実装もありません。 エディターで並べたものがそのまま動き、プレイヤーで見つけた不具合はどこでも再現します。
実務上の帰結は、地味ですが価値があります。公開しても、ものの意味が変わりません。
グラフはインタープリターではなく、コードになります
多くのツールでは、ビジュアルグラフは実行時に何かがノードごとに、毎フレーム辿るデータです。ここでは 一度ふつうの script にコンパイルされ、動くのはその script です。
| 速度 | グラフのコストは手書きのコードと同じです。グラフがコードそのものだからです |
| 中身が見える | コードパネルを開けば、グラフが何になったかをそのまま読めます |
| 組み合わせられる | グラフから script を、script からグラフを呼べます。下では同じものだからです |
だから「ここから先はビジュアルツールを捨ててコードで書き直し」という天井がありません。いまある グラフの隣に script を足すだけです。
マテリアルは、底までグラフです
組み込みのマテリアルの種類は、ノードエディターを後付けした固定セットではありません。独自グラフが 使うのと同じ体系の中の一点です。だからグラフのマテリアルは遅くもなく、二流でもなく、ウェブに 限られてもいません。同じグラフが、ブラウザー用にもスマートフォンのネイティブレンダラー用にも コンパイルされます。
そして使っていないものはコストゼロです。既定値のままの physical の追加項目はコンパイル時に 畳み込まれるので、ガラスにできる選択肢を持つマテリアルは、持たないものとまったく同じ安さです。
表計算ソフトのように動きます
ほかのすべてが乗っている判断がこれで、たとえ話がいちばん分かりやすいはずです。
表計算では、セルを 1 つ変えると、そのセルを使っている数式だけが再計算されます。シート全体を 計算し直す人はいませんし、「更新」ボタンを押すこともありません。
シーンもまったく同じです。 オブジェクトの色を変えたとき反応するのは、その色を読んでいたもの だけです。オブジェクトではなく、色です。
変更 material.color
│
├─ レンダラーがその面を塗り直す
├─ それを表示していたインスペクターの欄が更新される
├─ 変更が共同編集者向けのキューに入る
└─ …そのほかには何も実行されない
それで何が得られるか
ポーリングもしませんし、念のための再描画もしません。 何が動いたかを見つけるために、世界を 自分のコピーと比べる毎フレームのパスは存在しません。フレームは変更が要求した仕事だけを行い、 止まっているシーンのコストはほぼゼロです。
大きなシーンでも、編集中に反応が鈍りません。 2 万件のキーフレームを持つタイムラインは、 そのうち 1 つが動いたからといって描き直されません。パネルは、リストの全キーフレームを購読せずに 「キーフレームが変わった」ことだけに依存できます。大きなプロジェクトが実用に耐えるのは、この 違いのおかげです。
読み込みに、読み込み用のコードは要りません。 system はリソースを要求し、まだ届いていなければ 何も返りません。届いたら、system がもう一度走るだけです。コールバックを書く人も、準備完了フラグを ポーリングする人も、「まだ読み込まれていない」場合を二度書く人もいません。
1 本の変更の流れが、すべてを養います。 レンダラー、インスペクター、undo、共同編集者、script の サンドボックス、物理シミュレーション。すべてが同じ更新を読みます。だから互いにずれることも、 シーンの中身について食い違うこともありません。undo も特別扱いの経路ではなく、同じ管を通る もう 1 つの変更です。
エンジンに伝え忘れることができません。 「dirty を立てる」も「refresh」も、覚えておくべき手動の 無効化もありません。値を書くことが、そのまま通知です。
値の更新は、置き換えではなく、すでにあるものへのマージです。だから見ていたものは、見ていた対象に つながったままです。実装の細部に聞こえますが、これはなめらかなアニメーションと、シーンが大きく なるほど落ちていくフレームレートとの違いです。
フレームワークの機能ではなく、土台です
リアクティビティは独立したパッケージで、同じものがあなたにも使えます。プラグインのパネル、 component の拡張、そしてエディター自身のインスペクターも、すべてこれで書かれています。あなたの 書いた拡張が更新される理由は、組み込みのパネルとまったく同じです。
import { signal, computed, effect, batch } from '@was/signals';
const score = signal(0);
const label = computed(() => `Score: ${score.value}`);
effect(() => render(label.value)); // スコアが動いたときだけ再実行される
batch(() => { score.value += 1; score.value += 1; }); // 更新は 2 回ではなく 1 回
3 つの性質には名前を与える価値があります。たいていのリアクティブな仕組みは、そのうち 1 つか 2 つ しか持っていないからです。
| 細粒度 | 依存は読んだ値ごとに追跡され、component 単位ではありません。「念のため」の再描画は起きません |
| 深い | オブジェクトのツリー全体がリアクティブになるので、position.y を見ることはオブジェクトを見ることとは違います |
| 依存が安い | 何かが変わったことに、それを読まずに依存できます。1 件動いただけで巨大なリストが再描画されないのは、これのおかげです |
最後のものは地味に聞こえますが、2 万件のキーフレームを持つタイムラインが編集可能なままである 理由がこれです。
→ 共有パッケージ
1 フレームも食わないバリデーション
どの component もスキーマで記述されます。そしてスキーマはふつう税金を伴います。データが届くたび、 実行時に何かが記述をフィールドごとに辿るからです。
ここでは、スキーマはその形に合わせたコードへ一度だけコンパイルされます。辿る処理はありません。 違いは学術的なものではなく、このプロジェクト自身のベンチマークで測った値です。
| パース | バンドルへの追加 | |
|---|---|---|
| よく使われるライブラリ(zod) | 1 回 94.7 ns | 最小化 +267 KB、gzip +61 KB |
| こちら(svdt) | 1 回 5.0 ns | なし。すでに入っています |
およそ 19 倍速く、来場者が追加でダウンロードするものもありません。
これが効くのは、ここでのバリデーションが稀なイベントではないからです。エディター、script の サンドボックス、物理シミュレーション、共同編集者のあいだを越えるすべての変更で走ります。1 項目 あたりでは無視できそうなコストも、毎秒何千回も起きればアニメーションの予算そのものになります。
自分のプラグインが自分のデータのために import できる、同じパッケージです。 → 共有パッケージ
重い仕事はフレームの外へ
ブラウザーのページを止めがちな 2 つを、外に出しています。
- あなたの script はページから隔離されて動くので、遅いループが描画を凍らせることはありません
- 物理はフレームの中ではなく、その隣で走ります
シーンは忙しいときでも反応し続けます。ロジックが面白くなってきた途端にフレームを落とす、という ことがありません。
ランタイムは、必要になるまで仕事を拒みます
これは来場者にとって、読み込み時間として現れる部分です。
物理は、シーンが本当に必要とするまで起動もダウンロードもされません。 動く部品のない書き割りが シミュレーターの代金を払うことはありません。それは来場者が待たずに済むメガバイトです。判定は 生きていて、セッション中に作られたボールは、自分のために物理を起動させます。
1 万個のコピーのコストは、ほぼ 1 個分です。 草、群衆、瓦礫、パーティクルは 1 回の描画で 済みます。その配置は保存ではなくシードから生成されるので、ファイルにもネットワークにもコストが かからず、どの参加者にも同じに見えます。
パーティクル単位のものが同期されることは一切ありません。 同じ設定はどこでも同じ効果を生むので、 凝った効果もマルチプレイヤーでは無料です。
挙動の語彙はひとつ
イベント・patch グラフ・script は、同じトリガーを聞きます。on-click は 3 つとも同じ意味です。
名前の体系を 1 つ覚えたら、あとはどれだけコードを書きたいかを選ぶだけです。
つまり、インタラクションをイベントとして始め、条件が要るようになったら patch に格上げし、本物の ロジックが要るようになったら script に移れます。何も学び直さずに。
1 人のために作られてはいません
共同編集は後付けの機能ではありません。space はそこにいる全員が共有する、生きたひとつの世界 なので、編集は起きるそばから現れ、undo は各自のものとして働きます。
同じ仕組みが、エディター・script のサンドボックス・物理シミュレーションを同期させます。だから それらがシーンの中身について食い違うことはありません。
あなたの観客がすでにいる場所で動きます
公開したプロジェクトは、リンクから、ブラウザーで、何もインストールせずに開きます。それが WebAR の要点です。アプリストアも、ダウンロードも、「まず私たちのアプリを入れてください」も、 来場者とあなたの作ったものとの間には入りません。
そこからさらに、同じプロジェクトが作り直しなしで遠くまで届きます。
| どこへ | どうやって届くか |
|---|---|
| あらゆるスマートフォンのブラウザー | 公開リンク、または QR コード。インストール不要 |
| iOS の App Clip | リンク・QR コード・NFC から、App Store のインストールなしで開きます |
| ネイティブアプリ | ネイティブレンダラー(下記参照) |
| ヘッドセットのブラウザー | 同じ公開リンク。ランタイムが WebXR を話します |
| AR グラスとヘッドセット | XREAL One・Quest 3・PICO 4 向けのネイティブ XR レイヤー(下記参照) |
| Unity 経由のヘッドセット | Unity SDK — XREAL、PICO、Apple Vision Pro、HoloLens 2、Magic Leap 2、Rokid |
コードにカメラを向けると体験が開きます。ストアもアカウントも待ち時間もありません。Apple はクリップの サイズに上限を設けているので、最初のシーンは軽くしておき、動き出してから残りを読み込んでください。
スマートフォンは最初から動きます。デバイス固有のパッケージ — レンダリングパイプライン、 コントローラーのプロファイル、デプロイのテンプレート — はアカウント単位で付与されます。 → 対応プラットフォーム
スマートフォンでは、ネイティブの描画速度
ブラウザーでは WebGL が使えます。速いですが、天井があります。ネイティブアプリはブラウザーを まったく通さず、iOS でも Android でもネイティブレンダラーを使い、Metal と Vulkan に直接 話しかけます。
だから同じプロジェクトが、ネイティブアプリのグラフィックス予算を手に入れます。リアルタイムの影、 より重いマテリアル、画面上により多くのもの。しかも 2 つ目のバージョンを保守する必要はありません。 マテリアルはウェブ用と同じようにそのレンダラー用にもコンパイルされます。「同じプロジェクト」が 願望ではなく事実なのは、そのためです。
届く範囲のためにウェブ版を、フレーム予算が必要なプロジェクトのためにアプリを使ってください。
グラスにも、ネイティブに
ネイティブの XR レイヤーは、AR Clip のランタイムそのものをグラスに載せます。同じプロジェクト、 同じシーンが、ブラウザーではなくデバイス上で描画されます。
1 つのインターフェースに、差し替え可能なベンダーのバックエンドという形で作られているので、 デバイスはランタイムのフォークではなくバックエンドです。Unity は経由しません。
| デバイス | 経路 | そこで動くもの |
|---|---|---|
| XREAL One | ベンダー SDK | 表示、ヘッドトラッキング、カメラ、セッション記録 |
| Meta Quest 3 | OpenXR | 表示、トラッキング、コントローラー、ハンドトラッキング、セッション記録 |
| PICO 4 | OpenXR | 同じ。ただしカメラはオフ |
バックエンドは、できるふりをせず、実際に対応しているものを申告します。PICO にはここでのカメラ パススルーがないので、背後に現実世界が必要なシーンは XREAL か Quest に置くべきです。必要ない シーンなら 3 つとも動きます。
来場者がどの経路で来ても、component も挙動も同じ、同一のシーンです。ウェブ版とヘッドセット版を 別々に保守することにはなりません。
そして、効く場所で拡張できます
独自のステップやトリガーは、アプリケーションから登録できます。エディターは プラグインを受け付け、パネルを足したり、シーンを生成したり、まったく新しい 種類の component を持ち込んだりできます。ノードのカタログには、唯一カバーしていない場合のための 非常口があります。
このサイトにある component・トリガー・ステップ・ノードの表はすべて、エンジンそのものから生成 されています。エンジンにノードが増えれば、リファレンスに行が増えます。誰も更新を覚えておく必要は ありません。
次へ: オブジェクトとシーン — モデルの全体像。