第1章
このデモとは
「ブロックを置いて世界を作って、その中を自分で歩けたら楽しいよね?」を、そのまま動くコードにしたサンプルです。
このデモは、Babylon.js ファミリーの WebGPU 専用ランタイム Babylon Lite の公式リポジトリに含まれる、Sandblox(サンドブロックス)です。名前のとおり「サンドボックス(自由に遊べる砂場)」+「ブロック」。緑の大きな土台(ベースプレート)の上に、スタッド(丸い突起)付きのブロックを置き、動かし・伸ばし・複製し・塗り・壊し——そして四角い人形になって、作ったばかりの世界を歩いて跳んで確かめられます。作った世界はブラウザに自動保存され、ファイルとして書き出し/読み込みもできます。
プログラムとしての見どころは、次の3点です。
土台もブロックも、正体はすべて同じ Part。位置・大きさ・色・回転を持つだけの素直なデータで、描画も当たり判定も1本の道に集約されています。
5つの編集ツール(移動・複製・リサイズ・削除・ペイント)と、歩ける四角い人形。作る側と遊ぶ側が1つの画面に同居する“ミニ Roblox”。
何百個のブロックも1個ずつ描かず、GPU へまとめて転送。スタッドの突起も、効果音も、すべてコードでその場生成——外部アセットは最小限です。
第2章
あそびかた
コードを読む前に、まず触ってみるのがいちばんの近道です。「歩く操作」と「作る操作」の2系統があります。
babylonjs.com のライブデモを開く →(WebGPU 対応ブラウザが必要です)
歩く(キャラクター操作)
| 操作 | すること |
|---|---|
| W A S D / ↑ ↓ | 前後左右に移動(カメラの向きが「前」・速さ16 studs/秒) |
| Space | ジャンプ |
| 右ドラッグ | カメラを旋回(三人称ぐるり視点) |
| ← → | カメラを左右に旋回(キーでも回せる) |
| マウスホイール | ズームイン/アウト(半径 1〜50 studs) |
作る(ビルドツール)
| 操作 | すること |
|---|---|
| 1 移動 / 2 複製 / 3 リサイズ | 下部ツールバーのツールを選ぶ(4 削除 / 5 ペイント) |
| Esc | ツールを解除(何も選んでいない状態へ) |
| ドラッグ中の R / T | 掴んだブロックを90°回転(R=ヨー/T=ピッチ) |
| Export / Import map | 作った世界を map.json に書き出し/読み込み |
ブロックは常に1スタッド単位でピタッと吸着します。しかも「偶数サイズなら整数の中心、奇数サイズなら半整数の中心」に合わせるので、幅の違うブロックでもズレずに積めます。土台の外(±256 studs)には出られません。
第3章
用語ミニ辞典
この先の章で出てくることばを先にまとめて紹介します。分からなくなったらここに戻ってきてください。
ブロック上面に並ぶ丸い突起のこと。LEGO やあの積み木でおなじみ。長さの単位としても使われ、1スタッド=ブロック1マス分です。
この世界の最小単位オブジェクト。大きさ・位置・回転・色・ロック状態を持つだけの素直なデータ。土台もブロックも中身は同じ Part です。
足場になる緑の巨大な板。じつは 512×16×512 のロックされた Part 1枚で、上面がちょうど高さ0に来ています。
同じ形のメッシュを、位置と色だけ変えて何百個も一括描画する GPU 機能。1個ずつ描くよりずっと高速です。
傾いていない直方体の当たり判定。パーツの選択・衝突・積み重ね判定の土台になります。
マウスの位置から3Dの光線を飛ばし、最初に当たったパーツと面を求める処理。クリック=レイキャストです。
奥行きの精度を上げるため、深度を反転させて扱う方式(近=1・遠=0)。この光線計算もそれ前提で書かれています。
回転を4つの数で表す方法。このデモの回転は90°刻みだけなので、面の向きが必ずワールドの軸に一致します。
既存の標準シェーダに自前のコードを差し込むしくみ。スタッドの突起は、このプラグインで描いています。
「その点は形の内側?外側?どれだけ離れてる?」を返す関数。丸角スタッドの断面を、この距離で作ります。
表面の細かな凹凸を、法線(面の向き)情報として持つテクスチャ。平らな面でもスタッドの陰影が立ちます。
物理エンジンを使わず、重力・ジャンプ・着地を自前の計算で処理する方式。軽くて予測しやすいのが利点。
選択したパーツに付く操作用の目印。リサイズ用の6個のボールや、選択枠の線がこれです。
音声ファイルを一切使わず、コードから波形を合成して効果音を鳴らす方法。このデモの音は全部これです。
第4章
全体の地図
エントリの sandblox.ts は、エンジン・空・光を用意し、「ワールド」「キャラクター」「ツール群」の3つを組み立てて描画を回します。
flowchart TD
ENTRY["sandblox.ts
エンジン・シーン・空・光"]
subgraph WORLD["ワールド(作られる側)"]
direction LR
WS["workspace
全パーツの台帳"]
PART["part
1個のブロック"]
REND["part-renderer
一括描画"]
STUD["stud-*
突起の質感"]
end
subgraph PLAYER["キャラクター(遊ぶ側)"]
direction LR
CHAR["character
四角い人形"]
PC["player-controller
歩く/跳ぶ/カメラ"]
end
subgraph BUILD["ツール(作る道具)"]
direction LR
TB["toolbar
5ボタン"]
TM["tool-manager
1つだけ有効"]
TOOLS["move/clone/resize
paint/delete"]
end
ENTRY --> WORLD
ENTRY --> PLAYER
ENTRY --> BUILD
PART --> REND
REND --> STUD
TOOLS --> WS
PC --> WS
矢印は「上が下を利用する」向きです。中心にいるのは workspace.ts——世界にある全パーツの唯一の台帳です。ツールは「クリックした先に何があるか」をここに問い合わせ、キャラクターは「どこにぶつかるか」をここから受け取ります。作る側と遊ぶ側が、この1枚の台帳を通してつながっています。
おすすめは、①part.ts(1個のブロックとは)→ ②workspace.ts(台帳)→ ③part-renderer.ts(描画)→ ④ツール群。小さな部品から読むと、大きな仕組みが「知ってる部品の組み立て」に見えてきます。
第5章
起動の流れ
ページを開いてから世界が現れるまで、sandblox.ts は次の順で準備を進めます。
flowchart TD
A["① エンジンとシーンを用意
空色 (0.58, 0.79, 0.98)"] --> B["② 光と影・空のスカイボックス"]
B --> C["③ 台帳(workspace)と描画器(part-renderer)"]
C --> D["④ ベースプレート 512×16×512 を1枚置く"]
D --> E["⑤ 保存データを復元
無ければ default-map.json を取得"]
E --> F["⑥ 四角い人形を組み立て・影に登録"]
F --> G["⑦ ツール群とツールバーを配線"]
G --> H["⑧ PlayerController を起動しループ開始"]
特徴的なのは④と⑤です。足場の緑の板は特別扱いではなく、「ロックした大きな Part を1枚置くだけ」。そして⑤で、前回ブラウザに保存した世界があればそれを復元し、なければリポジトリ同梱の default-map.json をネット越しに取ってきて並べます。URL に ?fresh=1 を付けて開くと保存を消して初期マップから始められます。
sandblox.ts 本体にカメラを作るコードは出てきません。三人称カメラは camera-controller.ts の中で、キャラクターと一緒に生まれます。「シーンの準備」と「遊ぶ人の視点」を分けておく設計です。
第6章
パーツという単位
担当:part.ts。この世界にあるものは、キャラクターと空をのぞいて全部これです。
Part が持つのは、大きさ size・位置 position・回転 rotation(クォータニオン)・色 color・ロック状態 locked だけ。あらゆる変更は必ず setSize / setPosition / rotate90 / setColor といった専用の入口を通り、その中で「描画器に新しい姿を書き込む」「変更を通知する」の2つを確実に行います。データと表示がズレない、いちばん基本の作法です。
| 性質 | 値・ふるまい |
|---|---|
| デフォルトの大きさ | [2, 1, 1](横長ブロック1個) |
| デフォルトの色 | [0.16, 0.5, 0.73](青) |
| 最小サイズ | 各辺 MIN_SIZE = 1 スタッド未満にはならない |
| 回転 | rotate90("y")=ヨー、"x")=ピッチ。90°刻みのみ |
| ベースプレート | 512×16×512、上面が高さ0、locked: true |
| 当たり判定 | getAABB() で世界座標の直方体を返す(90°刻みなので厳密) |
生まれるとき Part は、描画器に「1枠ください」と allocInstance で場所を確保し、workspace(台帳)に自分を登録します。消えるとき(destroy)は逆に、枠を返し台帳から抜けます。この登録・解除の対称性が、何百個のブロックを破綻なく出し入れできる秘密です。
ベースプレートは locked: true。ロックされたパーツはクリックの対象にならず(=ツールで動かせない)、セーブにも含まれません。でも当たり判定には残るので、その上をちゃんと歩けます。「触れないけど、そこにある」を1つのフラグで表しています。
第7章
一気に描くしくみ
担当:part-renderer.ts(334行)。何百個のブロックを、たった数回の描画命令で出す心臓部です。
コツは thin instancing。単位立方体のメッシュを1つだけ用意し、それを「位置・回転・大きさ」の行列と「色」を差し替えながら大量に複製します。ブロックが100個あっても、送るのは100個ぶんの行列と色の配列だけ。描画命令の数はブロック数に左右されません。
このデモは用途別に3つのプールを分けています。混ぜないことで影の計算が破綻しないよう工夫しています。
| プール | 用途 |
|---|---|
caster | ふつうのブロック。影を落とし、影を受ける |
receiver | ベースプレート専用。影を受けるだけ(巨大なので影の範囲計算から隔離) |
wedge | 斜面ブロック(スタッドなし)。専用メッシュ |
アルファ値に「面の向き」を隠す
各インスタンスは 16個の行列+RGBAの色を持ちます。ここで面白いのが、色のアルファ(A)は透明度ではなく「どの面が上か」を表す点。(面番号 + 0.5) / 8 という式でこっそり埋め込み、シェーダ側がこれを読んでスタッドを描く面を決めます(番号6=スタッドなし)。使っていないチャンネルに情報を相乗りさせる、賢いパッキングです。
// 上向きの面を、色のアルファに符号化して忍ばせる // 面番号 0:+X 1:-X 2:+Y 3:-Y 4:+Z 5:-Z、6以上=スタッドなし alpha = (faceIndex + 0.5) / 8;
ソースには重要な注意書きがあります。標準マテリアルの thin instance は、そのままだと「一度描いた内容」がキャッシュされ、実行中に動かした・伸ばした・複製したブロックが更新されないままになります。そこで enableThinInstanceGpuCulling を呼び、描画をキャッシュ束から外して毎フレーム転送させています。これが無いと、編集しても画面が変わりません。
もう一つの地味な要は枠の使い回し。ブロックを消すと、内部では「最後の枠を空いた場所へ詰める」入れ替え方式で穴を埋めます。このとき色の配列も一緒に詰め替え、外から見える番号(ハンドル)は変わらないよう対応表を更新——だから途中でどれを消しても、他のブロックの参照が壊れません。
第8章
スタッドの質感
担当:stud-texture.ts と stud-material-plugin.ts。あの「丸い突起」を、テクスチャ1枚とシェーダで作ります。
まず stud-texture.ts が、突起1個ぶんの小さなタイル(64×64 ピクセル)をコードで描き起こします。丸角四角の SDF(符号付き距離関数)から「高さの地図」を作り、そこから2枚を生成——平らな面よりわずかに明るいグレーの色テクスチャ(下地の明るさ 0.82/突起の頂上 1.0)と、傾きを表すノーマルマップ。継ぎ目なく敷き詰められるよう、端は反対側を参照して計算しています。
| 定数 | 意味 |
|---|---|
TILE_PX = 64 | スタッド1個のタイル解像度 |
STUD_HALF_SIZE = 0.25 | 突起の半径(タイル比。実幅は 0.5) |
CORNER_RADIUS = 0.12 | 角の丸み |
BEVEL_PX = 2 | ふちの立ち上がりの鋭さ |
NORMAL_STRENGTH = 8.0 | ノーマルマップの陰影の強さ |
次に stud-material-plugin.ts が、共有マテリアルの標準シェーダに WGSL の断片を差し込みます(マテリアルプラグイン)。第7章でアルファに隠した「上向きの面番号」をここで復号し、その面が本当に上を向いているか(法線との内積が 0.995 以上か)を確かめて、スタッドの色と法線を合成。90°刻み回転なので、面が必ずワールドの軸に一致する——だから面ごとの向きのテーブルが正確に効きます。ブロックを倒せばスタッドも一緒に横を向きます。
実物のブロックも突起は上面だけ。全面に描くと積んだとき突起がめり込んで見えます。「その部品のローカルな“上”」1面にだけ描くことで、回転しても積んでも自然に見えるわけです。
第9章
光と空
担当:lighting.ts と world.ts。晴れた屋外の空気感を、2灯+空でつくります。
| 要素 | 設定のねらい |
|---|---|
| 環境光(ヘミスフェリック) | 上向き、強さ 0.8。空の色 [1.0, 0.9, 0.75]・地の色 [0.3, 0.35, 0.45] で全体を沈ませない |
| 太陽(ディレクショナル) | 方向 [0.25, -1, 0.9]・強さ 0.5。斜め上から差し、影を落とす |
| 影(PCF) | 解像度 2048、bias 0.0012、範囲 0.1〜150。毎フレーム更新 |
| 空(スカイボックス) | TropicalSunnyDay の6面画像、サイズ 10000 |
| 背景クリア色 | (0.58, 0.79, 0.98) の水色。空とスカイボックスがなじむ |
影は動く世界に合わせて毎フレーム描き直す設定です(forceRefreshEveryFrame)。誰がその影を落とすかは setShadowCasters で登録——キャラクターの全メッシュと、ブロック用・斜面用の描画メッシュが対象です。巨大なベースプレートを影の“落とす側”から外しているのは、それを入れると影の範囲が広がりすぎて解像度が無駄になるからです。
第10章
ブロック人間
担当:character.ts(647行・最大のファイル)。あの四角い人形を、角を丸めた箱と円柱だけで組み立てます。
このファイルは「形を作るだけ」で、動きは一切持ちません。土台(root)は足元=高さ0にあり、そこから各パーツを積み上げます。腕と脚は「ピボット(回転の支点)」からぶら下げ、少し下にずらして取り付けることで、肩・股から振り出せるようにしています。
| パーツ | 寸法・位置 |
|---|---|
| 胴(シャツ) | 箱 2×2×1、位置 (0, 3, 0) |
| 腕 ×2 | 箱 1×2×1、ピボット (±1.5, 3.8, 0) |
| 脚 ×2 | 箱 1×2×1、ピボット (±0.5, 1.8, 0) |
| 頭 | 丸めた円柱、位置 (0, 4.5, 0)。顔テクスチャ付き |
| 肌の色 | [0.96, 0.8, 0.19](“ヌーブ”イエロー) |
顔は 128×128 のキャンバスにコードで描いた笑顔テクスチャ。白を透明に抜き、黒で目(楕円)と口(二次曲線のスマイル)を描き、頭の正面に貼ります。すべての箱・円柱は角を bevelRadius 0.05 でわずかに丸め、輪郭にやわらかい光の線を出しています。
第11章
歩く・跳ぶ・見まわす
担当:player-controller.ts が、入力・物理・移動・アニメ・カメラの5つの部品をまとめ、毎フレームの処理順を仕切ります。
flowchart LR
IN["input
キー/マウス"] --> PH["physics
重力/ジャンプ/着地"]
PH --> MV["movement
水平移動/衝突/段差"]
MV --> FS["footstep
足音"]
FS --> AN["animation
手足を動かす"]
AN --> CAM["camera
視点を更新"]
この順番には意味があります。先に上下(重力・着地)を解決してから左右(水平移動・衝突)を処理し、カメラは最後に確定した位置を追う。1フレームの経過時間は最大 0.05 秒に頭打ちして、重い瞬間でもすり抜けないようにしています。
WASD をカメラ基準の向きに変換して 16 studs/秒で移動。向きは 13 rad/秒でなめらかに追従。高さ 1.2 までの段差は自動でよじ登り、それ以上は壁として滑ります。
物理エンジンなしの手計算。重力 196、ジャンプ初速 50。足元で最も高いブロック上面に吸着。高さ -80 を割ると (-20, 0, -20) へ復活。
三人称のアークローテート。半径 1〜50、頭の高さ(+3)を注視。地面にめり込みそうになると自動でズームインし、離れると戻ります。
キャラクターの当たり判定は、幅 0.5×0.5・高さ 5 の箱。ブロックの AABB と重なったら、はみ出しの小さい軸へ押し戻す「スライド衝突」で、壁に沿って自然に滑ります。
第12章
手足のアニメ
担当:animation-controller.ts。3つの動きをコードだけで作り、なめらかに切り替えます。
アニメの正体は、腕・脚のピボットを X 軸まわりに回すだけの手作りクリップ。外部のモーションデータは使いません。状態はキーを押した/離した・跳んだ・着地したといったイベントで切り替わり、切り替わるたびに 150ms かけて前の動きへブレンド(クロスフェード)します。
| 状態 | 中身 |
|---|---|
rest(待機) | 60フレームのループ。腕を ±0.044 rad でごくわずかに揺らす |
walk(歩き) | 20フレームのループ。腕・脚を ±π/5 で交互に振る(腕は同じ側の脚と逆位相) |
jump(跳躍) | ループなし。両腕を -π×0.98 まで一気に上げる |
「歩き→待機」「待機/歩き→跳躍」「跳躍→着地」の遷移は、キーの状態を毎フレーム調べるのではなく、物理側や入力側が投げるイベントに反応する形。ポーリングではなくイベント駆動なので、状態のとり違いが起きにくい素直な作りです。
第13章
5つのツール
担当:toolbar.ts と tools/ フォルダ。作る道具は、画面下のボタンと数字キーで切り替えます。
要は tool-manager.ts。「必ず1つだけ有効」を守るため、切り替え時はまず古いツールを止めてから新しいツールを起動します。各ツールは activate() / deactivate() だけを持つ最小の約束(Tool インターフェース)で、共有の道具箱(マウス・ドラッガー・選択枠・音)を受け取ります。
| キー | ツール | すること |
|---|---|---|
| 1 | 移動 Move | 掴んで、面に沿ってドラッグ。1スタッド刻みで吸着 |
| 2 | 複製 Clone | 真上の柱の頂上に複製を生成(連打で塔になる)。“ポン”と鳴る |
| 3 | リサイズ Resize | 6個のボールを掴んで、その面の向きに1スタッドずつ伸縮 |
| 4 | 削除 Delete | クリックで消去。爆発音。ロック(土台)は消えない |
| 5 | ペイント Paint | 14色パレットから選んで塗る。ホバー中は枠が色見本に |
ツールバーは 1〜5 で選択、Esc で解除。同じボタンをもう一度押すと解除になります。ボタンは click ではなく mousedown で反応し preventDefault する——こうしてキャンバスからフォーカスを奪わないので、ツールを持ったままでも WASD で歩けます。
14色の「クラシック」パレット
ペイントの色は、あの積み木でおなじみの定番14色。paint-tool.ts に定数として並んでいます。
第14章
マウスで掴むしくみ
担当:mouse.ts・dragger.ts・ray-helpers.ts と adornments/。「クリックした先のブロック」を、どう突き止めているのか。
核は mouse.ts——ツール用マウス操作の唯一の窓口です。マウスが動くたびに画面の点から3Dの光線を作り(ray-helpers.ts、reverse-Z 前提のアンプロジェクト)、台帳にレイキャストして「今どのパーツ・どの面・空間のどこ」を指しているかを更新。その結果を、登録された各ハンドラへ「消費できるイベント」として順に配ります。誰かが consumed を立てたら、そこで配布は止まります。
リサイズ用の6個のボール(handles.ts)は、どのツールよりも先にマウスへ登録されます。だからボールを掴んだクリックはハンドルが「消費」し、ツールには届きません。おかげで、平地のクリックは選択、ボールのクリックはリサイズ、と自然に切り分けられます。
掴んで動かす部分は dragger.ts が担当(移動ツールと複製ツールが共用)。カーソル下の面にブロックを吸い付かせ、1スタッドのグリッドにスナップし、土台の外(±256)へは出さない。ドラッグ中に R / T で90°回して置き直せます。選択中のブロックを囲む青い枠線(selection-box.ts、12本の辺)も、これらのツールが共通で使う目印です。
| 部品 | 役割・数値 |
|---|---|
mouse.ts | ピッキングの窓口。ロックされたパーツは対象外 |
dragger.ts | 1スタッド吸着・±256クランプ・R/T回転 |
ray-helpers.ts | 画面→世界の光線と、光線×AABB の交差計算(reverse-Z) |
handles.ts | 面ごとの6個のボール(径 0.9・隙間 1.0)。1スタッド刻みでドラッグ量を発火 |
selection-box.ts | 選択枠(12辺・厚み 0.12)。ツールごとに色が変わる |
第15章
音の合成
担当:sounds.ts(247行)。効果音は1つも音声ファイルを使わず、WebAudio でその場合成します。
AudioContext は最初に音を鳴らす瞬間に遅延生成し、WebAudio が使えない環境(ヘッドレステスト等)では静かに何もしません。波形はサイン波やノイズを、フィルタと音量エンベロープで加工して作ります。
| 音 | 作り方(抜粋) |
|---|---|
| 複製のポン | 915Hz と 935Hz のわずかにずれたサイン波2本+余韻のエコー |
| 削除の爆発 | ブラウンノイズの“ドン”(1.35秒)+ローパス・ピーキング+出だしの破裂音 |
| リサイズのカチ | ハイパスをかけたごく短いノイズのチッ(0.035秒) |
| 足音 | 強弱パターン [1,3,3,2,3,2] を巡回。毎回ランダムに揺らす |
| ジャンプ | バンドパスのノイズを 380→780Hz へ掃引した“シュッ” |
足音は歩きクリップに合わせ、着地している間だけ 1/3 秒ごとに再生。[1,3,3,2,3,2] という強弱の並びで、単調にならない歩きのリズムを作っています。
第16章
保存と持ち運び
担当:persistence.ts・world-io.ts・map-io-ui.ts。作った世界を残し、ファイルとして持ち運びます。
心臓は world-io.ts が定めるたった1つの保存フォーマット。自動セーブ(localStorage)も、書き出し/読み込みも、default-map も、全部この同じ形を使うので互換性が保たれます。キー名は短く詰め、ロックされたパーツ(=土台)は保存しません。
// world-io の JSON(version 1) { version: 1, parts: [ { s:[2,1,1], // size 大きさ p:[0,0.5,0], // position 位置 q:[0,0,0,1], // quaternion 回転 c:[0.16,0.5,0.73], // color 色 sh:1 // shape 1=斜面(省略=ブロック) } ] }
persistence.ts は、パーツが変わるたびに 250ms だけ待ってまとめて保存(デバウンス)し、タブを閉じる直前にも書き出します。読み込み側は防御的で、version が違ったり値の形が壊れていたら、その要素を飛ばして「1未満なら1に丸める」と安全に倒します。map-io-ui.ts の Export/Import は同じ形式なので、保存データとエクスポートしたマップは互換。右上のリーダーボード(Builder 1000〜9999/スコア0)は、雰囲気づくりの飾りです。
第17章
設計テーマ
35ファイルに散らばって見えて、じつは一貫した4つの考え方が通っています。
土台もブロックも斜面も同じ Part。台帳(workspace)も描画も当たり判定も1本に集約。種類を増やさず、フラグと形で表現する。
ツールは activate/deactivate だけ、当たり判定は getAABB() だけ。細い interface で結ぶから、部品を足しても全体が揺れない。
スタッドのテクスチャも、効果音も、キャラの顔も、コードで生成。外部依存は空の画像と初期マップくらい。読めば全部わかる。
編集ツールとキャラクター操作は独立し、共有するのは「台帳」だけ。だから片方を読むとき、もう片方を気にせず読める。
このコードのコメントには「なぜそうしたか」がていねいに書かれています。GPUカリングを呼ばないと編集が反映されない/巨大な土台を影の落とし手から外す理由/90°刻みだから面の向きテーブルが厳密——判断の理由ごと読めるのが、このサンプルの教材価値です。Minecraft・Platformer・Tetris デモと共通する“コメント文化”が、ここにも息づいています。
第18章
ファイル一覧(全35ファイル)
コードリーディングのおともに。行数は master ブランチのソース実測値です。character.ts が最大で、合計およそ5,293行。
| ファイル | 行数 | やくわり |
|---|---|---|
| エントリ | ||
sandblox.ts | 149 | エンジン・空・光・ワールド・キャラ・ツールを組み立ててループ開始 |
| ワールドとパーツ | ||
part.ts | 249 | 1個のブロック。大きさ・位置・回転・色・ロックを持つ最小単位 |
part-renderer.ts | 334 | thin instancing による一括描画。3プール・面の向きをアルファに符号化 |
workspace.ts | 118 | 全パーツの唯一の台帳。レイキャストと衝突源を一手に提供 |
lighting.ts | 63 | 環境光+太陽+PCF影(2048)の照明リグと影キャスター登録 |
rigid-body.ts | 20 | 衝突用インターフェース(毎フレーム AABB を返す約束) |
world.ts | 17 | 空のスカイボックス(TropicalSunnyDay)を読み込む |
| スタッドの質感 | ||
stud-texture.ts | 198 | 丸角SDFから継ぎ目なしのスタッド・タイル(色+法線)を生成 |
stud-material-plugin.ts | 77 | 標準マテリアルにWGSLを差し込み、上面だけにスタッドを描く |
| キャラクター | ||
character.ts | 647 | 四角い人形を丸め箱・円柱で組む(形だけ・笑顔テクスチャ) |
player-controller.ts | 119 | 5部品を束ね、毎フレームの処理順と状態機械を仕切る |
movement-controller.ts | 206 | カメラ基準WASD(16studs/s)・スライド衝突・段差の自動よじ登り |
animation-controller.ts | 217 | rest/walk/jumpの手作りクリップとクロスフェード(150ms) |
input-manager.ts | 172 | プレイヤー移動のキー/マウス入力と移動開始/停止イベント |
physics-controller.ts | 120 | 手計算の重力(196)・ジャンプ(50)・地面吸着・落下復活 |
camera-controller.ts | 90 | 三人称アークローテートカメラ・ズーム・地面めり込み回避 |
events.ts | 49 | コントローラ間の型付きイベント発行器(jumped/landed等) |
| 編集ツール | ||
toolbar.ts | 239 | 下部の5ボタン・数字キー・Escのツール切替UI |
tools/paint-tool.ts | 143 | 14色パレットで塗る。色見本DOMを生成 |
tools/resize-tool.ts | 130 | ハンドルを掴んで面方向に1スタッド刻みで伸縮 |
tools/clone-tool.ts | 105 | 真上の柱の頂上に複製(連打で塔)。掴んだまま配置 |
tools/move-tool.ts | 60 | 掴んでドラッグ移動。ホバーで選択枠を表示 |
tools/delete-tool.ts | 50 | クリックで破壊+爆発音(ロックは対象外) |
tools/tool-manager.ts | 31 | 常に1つだけ有効。古い→止めて新しい→起動 |
tools/tool.ts | 26 | Toolインターフェースと共有道具箱ToolContext |
| 掴む・当たり判定 | ||
adornments/handles.ts | 328 | 面ごとの6個のボール。1スタッド刻みのドラッグ量を発火 |
ray-helpers.ts | 263 | 画面→世界の光線とAABB交差(reverse-Z前提) |
dragger.ts | 158 | グリッド吸着ドラッグ(移動/複製で共用・R/T回転) |
adornments/selection-box.ts | 147 | 選択パーツを囲む12辺のワイヤ枠(ツールごとに色) |
mouse.ts | 139 | ツール用マウスの唯一の窓口。消費可能なレイイベントを配布 |
| 音・保存・UI | ||
sounds.ts | 247 | WebAudioで全効果音を合成(音声ファイル0) |
persistence.ts | 116 | localStorageへデバウンス自動保存(250ms)と起動時復元 |
map-io-ui.ts | 103 | map.jsonの書き出し/読み込みボタン |
world-io.ts | 88 | 唯一の保存フォーマット(version 1)。防御的な読み込み |
leaderboard.ts | 75 | 右上の飾りのスコア表(Builder 1000〜9999/スコア0) |
行数は master ブランチ(2026年時点)のソース実測値です。合計およそ5,293行(35ファイル)。README を含めると36ファイル。