第1章
このデモとは
「マリオみたいなゲームって、どう作られているの?」に、動くコードで答えてくれるサンプルです。
このデモは、Babylon.js ファミリーの新しい WebGPU 専用ランタイム Babylon Lite の公式リポジトリに含まれる、横スクロールアクションのサンプルです。3D は一切使わず、2D スプライト(絵札)を並べるだけの描画で、草原→洞窟→城の3エリアを走り抜けます。ブロックを叩き、スライムを踏み、キノコで大きくなり、スターで無敵になり、最後は城のボスと戦います。画面全体には昔のテレビ(ブラウン管)の走査線がのっていて、レトロゲームの空気そのものです。
プログラムとしての見どころは、次の3点です。
ジャンプの高さはボタンを押した長さで変わり、崖から落ちた直後や着地の直前でもジャンプが「効く」よう、わざと甘さを仕込んでいます。
溶岩・松明の光・炎・火花・CRT は画像を用意せず、時間と座標だけからシェーダで生成。スプライトは CC0 のフリー素材です。
巨大な1枚マップを持たず、エリアごとにタイルと敵をまるごと入れ替え。土管に入ると次のエリアへワープします。
第2章
あそびかた
コードを読む前に、まず遊んでみるのがいちばんの近道です。キーボードでもタッチでも遊べます。
babylonjs.com のライブデモを開く →(WebGPU 対応ブラウザが必要です)
| 操作 | すること |
|---|---|
| ← → / A D | 歩く・走る(後述のダッシュと合わせて加速) |
| Space / Z / ↑ / W / K | ジャンプ(長く押すほど高く跳ぶ) |
| Shift / X / J | ダッシュ。ファイアフラワー取得中はこれで火の玉を撃つ |
| ↓ / S | しゃがむ/土管の上で下入力するとワープ |
| Enter / R | スタート/やられたあとのリスタート |
| タッチ操作 | 画面に十字キーと A(ジャンプ)・B(ダッシュ)ボタンが出る。マルチタッチ対応 |
「ジャンプを長押しすると高く跳ぶ」「走ってからジャンプすると遠くへ跳ぶ」——この2つは第7章・第8章で分解します。触っているうちに体が覚える“手ざわり”が、実は数個の数字でチューニングされています。
第3章
用語ミニ辞典
この先の章で出てくることばを先にまとめて紹介します。分からなくなったらここに戻ってきてください。
2Dの「絵札」。この世界は3Dの立体を使わず、四角い絵札を画面に何百枚も並べて描きます。地面も敵も背景も、ぜんぶスプライトです。
たくさんの小さな絵を1枚の大きな画像にまとめたもの(シール台紙)。切り替えが減って描画が速くなります。このデモは付属の PNG シートを読み込みます。
世界を格子状のマス(タイル)で表す方式。このデモのレベルは、記号を並べた文字の地図から組み立てられます。
「軸に平行な長方形」。当たり判定を回転しない四角形どうしの重なりで判定する、いちばん基本の方法です。
横の動きと縦の動きを別々に処理して壁や床にめり込ませない工夫。まず X を動かして壁で止め、次に Y を動かして床で止めます。
崖から足が離れた「直後」の短い時間だけ、まだジャンプを受け付ける甘さ。崖ギリギリの操作が快適になります。
着地する「少し前」に押したジャンプを覚えておき、着地した瞬間に発動させるしくみ。連続ジャンプがつっかえません。
ボタンを押している間だけ重力を弱めることで、長押し=高く・ちょい押し=低く、を実現するテクニック。
遠くの背景はゆっくり、近くの背景は速く流すことで奥行きを感じさせる手法。空・雲・遠い丘・近い丘の4層で表現します。
GPU の上で動く小さなプログラム。WGSL は WebGPU 用の言語で、溶岩・CRT・スターの虹色など「時間と座標から絵を作る」処理を担います。
火花・スコア表示・破片など、あってもなくても遊べるけれど“気持ちよさ”を足す演出のこと。英語で juice(ジュース)。
音楽を正確なリズムで鳴らすため、少し先の音を前もって予約していく方式。ブラウザのタイマーのブレを避けられます。
第4章
全体の地図
エントリポイントの platformer.ts はほんの数十行。エンジンを起こして game.ts に「あとはよろしく」と渡すだけです。実装のほとんどは巨大な game.ts と、その周りの専門部品にあります。
flowchart TD
ENTRY["platformer.ts
エンジン起動 → game へ委譲"]
GAME["game.ts(2,211行)
状態機械・毎フレームの司令塔"]
subgraph DATA["データ層 ─ 世界の設計図"]
direction LR
LV["level
文字の地図→タイル"]
ENT["entities
敵・ブロック・効果の型"]
FR["frames / constants
物理値・寸法・フレーム名"]
end
subgraph SYS["システム層 ─ 手ざわりと入出力"]
direction LR
PHY["physics
AABB衝突"]
INP["input
キー・タッチ・押下判定"]
AUD["audio
WebAudio合成"]
HUD["hud
DOMのスコア表示"]
end
subgraph FX["描画・エフェクト層 ─ 見た目"]
direction LR
ATL["atlas
スプライト読込"]
PAR["parallax
4層背景"]
PROC["lava / lantern / fire /
portal / juice"]
CRT["crt
ブラウン管仕上げ"]
end
ENTRY --> GAME
GAME --> DATA
GAME --> SYS
GAME --> FX
矢印は「上が下を利用する」依存の向きです。game.ts がすべてを束ねる中枢で、Minecraft デモが「薄い指揮者+部品」だったのとは対照的に、厚い中枢+専門部品という形をしています。横スクロールアクションは「毎フレーム、たくさんの物が同時に絡み合う」ため、状態機械(タイトル→レディ→プレイ中→クリア→…)を1か所にまとめる設計が読みやすい、という判断です。
おすすめは、①frames.ts(物理定数 PHYS と寸法)→ ②level.ts(文字の地図)→ ③physics.ts(当たり判定)→ ④game.ts のプレイ中ループ。小さな部品を先に押さえると、巨大な game.ts が「知ってる部品の組み合わせ」に見えてきます。
第5章
起動の流れ
ページを開いてから世界が動きだすまで、プログラムは短いステップを順番に進みます。エントリの platformer.ts の仕事はここだけです。
flowchart TD
A["① canvas を取得"] --> B["② 読み込み進捗を表示
スプライトシートの取得量を見積もる"]
B --> C["③ エンジン生成
Babylon Lite の2Dスプライト描画"]
C --> D["④ startGame → 世界を用意
スプライトシート読込・レベルcompile・部品生成"]
D --> E["⑤ フレームループ開始!"]
E -.失敗したら.-> X["canvas に赤いエラー画面
等幅フォントで原因を表示"]
エントリポイントは try / catch で全体を包み、もし起動に失敗したら、真っ赤なオーバーレイを canvas に直接描いて原因を等幅フォントで表示します。WebGPU 非対応など「そもそも動かない環境」でも、真っ白な画面ではなく理由が出るようにという配慮です。
Minecraft デモがテクスチャを実行時に fetch していたのに対し、このデモは CC0 のスプライトシートをリポジトリに同梱しています。ネットワークに出ずに済むぶん、起動が安定します。読み込み進捗バーは、そのシート取得の目安(約100万バイト)を見せています。
第6章
レベルのつくりかた
担当:level.ts。レベルは、記号を並べた文字の地図(ASCIIタイルマップ)として書かれています。人間が目で見て編集でき、コードが読み取ってタイルや敵に変換します。
// 高さは全エリア共通で 14 行(ROWS = 14)。横幅はエリアごとに違う // 一例(イメージ): "........................" "...?..BB?B.............." "..........o.o.o........." "......=====....g...n...." "###############..####..F"
1文字が1マス(タイル)に対応します。compileGrid() がこの地図を1文字ずつ走査し、床の当たり判定・地形スプライト・叩けるブロック・コイン・敵・ハザードへ振り分けます。地面タイルには、隣を見て「左端/右端/中央」の絵を自動で選ぶ処理(groundFrame())が入っていて、継ぎ目が自然につながります。
記号の凡例
| 記号 | 意味 |
|---|---|
# | 地面(草・土) |
= | 浮き足場(石の箱) |
B | レンガブロック(叩くと壊れる) |
? | ?ブロック(コイン入り) |
M / S | キノコ入り/スター入りブロック |
o | コイン |
g / n / f | スライム/カタツムリ/飛ぶ敵 |
^ | トゲ(ハザード) |
p / F | プレイヤー開始位置/ゴール旗 |
1枚マップではなく「3つのエリア」
世界はひとつの巨大マップではなく、独立した3つのエリアに分かれています。土管に入ると、いまのエリアのタイル・地形・敵をまるごと片づけ、次のエリアのデータで詰め直します。共有の巨大格子を持たないので、切り替えのコストが軽いのが利点です。
| エリア | 横幅 | ふんいき |
|---|---|---|
| 1-1 オーバーワールド | 124 列 | 青空と丘の草原。基本の操作を覚えるステージ |
| 1-2 ケイブ | 57 列 | 暗い洞窟。ランタンの光と溶岩 |
| 1-3 キャッスル | 30 列 | 城。溶岩地帯とボス戦 |
第7章
動きの物理
担当:physics.ts(当たり判定)と frames.ts の PHYS(数字のつまみ)。マリオらしい走りと跳びは、たった15個ほどの数字で決まっています。
プレイヤーを動かす2段構え
毎フレーム、まず入力から横方向の加速・摩擦を計算し、重力で縦の速度を足します。そして moveAndCollide() が、その速度で四角い当たり箱を動かしながら固体タイルにぶつけて止めます。ポイントは横(X)と縦(Y)を別々に処理する「軸分離」。まず横に動かして壁で止め、次に縦に動かして床・天井で止めるので、角にめり込みません。落ちる床のような一方通行の足場もここで扱い、「上から乗るときだけ」ぶつかるよう判定します。
PHYS ── 手ざわりのつまみ一覧
| つまみ | 値 | 意味 |
|---|---|---|
| gravity | 2600 | ふだんの重力(下向きの加速) |
| jumpHoldGravity | 1300 | ジャンプ長押し中の弱い重力(=高く跳ぶ) |
| jumpSpeed | 884 | ジャンプの初速 |
| maxFall | 1200 | 落下速度の上限(速すぎてすり抜けない) |
| maxWalk / maxRun | 360 / 560 | 歩き/ダッシュの最高速 |
| walkAccel / runAccel | 2600 / 3200 | 歩き/ダッシュの加速の強さ |
| airAccel | 1600 | 空中での方向転換のしにくさ |
| groundFriction | 2400 | 地上の摩擦(離すと止まる速さ) |
| stompBounce | 760 | 敵を踏んだときの跳ね返り |
| coyote | 0.09 | 崖を離れてもジャンプできる猶予(秒) |
| jumpBuffer | 0.11 | 着地前のジャンプ入力を覚える時間(秒) |
| enemySpeed / shellSpeed | 95 / 620 | 敵の歩く速さ/蹴った甲羅の速さ |
寸法の基準 TILE は 70。これは画面のピクセルではなくワールド座標の単位です。レベルの高さ(14行 × 70 = 980)が画面の高さにちょうど収まるよう、描画のときに一括で拡大・縮小します。だから、どんな画面サイズでも「見える範囲のマス数」は変わりません。
第8章
ジャンプの手ざわり
このデモがいちばん丁寧に作り込んでいるのが「ジャンプの気持ちよさ」です。正体は3つの小さな甘さ——可変ジャンプ・コヨーテタイム・ジャンプバッファ。名作アクションはたいてい、これを仕込んでいます。
① 可変ジャンプ ── 押した長さで高さが変わる
ジャンプ中、ボタンを押している間だけ重力を弱め(2600 → 1300)、離すと通常の重力に戻します。長く押せば長く浮いて高く、ちょい押しなら低く跳ねる。1本のボタンで高さを無段階に操れる、シンプルで強力な仕掛けです。
② コヨーテ & ③ バッファ ── 「効いてほしい」を先読みする
人間は崖の端でジャンプすると、たいてい少し遅れてボタンを押します。また着地の直前に次のジャンプを押しがちです。厳密に判定すると、そのどちらも「不発」になってストレスになります。そこで:
ちなみに input.ts 自体は「押した瞬間」を1回だけ拾うシンプルな押下検出(jumpPressed)に徹していて、コヨーテとバッファの時間の管理は game.ts のプレイ中ループが担当します。役割がきれいに分かれているのも読みどころです。
第9章
毎フレームの仕事
ゲームは「状態機械」です。いまがタイトルか、レディか、プレイ中か、クリア演出か——状態ごとに、その1フレームでやることが切り替わります。
flowchart TD
J["ジュース更新(火花・スコア・破片)
状態に関係なく毎フレーム"] --> SW{"いまの状態は?"}
SW -->|タイトル| T["敵を眺めさせる・カメラを流す"]
SW -->|レディ| R["カウントダウン"]
SW -->|クリア/やられ/勝利| A["各種アニメーション"]
SW -->|プレイ中| P["フル・シミュレーション"]
P --> RN["描画:カメラ→背景→全スプライト配置"]
RN --> C["CRTポスト処理へ受け渡し"]
T --> RN
R --> RN
A --> RN
「プレイ中」1フレームの中身
プレイ中はこの順で処理が流れます。順番そのものがバグを防ぐ設計です(例:先に動く床を動かしてから、プレイヤーを床に乗せる)。
// game.ts プレイ中の更新順(要約) タイマー更新(無敵・スター・ワープ・残り時間) 動く床 発射クールダウン・火の玉ショット 横入力(加速・摩擦) ジャンプ(コヨーテ+バッファ) 重力・終端速度 moveAndCollide(当たり判定) 動く床に乗る 落下死・溶岩死の判定 土管ワープ判定 敵の更新と接触 ボスと弾 パワーアップの物理 火の玉の更新 コイン回収 ゴール旗の判定
コイン200点、ブロック破壊50点。敵を連続で踏むと 100→200→400→800→…→8000 とスコアが伸び、8体目で 1UP。コインを100枚集めても 1UP。クリア時は残り時間 × 50 点、城クリアはさらに 5000 点ボーナス——このあたりの数字も、往年の名作へのオマージュになっています。
第10章
敵とパワーアップ
担当:entities.ts(型の定義)と game.ts(振る舞い)。敵もアイテムも、生成・破棄を繰り返さずあらかじめ確保した“枠”を使い回す(プーリング)ので、動作が安定します。
| 敵 | 倒しかた・性質 |
|---|---|
| スライム(g) | 基本の敵。上から踏むと倒せる |
| カタツムリ(n) | 踏むと甲羅に。甲羅を蹴ると滑って他の敵を巻き込む。しばらくは自分に当たらない猶予つき |
| 飛ぶ敵(f) | 横に流れながら上下に波打つ(振幅・周期が定数で調整) |
| ピラニア | 土管から一定周期で顔を出す(3.4秒サイクル) |
| ボス(城) | 大きなクモ。HP3・無敵点滅・弾を最大4発。踏むかファイアで削る |
パワーアップ
体が大きくなる(当たり判定も背が高くなる)。ダメージを受けると小さい状態に戻る。
一定時間、無敵。虹色にパレットが循環し、残像トレイル(6枚・3フレーム間隔)が尾を引く。シェーダで実装。
火の玉を撃てるように。跳ねながら飛び、敵を貫く。同時に出せるのは2発まで、発射間隔0.28秒。
? ブロックとレンガは叩くと下から突き上がるアニメがつき、レンガは大きい状態のプレイヤーが叩くと砕けて回転する破片(デブリ)を撒きます。破片・火花・スコア表示はすべて固定数の“プール”から貸し出され、寿命が来たら visible=false にして枠へ返します。
第11章
スプライトと背景
担当:atlas.ts(絵札の読込)と parallax.ts(奥行きのある背景)。ここだけは画像素材(CC0 の Kenney 製)を使います。
絵札を1枚のシートから切り出す
スプライトは Kenney の「Platformer Art Deluxe」を使います。この素材は絵がびっしりすき間なく詰め込まれたシート(TexturePacker 形式)なので、格子で機械的に切るのではなく、付属の XML を自分で読んで「この矩形が coinGold」「ここが alienBeige_walk1」と1枚ずつ位置を拾い出します。詰め込みの境目でにじまないよう、UV座標もていねいに調整しています。
4層のパララックス背景
背景はタイルを並べるのではなく、新機能の uvScroll(テクスチャの表示位置をずらす機能)で「横に無限スクロールしても追加コストゼロ」を実現しています。丘の稜線は 2 つの正弦波を 0.65×a + 0.35×b で混ぜたなめらかな波、雲は種 0x51a7 の疑似乱数で毎回同じ位置に5つ配置し、端で途切れないよう巻き取りコピーを添えています。
第12章
描かずに作るエフェクト
このデモの美点は、多くの見た目を画像に頼らずコードで生成していること。時間 t と画面座標 uv だけを入力に、シェーダや手続きで“描いて”います。
テクスチャを一切使わず、断片シェーダだけで生成。正弦波を重ねた流れ、ぶくぶくした表面のシルエット、立ち上る火の粉、陽炎のゆらぎを、時間と座標から計算する。
洞窟の暗さは、画面全体を暗くする「乗算の黒い板」に、プレイヤーの周りだけ丸くくり抜いた光だまりを重ねて表現。壁の松明は加算合成のゆらぐ光。
ファイアフラワーの絵も、ふわっと光る火の玉のテクスチャも実行時に生成。火の玉は加算合成なので、重なると明るくなる“弾らしさ”が出る。
コイン取得や踏みつけで弾ける金・白の火花、砕けたレンガの回転破片、ふわっと上がるスコア数字。どれも小さなプールから貸し出す使い回し。
エリア切り替えの瞬間は、中央の円だけを残して外側を黒く塗るシェーダ(アイリス・ワイプ)でスッと閉じ、次のエリアで開きます。円の半径を fx.params.x で動かすだけの、こちらも“描かない”演出です。
第13章
CRTブラウン管
担当:crt.ts。最後にもう一段、画面全体を昔のブラウン管テレビのように加工して、レトロな空気をまとわせます。
やり方は「2段階レンダリング」です。まずゲーム本編をいったん画面ではなく“オフスクリーンの絵”(レンダーテクスチャ)に描き、次にその絵を CRT シェーダ越しにもう一度スプライトとして貼り直します。エンジンに新しく入った「オフスクリーン描画のフック」を最初に使った機能でもあります。
| 効果 | 内容 |
|---|---|
| 樽型のゆがみ | 画面をほんの少しふくらませる(角は画面内に残る控えめさ) |
| 色ずれ | 赤と青を左右にわずかにずらす(色収差) |
| 走査線 | 横方向のうっすらした縞。ゆっくり流れる |
| アパーチャマスク | 蛍光体のピッチを思わせる縦の細かい模様 |
| ブルーム・信号帯・粒子・ちらつき | にじむ光、流れる暗い帯、砂嵐、電源ハムのような微妙な明滅 |
あえてヴィネット(四隅を暗く落とす処理)は入れていないのがこだわりで、「画面のすみずみまで明るく」を選んでいます。効果の強さは fx.params.x で 0(素通し)〜1(全開)まで調整可能。この CRT は Aka 氏の MIT ライセンス「CRTFilter」を WGSL へ忠実に移植したものです。
第14章
音を合成する
担当:audio.ts。効果音もBGMも音声ファイルは1つもなく、すべて WebAudio のオシレータ(発振器)とエンベロープでその場で合成します。まさにチップチューンです。
マスター音量(0.22)の下に、BGM専用のサブミックス(0.8)をぶら下げ、音楽が効果音の下にちゃんと敷かれるようにしています。効果音は、ジャンプ(420→760Hz の矩形波スイープ)、コイン、踏みつけ、ブロックの突き上げ、パワーアップ、やられ、1UP、クリアのジングル、ワープ、火の玉、ブロック破壊……と、往年の作品を思わせるラインナップです。
ズレないリズムの秘密:ルックアヘッド
BGMは「8分音符のグリッド」に音を並べた2曲——オーバーワールド(132 BPM、ハ長調の I–vi–IV–V)とケイブ(104 BPM、イ短調でもの悲しい)——がループします。それぞれ主旋律・ベース・和音の分散が重なります。ここで setInterval にそのまま頼るとリズムがブレるので、約30msごとに目を覚まし、約120ms先までの音をあらかじめ予約する「ルックアヘッド・スケジューラ」で、正確な時刻に鳴らします。ブラウザのタイマーのブレを、先読みで吸収する定番のテクニックです。
第15章
設計テーマと歴史書
約4,900行を貫くのは、この3つの考え方です。自分のプロジェクトにもそのまま持ち帰れます。
可変ジャンプ・コヨーテ・バッファのように、「正しさ」より「気持ちよさ」を優先した小さな甘さを、数個の数字(PHYS)で集中管理。触り心地はコードで作れる。
溶岩・光・炎・火花・CRT・音は、画像や音声ファイルを持たず、時間と座標から生成。リポジトリを重くせず、パラメータで無段階に調整できる。
巨大な1枚マップを避け、エリアごとにデータをまるごと入れ替え。プールしたスプライトを片づけて詰め直すので、切り替えが軽くて破綻しない。
フォルダには improvements.md という開発日誌が同梱されています。「スターの無敵をどう“まばゆく”したか」「12枚のタイル巻き取りから手続き生成のパララックスへ」「土管ワープでエリアを分ける発想」「洞窟の明かりと溶岩」「初のオフスクリーン描画=CRT」——完成形だけでなく、試行錯誤の経緯ごと読めるのが、このサンプル最大の教材価値です。Minecraft デモのコード内コメントと同じ“歴史書”文化が、ここにも息づいています。
第16章
ファイル一覧(全18ファイル)
コードリーディングのおともに。行数は master ブランチのソース実測値です。game.ts だけで全体の約4割を占めます。
| ファイル | 行数 | やくわり |
|---|---|---|
| エントリ/中枢 | ||
platformer.ts | 44 | エンジンを起こして game に委譲。エラー画面つきの薄い入口 |
game.ts | 2,211 | 状態機械と毎フレームの司令塔。ゲームのルールはほぼここに集約 |
| データ層 | ||
level.ts | 554 | 文字の地図を3エリア分ぶんパースしてタイル・敵・地形に変換 |
entities.ts | 178 | 敵・ブロック・弾・火花・スコアなど、全エンティティの型 |
frames.ts | 65 | 物理定数 PHYS・TILE・スプライトのフレーム名・タイミング補助 |
constants.ts | 148 | 寸法・敵の挙動・パーティクル数などの調整値とスター用シェーダ |
| システム層 | ||
physics.ts | 111 | 軸分離のAABB衝突(moveAndCollide / overlaps)。一方通行の足場も |
input.ts | 178 | キーボード+タッチ。押した瞬間の検出とフレーム消費 |
audio.ts | 323 | WebAudioで効果音とBGMを合成。ルックアヘッド・スケジューラ |
hud.ts | 151 | スコア・コイン・時間・残機・ボスHP。DOMで実装(UI機能がないため) |
| 描画・エフェクト層 | ||
atlas.ts | 163 | TexturePacker形式のスプライトシートをXMLごと読み込む |
parallax.ts | 244 | 空・雲・遠い丘・近い丘の4層背景。uvScrollで無限スクロール |
crt.ts | 224 | ブラウン管シェーダ。走査線・色ずれ・樽ゆがみ(ヴィネット無し) |
lava.ts | 92 | 溶岩。時間とUVだけの手続きシェーダ |
portal.ts | 80 | 土管ワープとアイリス・ワイプの遷移演出 |
fire.ts | 91 | ファイアフラワーと火の玉のテクスチャを実行時生成 |
lantern.ts | 58 | 洞窟の光だまりと松明の加算グロー |
juice.ts | 49 | 火花パーティクル用のきらめきテクスチャ生成 |
※ このほかに開発日誌 improvements.md が同梱されています(コードではありません/381行)。行数は master ブランチ(2026年時点)のソース実測値です。