← デモ解説一覧へ戻る

BABYLON LITE 公式デモを読む

斜め見下ろしの世界を、
タイル1枚から組み上げる。

WebGPU 専用の軽量 3D エンジン「Babylon Lite」に付属する、Civ 風アイソメトリックの世界地図。じつは 3D を一切使わず2Dスプライトだけで、本物の Freeciv タイルセットを読み込み、大陸を自動生成し、霧・昼夜・雲・水面まで描き込む——全 19 ファイルのしくみを初心者向けにやさしく解説します。

19ファイル
約4,340行のコード
96×96タイル
15描画レイヤー

第1章

このデモとは

「あの斜め見下ろしの Civ みたいな世界地図、3Dモデルなしで、絵(スプライト)を並べるだけで作れるの?」に、動く実装で答えるサンプルです。

このデモは、Babylon.js ファミリーの WebGPU 専用ランタイム Babylon Lite の公式リポジトリに含まれる、freeciv(フリーシブ)——オープンソースの Civ クローン「Freeciv」の見た目を借りた、アイソメトリック(斜め見下ろし)の世界地図です。本物の Freeciv タイルセット(amplio2)を読み込んで、ダイヤ形のタイルを何千枚も敷き詰め、大陸を自動生成し、都市・道・川・野生動物、そして霧・昼夜・雲・きらめく水面まで描き込みます。

🖼️ 3Dを一切使わない

いちばんの驚きは、このデモがシーンもカメラもメッシュもライトも作らないこと。Babylon Lite の2Dスプライト描画の経路だけで、この立体感あふれる世界を組み上げています。「奥行き」は3Dではなく、ダイヤ形タイルを正しい順で重ねることで生まれる“錯覚”です。

プログラムとしての見どころは、次の3点です。

🗺️ 本物のタイルセット

Freeciv の .spec ファイル(タイル配置の設計図)を自前で解析し、1枚のPNGから何百ものタイル絵を切り出す。“データ考古学”の実践です。

🌍 大陸をその場生成

ノイズと緯度・湿度から、海・草原・砂漠・森・山・ツンドラを描き分け、川を流し、都市を置き、道でつなぐ。シード固定で毎回同じ世界に。

✨ 重ね塗りの演出

戦場の霧・昼夜・雲と影・水面のさざなみ・太陽のきらめき——すべて“世界に貼りついた1枚のシェーダ板”として、15レイヤーに重ねます。

第2章

あそびかた

コードを読む前に、まず地図をぐりぐり動かすのが近道です。マップ探索と、1体だけ動かせる斥候(スカウト)がいます。

🎮 ブラウザで遊ぶ

babylonjs.com のライブデモを開く →(WebGPU 対応ブラウザが必要です。初回はタイルセットの読み込みに少し時間がかかります)

操作すること
ドラッグ地図をスクロール(パン)
マウスホイールカーソル位置を中心にズーム(倍率 0.5〜8)
ホバータイルを水色の枠で強調+地形名のツールチップ
斥候をクリック → タイルをクリック斥候を選択し、経路探索で目的地まで移動
N昼 ⇄ 夜 の切り替え(5秒かけてなめらかに)
ミニマップをクリック/ドラッグその場所へ表示を移動
🏙️ 首都は「Babylon」

生成される世界の中心には、必ず首都 Babylon(人口5)が置かれます。「Babylon 5 へのオマージュ」というコメント付き。起動時のカメラは、この首都に寄って始まります。ほかにも Carthage・Paris・Kyoto が固定配置され、残りは自動で名付けられます。

第3章

用語ミニ辞典

この先の章で出てくることばを先にまとめて紹介します。分からなくなったらここに戻ってきてください。

アイソメトリック

斜め45°の見下ろし視点。正方形のマスを2:1のダイヤ形に描くことで、3Dなしに立体的な地形を表現します。

タイル

世界を敷き詰めるダイヤ形のマス1枚。このデモでは横96×縦48ピクセルの菱形で、96×96マスの世界を作ります。

スプライト / アトラス

スプライト=1枚の絵、アトラス=たくさんの絵を1枚に詰め込んだ画像とその切り出し表。GPUに優しい定番手法。

.spec / amplio2

.spec=Freeciv の「どのタイル絵がシートのどこにあるか」を書いた設計図。amplio2=その定番タイルセットの名前です。

ペインターズアルゴリズム

「奥のものから先に描く」画家の手法。アイソメトリックでは x+y の小さい順に描けば、手前が正しく上に重なります。

セル分割(CELL_CORNER)

海岸線などを、タイルを4つの角セルに割って隣接地形ごとに描き分ける Freeciv の技。境目をなめらかにつなぎます。

値ノイズ / fBm

乱数を滑らかに補間した“自然な凸凹”(値ノイズ)を、周波数を変えて重ねたもの(fBm)。地形・雲・霧の生成に使います。

戦場の霧

まだ見ていない土地を霞ませる演出。斥候や都市の視界の中だけが晴れます。1枚の連続したモヤとして描きます。

スーパーサンプリング

実際より高い解像度で描いてから縮小する手法。ダイヤの継ぎ目に隙間(クラック)が出ない、なめらかなズームの要です。

WGSL / シェーダ板

WGSL=WebGPU のシェーダ言語。このデモは霧・雲・水などを「世界に貼った1枚の板+自前シェーダ」で、ピクセルごとに合成します。

レイヤー順(order)

スプライトを描く重ね順。海(0)→水面(0.5)→海岸(1)→地形…→都市(10)→ユニット(11)→霧(13)→雲(40)→夜(45)…と積みます。

ダイクストラ / 最小全域木

ダイクストラ=斥候の最短経路探索。最小全域木(MST)=都市を最短でつなぐ道路網の作り方です。

第4章

全体の地図

エントリの freeciv.ts(664行)が司令塔。タイルセットを読み、世界を生み、レイヤーを積み、演出を配線して、1本のループを回します。

flowchart TD
    ENTRY["freeciv.ts
司令塔(2Dスプライトのみ)"] subgraph DATA["読む"] SPEC["spec-parser + atlas
10枚の.spec→絵の切り出し"] WGEN["worldgen
96×96の大陸生成"] end subgraph DRAW["描く"] TMAP["tilemap
15レイヤーに配置"] FX["fog/daynight/atmosphere
water/glints/dust…"] PRES["present
なめらかズーム合成"] end LIVE["live
斥候・野生動物"] ENTRY --> DATA ENTRY --> DRAW ENTRY --> LIVE WGEN --> TMAP SPEC --> TMAP TMAP --> PRES FX --> PRES

矢印は「上が下を利用する」向きです。freeciv.ts は、10枚のタイルシートを並列で読み込み→世界を生成し→15枚のスプライトレイヤーを積み上げ→動くものと演出を配線し→毎フレーム、シミュレーション更新・ズーム補間・各演出の更新を回します。「読む」「描く」がはっきり分かれているのが最初の目印です。

🧩 すべてはスプライトの重ね順

3D の奥行きバッファは使いません。かわりに、海・水面・海岸・地形・森・山・川・道・都市・ユニット…と15枚のレイヤーを決まった順で重ねることと、各レイヤー内では x+y の小さいタイルから描く「画家のアルゴリズム」で、立体的な前後関係を作り出します。

第5章

タイルセットを読む

担当:spec-parser.tsatlas.ts。本物の Freeciv タイルセットを、コードで読み解きます。

Freeciv のタイルは、1枚の大きなPNGに格子状に並んでいて、その「どのマスがどのタイルか」を .spec という INI 風のテキストが定義します。spec-parser.ts はこれを自前で解析——シート画像のパス、格子の原点・セル幅・間隔、そして「行・列・タグ名」の対応表を読み取ります。

flowchart LR
    SPEC[".spec テキスト"] --> PARSE["parseSpec
格子geometry+タグ表"] PNG["シートPNG"] --> ATLAS["loadFreecivSheet
共有テクスチャ+アトラス"] PARSE --> ATLAS ATLAS --> FRAME["frameOf(タグ)
= 行×列数+列"]

atlas.ts は、その設計図に沿ってPNGを GPU テクスチャに載せ、グリッドごとにタグ名 → フレーム番号の引き表を作ります。テクスチャはニアレスト(最近傍)フィルタ——ドット絵をくっきり保ち、1px の境界を越えて色がにじまないようにします。10枚のシート(地形・丘・山・海・水・都市・ユニット・動物・選択枠…)から、15枚の描画レイヤーが作られます。

📐 グリッドは1枚に複数

1つのシートに複数の格子が同居することもあります。たとえば water.spec は、川用の 96×48 グリッドと、海岸セル用の 48×24 グリッドを別々に持ちます(海岸グリッドはシート内の x=1, y=437 から始まる)。パーサはこのグリッドごとの独立した原点を正しく扱います。

第6章

斜め格子の座標

担当:iso.ts(79行)。マス目 (x, y) と、画面のピクセル座標を行き来する数学です。

タイルは横 TILE_W = 96・縦 TILE_H = 48 の 2:1 ダイヤ形。マス (x, y) の画面中心は、次の一行で決まります。

// マス目 → 画面ピクセル(アイソメトリック変換)
isoCentre(x, y) = [ (x - y) * 48,   // = TILE_W/2
                    (x + y) * 24 ]; // = TILE_H/2
// 逆変換 worldToTile はこれを解いて四捨五入 → 点の下のダイヤに吸着

この式のおかげで、隣り合うマスが画面上で綺麗な菱形の格子になります。iso.ts はさらに、8方向の隣接マス(DIR8)や、ダイヤのを共有する4方向(EDGES=川や海岸で使う“実質的な東西南北”)、画面方向を Freeciv のスプライト向き記号に変換する表(SPRITE_DIR)を提供します。これらは世界生成と描画の両方が共通で使う、いわば“方位磁針”です。

第7章

世界を生成する

担当:worldgen.ts(526行)。シードから、海に囲まれた1つの大陸を組み上げます。

まず高さの地図を作ります。5オクターブの値ノイズ(fBm)に、中心からの距離で効く放射状の減衰1 - 距離²×1.15)を掛ける——だから縁は海に沈み、中央が陸になります(高さ < 0.32 は海)。そこへ別の低周波ノイズ=湿度と、赤道からの緯度を組み合わせて、11種類の地形を振り分けます。

// 地形の振り分け(抜粋・実際のしきい値)
if (elevation < 0.32) → Ocean          // 海面
if (lat > 0.86)       → Arctic         // 極地
if (lat > 0.72)       → Tundra / Mountains
if (elevation > 0.78) → Mountains
if (elevation > 0.62) → Hills
// 残りは湿度で: 乾→砂漠/平原、湿→湿地/密林/森、中間→草原

地形ができたら、生活を吹き込みます。は高地(高さ > 0.66)から湧き、辺づたいに一番低い隣へ流れ下って海に達したものだけ残す(最大8本/“取り残された水たまり”は消す)。都市は草原か平原に、間隔6マスで最大9つ——中心には首都 Babylon(人口5)、Carthage・Paris・Kyoto は固定座標に。道路は都市どうしを最小全域木で最短につなぎ、約9%の陸地には地形に合った資源や灌漑・鉱山を配置します。

🎲 シード固定で毎回同じ世界

すべての乱数は整数ハッシュから作られ、seed: 7 固定。だから何度開いてもまったく同じ大陸が再現されます。手続き的生成の「決定性」の教科書です。

第8章

レイヤーで描く

担当:tilemap.ts(368行)。生成された世界を、正しい順でタイル絵に置き換えていきます。

描画は奥から手前へx+y の昇順=画家のアルゴリズム)。1マスは、複数レイヤーにまたがる絵の積み重ねとして表現されます。

レイヤー内容
0〜1ocean / water / coast深海・浅瀬・きらめく水面・海岸線
2〜5terrain / forest / hills / mountains基本地形と、せり上がる森・丘・山
6〜9river / road / improvement / special川・道・灌漑や鉱山・資源
10〜12city / unit / animals都市・ユニット・野生動物(足元を接地)
15highlightホバー中のタイル強調枠
🧵 境目をなめらかに(Freeciv の技)

ただ地形ごとに絵を置くだけでは、海と陸の境がカクカクします。そこで CELL_CORNER:浅瀬のタイルを4つの角セルに割り、各角で出会う3マスが陸か水かを見て、境界専用の絵を選ぶ。森・丘・山は CELL_WHOLE の“マッチ”方式で、4辺の隣が同じ地形かをビットにして、輪郭のつながった絵を選びます。道路は「つながっている方向ごとに1枚ずつ」重ね描き。本物の Freeciv と同じタイリング規則を再現しています。

第9章

なめらかズーム

担当:present.ts(167行)。ドット絵タイルを、隙間なく・なめらかに拡大縮小する巧妙な仕掛けです。

問題はこうです。ダイヤ形タイルを中途半端な倍率(例:1.7倍)で直接描くと、タイルの継ぎ目に髪の毛のような隙間(クラック)が出ます。かといってピクセルを整数倍でしか拡大しないと、ズームがガクガクします。

解決は2段構え。まずタイルを、2倍のスーパーサンプリングをかけたオフスクリーンの画像に、継ぎ目が出ない“きりのいい倍率”(0.5/1/2/4/8 のいずれか)で描く。次にその画像を、1枚の板として、実際に見せたい中途半端な倍率へ滑らかに縮小して画面に貼る。こうして「くっきり」と「なめらか」を両立します。描画は ワールド → 合成板 → HUD の3パスで流れます。

第10章

動くもの

担当:live.ts(285行)と dust.ts。止まった地図に、命を吹き込む層です。

動くものは2種類。1つはプレイヤーが操る斥候(スカウト)——命令があるまで待機し、目的地が指示されると、経路を1マスずつ約 620ms/マス で歩きます(滑らかに補間)。選択中は大きさは変えず、透明度がゆっくり明滅して「選ばれている」ことを示します。もう1つは6体の野生動物(狼・熊・豹・虎・ワニ・ゴリラ・蛇から)——都市以外の隣マスへ気ままに歩き回ります。すべてシード固定で、毎回同じ動き。

💨 足元に舞う土ぼこり

dust.ts は、斥候が歩いている間だけ、足元に 95ms ごとに土ぼこりの粒(最大18個のプール)を落とします。粒はワールド座標に固定されるので、斥候は自分の足跡から離れていく。しかも描画順をユニットより下・夜の陰りより下に置くことで、斥候が土煙を踏み越え、夜には足跡もろとも暗くなる——手のかからない自然さです。

第11章

クリックの手ごたえ

担当:pick.tscommandfx.ts。操作に“手ごたえ”を返す、ゲームフィールの層です。

マウスを乗せると、pick.ts がそのタイルに水色の枠を置き、地形・資源・改良・川/道・座標をまとめたツールチップを出します(3Dのピッキング機能ではなく、座標計算だけで実現)。斥候を選んで行き先をクリックすると、freeciv.tsダイクストラ経路探索(道の上は移動コスト 1/3 に割引)が最短路を求め、斥候がそこへ歩き出します。

その瞬間の視覚フィードバックが commandfx.ts。すべて WGSL シェーダで1枚の板に描かれます。

演出中身
目的地のリング暖色の点線(マーチングアンツ)が回転(7本の破線が流れる)
クリックのさざ波淡いシアンのピング(波紋)が広がって 520ms で消える(同時5つまで)
選択中の斥候(ここではなく)ユニット自身の明滅で表現

第12章

戦場の霧

担当:fog.ts(256行)。未踏の土地を霞ませる、あの“霧”です。

ポイントは、霧をタイルごとの絵で置かないこと。もしそうすると、ダイヤの継ぎ目に不自然な段差が出ます。代わりに、世界に貼りついた1枚の連続したモヤの板を用意し、そのシェーダが「タイルごとの視界の強さ」を記録した小さなテクスチャをなめらかに(バイリニアで)読んで、モヤの濃さを決めます。

CPU 側は「踏破済み・現在視界・霧の濃さ」を配列で管理。地図は最初から全域が踏破済みで、見ていない土地は薄い霧(0.72)、視界に入ると 450ms かけて晴れます。視界の半径は都市の周り2マス・斥候の周り3マス。さらにシェーダは、fBm ノイズで霧のふちを揺らして、風にたなびくように見せます。

第13章

昼と夜

担当:daynight.ts(215行)。N キーで、世界が昼と夜を行き来します。

N を押すと、5000ms かけて昼 ⇄ 夜へなめらかに遷移し、到達した側でとどまります(自動ループはしません)。夜は、画面全体を覆う1枚の暗い青の板rgb(0.04, 0.06, 0.18))の不透明度を、時間に比例して最大 0.62 まで上げるだけ。そして日が傾くと、各都市が暖色の光rgb(1.0, 0.82, 0.5))を加算でにじませ、街の灯がともります。

💡 “脈打ち”を消した設計

コメントによれば、以前は夕暮れに暖色を差す演出があったものの、地平線を越える瞬間に「明るくなってから暗くなる」不自然な脈動が出たため、あえて外して一定の寒色に統一したとのこと。判断の理由まで読めるのが、このコードの良さです。

第14章

空・雲・水・きらめき

担当:atmosphere.tswater.tsglints.ts。空と海の“生きた”質感です。

☁️ 雲と影

5オクターブの fBm で流れる雲を1枚の板に描き、少しずらした雲の影を地面に落とす。どちらもズームで寄ると消える(俯瞰のときだけの演出)。夜は影が消えます。

🌊 水面のさざなみ

海タイル1枚ごとに、3つのサイン波を足し合わせたコースティック(水の揺らぎ)を GPU で描画。タイルごとに位相をずらし、隣どうしが同時に光らないように。

✨ 太陽のきらめき

外洋にだけ、固定した太陽の方角へ伸びる細い光の筋がチカチカ瞬く。夜(daylight)になると消灯します。

これらに共通するのが、「世界に貼った1枚の板+自前 WGSL シェーダ」という作り。板のスプライトの色(tint)に「画面上の世界の矩形」や「タイル位置+位相」を忍ばせ、ピクセルごとに模様を合成します。霧・雲・水・きらめきはすべてこの共通テクニックで、CPU の負担をほぼ増やしません。

第15章

古地図と縁取り

担当:backdrop.tsvignette.tsminimap.ts。世界の“額縁”と俯瞰図です。

backdrop.ts は、島の背後に1569年の Mercator 世界地図(パブリックドメイン)を敷きます。暗く落とし、紙のような色をかぶせて“古びた抽象背景”に加工。さらに海岸が外洋へ溶け込むよう、青いにじみ(sea halo)を島の縁に足します。静的で、地図と一緒にパン・ズームします。

vignette.ts は、画面の四隅を暗く落とす周辺減光。中央 50% は明るいまま、隅へ向かってなめらかに暗くして、背景の“余白”を目立たなくします。minimap.ts は右下のミニマップ(長辺180px)——世界を1タイル=1ピクセルのデータ画像として一度に描き、地形色・都市の点・現在の表示範囲(回転した四角形をはみ出さないよう切り抜き)を重ね、クリック/ドラッグでそこへ表示を飛ばせます。

第16章

設計テーマ

19ファイルに散らばって見えて、じつは一貫した4つの考え方が通っています。

🖼️ 2Dスプライトだけで作る

シーンもカメラもメッシュもライトも無し。奥行きは「レイヤー順」と「x+yの描画順」で作る。3Dに頼らず立体を見せる潔さ。

🗺️ 本物のデータに忠実

Freeciv の .spec を自前で解析し、CELL_CORNER や CELL_WHOLE といった本家のタイリング規則を再現。データに忠実だから“らしく”見える。

🎨 演出は「板+シェーダ」

霧・雲・水・きらめきは、世界に貼った1枚の板と自前WGSLで合成。CPUを増やさず、GPUに任せて豊かな空気感を出す。

🎲 決定的な生成

世界も動物の動きもシード固定。整数ハッシュ由来の乱数で、毎回まったく同じ世界・同じ挙動を再現できる。

📝 コメントは「理由の記録」

このコードのコメントには「なぜそうしたか」がていねいに書かれています。夕暮れの脈動を消して一定の寒色にした理由/霧を連続した板にして継ぎ目を消す判断/ズームのクラックをスーパーサンプリングで防ぐ工夫——判断の理由ごと読めるのが、このサンプルの教材価値です。Minecraft・Platformer・Tetris・Sandblox・DOOM デモと共通する“コメント文化”が、ここにも息づいています。

第17章

ファイル一覧(全19ファイル)

コードリーディングのおともに。行数は master ブランチのソース実測値です。freeciv.ts が最大で、合計およそ4,342行。

ファイル行数やくわり
エントリ
freeciv.ts664司令塔。読込・生成・レイヤー・演出・入力・ズーム・経路探索を配線
データ・座標
worldgen.ts526大陸の手続き生成(地形・川・都市・道・資源)
spec-parser.ts160Freeciv の .spec タイル設計図を解析
atlas.ts118PNG+.spec からアトラスとタグ→フレーム表を構築
iso.ts79アイソメトリック座標変換と8方向テーブル
描画・合成
tilemap.ts368世界を15レイヤーのタイル絵に配置(海岸・森・山の境界処理)
present.ts1672×スーパーサンプルのオフスクリーンでクラックのないズーム合成
動き・操作
live.ts285斥候(プレイヤー操作)と6体の野生動物のアニメ
minimap.ts267右下のミニマップと、クリックで表示移動
dust.ts187歩く斥候の足元に舞う土ぼこりの粒
commandfx.ts173目的地の点線リングとクリックの波紋(WGSL)
pick.ts127ホバータイルの強調枠と地形ツールチップ
環境・演出
atmosphere.ts216流れる雲と地面に落ちる雲の影(fBm・5オクターブ)
daynight.ts215Nキーで昼夜(5秒遷移)と都市の灯り
fog.ts256連続した1枚の板で描く戦場の霧(視界2〜3マス)
glints.ts176外洋の太陽のきらめき(昼のみ)
backdrop.ts145背景の Mercator 1569 世界地図と海のにじみ
water.ts120海タイルの水面のさざなみ(GPU コースティック)
vignette.ts93画面四隅の周辺減光(背景の余白を隠す)

行数は master ブランチ(2026年時点)のソース実測値です。合計およそ4,342行(19ファイル)。