第3回で「MVPは完成した」と思っていた。ところが実機のスクリーンショットを並べた瞬間、目を疑った。建物が見切れる。建物が建ったのにタイルが残る。街並みが重なって散らばる。1つずつバグとして直したのに、症状は形を変えて4度も再発した。4度目でようやく腹をくくった。個別のバグを追うのはやめる。設計を、最初からやり直す。真因は個別のバグではなかった。正方形グリッドという座標系そのものが、載せたいアイソメトリックの建物と、幾何学的にかみ合っていなかった。座標系を丸ごと作り直した記録を、包み隠さず開く。この連載で、いちばん学びの大きい回だ。
▶ この連載の全記事はバイブコーディング開発実例(一覧)から。うまくいった話より、この「4度の失敗」のほうが役に立つはずだ。
目次
この連載で作るもの(全6回の地図)
- ① 土台と3レイヤ設計 —
sim/render/uiの分離・グリッド・Canvas描画 - ② AIでドット絵タイルを作る — 生成→均一化→アルファ実測→組み込み、道路の自動接続
- ③ 街を「動かす」— 需要・成長・経済の決定論シミュレーション(MVP完成)
- ④ 4度つまずいて座標系ごと作り直した話(この記事)— 真のアイソメグリッドへの全面移行
- ⑤「面白くない」を4軸で解消する — 目標・施設・イベント・音
- ⑥ 静的ビルドをWordPressへ公開する — 検証と配信、そして連載の総括
第4回のゴール
この記事を読み終えると、なぜ症状が4回も再発したのか、そして座標系を「トップダウンの正方形」から「真のアイソメトリック(2:1のひし形)」へ全面的に作り替えるとはどういうことかが分かる。まず、到達点を見てほしい。ここまで持っていくのに、4度の遠回りがあった。

Step 1. 何が起きたか:4度つまずいた時系列
建物は、トップダウンの地形の上にアイソメトリック(斜め45°見下ろし)のスプライトを重ねる方針だった。問題は、その見え方が実機で何度直しても整わなかったことだ。
- 1・2度目:見切れる:スプライトの下が切れている。切り抜き(クロップ)の品質問題として、生成後の後処理を直した。
- 3度目:建物が建ってもタイルが残る:ようやく
renderer側のバグを特定。縦長ロットのスプライトを1マス幅に圧縮していた。描画幅の計算を直した。見切れとタイル残りは、実は同じ原因の2つの症状だった。 - 4度目:まったく整わない、散らばって見える:今度は複数の建物が隣り合った実際の街並みを見た。建物どうしが重なり、ランダムに散らばって見える。ここで気づいた。直しても直しても再発するのは、個別のバグではなく前提が間違っているからだ。
💡 いちばんの教訓:同じ種類の不具合(見切れる/はみ出す/整列しない/重なる)が、形を変えて2回以上再発したら、個別バグの修正をやめて「土台の前提」を疑う。そして、1個だけを見て「直った」と言わない。3度目まで、私は建物を1棟ずつ離して置いて確認していた。単体では正しく見えた。だが実際の街は「複数が隣接する」。この検証の穴が、4度目まで真因を隠していた。
Step 2. 真因:正方形グリッド × アイソメアートの非互換
アイソメトリックの建物は、奥行きを画面の横幅で表現する。だから同じ「1マス」でも、正方形グリッド上では隣り合う建物の見かけの幅がそろわず、重なりや隙間が必然的に生じる。「見切れる」「タイルが残る」「散らばる」は、すべて1つの根本原因(正方形の升目にアイソメの絵を載せる、という座標系の不整合)が、姿を変えて現れていただけだった。
個別のパッチでは、絶対に直らない。直すべきは座標系そのものだった。都市建設ゲームの定番(SimCity / Cities: Skylines 系)と同じ、真のアイソメトリック=2:1のダイヤモンド(ひし形)グリッドへ移行する。改修は大きいが、これが唯一の正解だと判断した。
Step 3. 大改修の進め方:一気に書かず、段階で検証する
過去に3回パッチが失敗している。同じ轍を踏まないため、進め方を変えた。
- 選択肢を書き出して決める:「①ダイヤモンドグリッドへ全面移行(定番・改修大)」vs「②正方形のまま建物をフラットに作り直し(改修小・見栄え下がる)」の2案を並べ、①に決めた。
- 数式を先に独立検証:投影の数式とファイル別の変更点を、実装前に設計として固めた。
- 段階的に実装し、各段でスクショ確認:一気に全部書いて最後に1回だけ見る、をやめた。地形→道路→建物→実操作、と段ごとに実機で確かめてから次へ進んだ。
Step 4. 2:1ダイヤモンド投影の数式
核心はカメラの座標変換だ。マス目の座標 (gx, gy) を、ひし形に並ぶ画面座標へ写す。1マスは横 HALF_W×2・縦 HALF_H×2 のひし形。右へ1マスは「右下」へ、下へ1マスは「左下」へずれる。これが2:1アイソメの見え方だ。
// src/render/camera.ts(アイソメ移行後)
export const HALF_W = 26; // ひし形の横半径
export const HALF_H = 13; // 縦半径(横の半分=2:1)
// マス目(gx,gy) → 画面px。右+1マスで(+HALF_W,+HALF_H)、下+1マスで(−HALF_W,+HALF_H)
export function worldToScreen(cam: Camera, gx: number, gy: number) {
const dx = gx - cam.offsetX;
const dy = gy - cam.offsetY;
return { x: (dx - dy) * HALF_W * cam.zoom, y: (dx + dy) * HALF_H * cam.zoom };
}
// 逆変換(2×2行列の逆行列・行列式 = 2·HALF_W·HALF_H·zoom² ≠ 0 なので必ず解ける)
export function screenToWorld(cam: Camera, sx: number, sy: number) {
const px = sx / cam.zoom, py = sy / cam.zoom;
const dx = (px / HALF_W + py / HALF_H) / 2;
const dy = (py / HALF_H - px / HALF_W) / 2;
return { x: dx + cam.offsetX, y: dy + cam.offsetY };
}
🔎 座標変換を1ファイルに閉じた恩恵:第1回で「camera を独立させておくと、見せ方を変えるときここだけ直せばいい」と書いた。その伏線がここで回収される。worldToScreen / screenToWorld を書き換え、地形はひし形にクリップ、道路は手続き描画に置換した。それでもsim 層(grid.ts / rules.ts / world.ts)は1行も変えていない。街のルールは座標系を知らないから、座標系を丸ごと替えても無傷だった。層を分けた最大の見返りが、この大改修だ。
Step 5. 建物を「敷地のひし形」に載せる
建物スプライトは、正方形の端ではなくフットプリント(占有マス)のひし形の「下の頂点」を基準に置く。奥にあるものから手前へ描く深度ソートも要る。ひし形投影では、並べ替えの鍵は x + y + w + h(値が小さいほど奥)になる。
// src/render/renderer.ts の drawBuildings(抜粋)
// 奥→手前に並べ替え(ひし形投影のペインターズ・アルゴリズム)
const sorted = items.sort(
(a, b) => (a.x + a.y + a.w + a.h) - (b.x + b.y + b.w + b.h) || a.x - b.x || a.id - b.id,
);
for (const b of sorted) {
const c = footprintCorners(cam, b.x, b.y, b.w, b.h);
const img = /* variant(id%3) → base → 色ブロック の順にフォールバック */;
if (img) {
const drawW = img.naturalWidth * cam.zoom; // スプライトのネイティブ幅で描く(潰さない)
const drawH = img.naturalHeight * cam.zoom;
// w×h ひし形の下頂点は水平中央ではなく、幅の w/(w+h) の位置に来る
const anchorFrac = b.w / (b.w + b.h);
ctx.drawImage(img, c.bottom.x - drawW * anchorFrac, c.bottom.y - drawH, drawW, drawH);
}
}
3度目の失敗(縦長スプライトを1マス幅に圧縮)を、ここで正しく直している。スプライトはネイティブの画素サイズ×zoomで描き、footprint幅へ引き伸ばさない。アンカーを「水平中央」ではなく w/(w+h) の位置に置くのが、ひし形にぴったり載せる鍵だ。
Step 6. AIで「敷地ごと」建物を作る(プレート方式)
建物スプライトは、ローカルの Flux dev(Q8・ComfyUI)+アイソメ用LoRA で生成する(API課金ゼロ)。ここでも大きな方針転換があった。当初は生成物から建物だけを切り抜こうとしたが、アイソメ建物の底辺はひし形(斜め2辺)なので、水平線でどこを切っても「壁が欠ける」か「敷地が残る」。切り抜き自体が構造的に無理だと判明した。
そこで、Flux が必ず描くディオラマ風の敷地ごと採用する方式に転換した(Cities: Skylines のロット方式)。切り抜きを全廃し、敷地のひし形をそのままフットプリントに使う。細長いロットは、生成で向きを制御しようとすると正方形街区に負けるので、被写体の名詞そのものを形状のレバーにした(例:townhouse row/strip mall/warehouse shed)。これが最強の制御だった。
💡 「AIに描かせる」より「後処理で保証する」:生成AIに「必ず◯◯を描いて」と頼むより、保証が要る要素は生成後に手続きで整えるほうが確実だ。第2回の「道路は十字1枚から派生」に続く2例目で、細長ロットの向きは実測してミラーで正規化し、敷地の下頂点が正しい位置に来るよう透明パディングで整えた。生成のガチャに品質を委ねない。


Step 7. 段階検証:「複数隣接」で必ず確かめる
今回は、各段階で実機のスクショを撮ってから次へ進んだ。①地形だけひし形化 → 隙間・重なりゼロを確認。②道路を単独のテストマップで全形状(孤立/直線/カーブ/T字/十字)確認。③建物18種をデバッグ用フックで直接、隙間なく隣接配置して撮影(=4度目に散らばった「複数隣接」の直接検証)。④実際のUI操作で自然に育つのを確認。3度目まで欠けていた「複数を隣り合わせた状態での検証」を、今回は各段で徹底した。これで5度目の失敗を避けられた。
まとめ:第4回の要点
- 再発する症状は前提を疑う:同種の不具合が形を変えて2回以上出たら、個別バグでなくアーキテクチャ(今回は座標系)の不整合を疑う。
- 正方形×アイソメは非互換:真のアイソメには2:1ダイヤモンドグリッド。
worldToScreenを可逆な行列に。 - 層分離が救った:座標系を全面変更しても
simは無傷。第1回の設計が、ここで最大の見返りを出した。 - 建物はネイティブ幅・下頂点アンカー:潰さず、
w/(w+h)の位置に載せ、x+y+w+hで奥から描く。 - 保証は後処理で:切り抜き不能→敷地ごと採用、向きは実測ミラー、被写体名詞で形状を制御。
- 複数隣接で検証:1個だけ見て「直った」と言わない。段階ごとに実機スクショで確かめる。
次回(第5回)は、見た目が整った街に「面白さ」を入れる。実は見た目が整ったあと、自分で遊んでみて「ゲームとして面白くない」と感じた。不満は4つの軸に整理できた。①目標・達成感 ②打ち手 ③変化・驚き ④BGM。都市ランクとミッション、サービス施設と幸福度、ランダムイベントとニュース速報、そして生成AIによるBGM。既存のシミュレーションを壊さずに新しいシステムを積み重ねる設計手法(専用の乱数系列と機能フラグ)まで、まとめて開く。
※ AI生成コンテンツの開示:アイソメ建物スプライトは、画像生成モデル FLUX.1 [dev] + アイソメ用LoRA(Isometrica)でローカル生成し、後処理・検証して使用しています。FLUX.1 [dev] は Non-Commercial License 配布ですが、生成物(Output)の商用利用はライセンス本文 Section 2(d) で明示的に許可されており、その条項を一次確認のうえ使用しています(2026-07-02 確認・モデル本体は再配布していません)。掲載コードは実際のソースからの抜粋です。© 2026 Cooliris(本作のコード・構成・データの権利は作者に帰属します)。