『じぱんぐライク』のつくりかた、または地図タイルの動的プロシージャル生成

前回の記事「クライアントサイド地図タイルサーバー」の続き、応用実践編です。
RPG風ドット絵日本地図
前回、簡素なヒートマップの実装を通して、サービスワーカーによるクライアント側での地図タイル生成が期待どおりに働くことを確認しました。 そこで今度は、その技術を全面的に用いて『じぱんぐライク』なる地図ウェブアプリを作ってみました。 この記事の主題なので、まずは一度触ってみてください。
おわかりのとおり、日本地図をRPG風のドット絵で表示するものです。 陳腐なネタではあるものの、案外というかそれゆえにというべきか、正面からまともに取り組んだ実装は見たことがありません。 既存の地図アプリにそれらしい皮をかぶせたものがせいぜいです。 実用性も収益性もなさそうなネタを、わざわざコストをかけて一から開発・運営する酔狂はいなかったわけですね。
しかし、地図タイルをクライアント側で生成することにすれば、最も心配なサーバー関連のコストはかかりません。 機能豊富なウェブ地図ライブラリーを無料で利用でき、十分な範囲と粒度のオープン地理データが出そろっており、複雑な処理を爆速でこなすGPUもあらゆる端末に備わっている。 かつての「酔狂」を「当たり前」にする機は熟しているのです。
原理
用語が紛らわしいので、とりあえず次のように呼んで区別することにします。
- 地図タイル … ウェブ地図ライブラリーが“XYZ形式”でリクエストする表示単位
- マップチップ … 16×16ピクセル程度の正方形で描かれる、山・川・草原などを表すドット絵
- メッシュ … 情報が格子状に敷き詰められた地理データ
前記事では、人口メッシュの内容を色分けした地図タイルをサービスワーカーで生成し、ヒートマップとして表示していました。 それと同じ要領で、メッシュを彩色ではなくマップチップで表す地図タイルを生成すれば、RPG風ドット絵日本地図になるはずです。
原理はとてもシンプルですね。 ただし、実装にはこまごました工夫を要します。 以下にかいつまんで説明します。
基本の定義
マップチップは16×16ピクセルとします。 地図タイルは256×256ピクセルのラスター(ビットマップ)画像とします。 ひとつの地図タイルにマップチップがぴったり16×16個収まることになり、処理しやすく見た目もすっきりします。
ズーム時の挙動
まず考えねばならないのが、地図をズームしたときのマップチップの見え方です。
さっき「ひとつの地図タイルにマップチップが16×16個収まる」と決めました。 しかし、これは奇妙な定義です。 ひとつの地図タイルに描かれるべき範囲は、ズームレベルに応じて変化するはずなのですから。
にもかかわらず、常に「ひとつの地図タイルにマップチップが16×16個」であるとはどういうことでしょう。 それはつまり、「マップチップはクラスター的に表示が切り替わる」ということです。 具体的な挙動を言葉で説明しようとするとややこしいのですが、じぱんぐライクを触ってみたなら意味は明らかでしょう。

もしクラスター的でなかったらどうなるか。 地図アプリのズームの画角変化は極端です。 マップチップを敷き詰めたとして、あるズームレベルでは最適に見えても、ちょっとズーム操作したとたんチップが1ピクセル以下にまで縮小されてしまったり、逆に画面一杯に拡大されてしまったりします。 それはそれでいいとの考えもあるでしょうが、わたしの望む挙動ではありません。
なお実際には、地図タイルあたりマップチップは常に16×16個固定ではなく、8×8個や4×4個になることもあります。 やや不規則ですが、ズーム操作していて違和感のないよう調整した結果です。
座標変換
基本の定義にはもうひとつ奇妙な点があります。
地図は日本の公的機関の地理情報メッシュデータ(地域メッシュ)を原典としますが、それらはXYZ形式の地図タイルに合わせた区切りになどなっていません。 もしメッシュデータの格子をそのままマップチップにしたら、「ぴったり16×16個収ま」ってくれません。 微妙にはみ出したり足りなかったりしてしまいます。
よって、地図タイルの座標系できっちり整列するようメッシュデータを調整せねばなりません。 具体的な処理としては、まずメッシュの各格子のID(地域メッシュコード)を経緯度に変換し、それをさらにXYZ形式のタイル番号に変換してやります。
地域メッシュコードから経緯度を求める式は地域メッシュの仕様書(PDF)を、経緯度からタイル番号を求める式は前記事でも触れたオープンストリートマップのウィキを読めばわかります。
メッシュデータの粒度
ところでメッシュの粒度、地域メッシュにおける3次・4次・5次などといった区分はどれを選ぶべきでしょうか。 先に結論をいうと、4次としました。
正確には、XYZ形式タイルにおけるズームレベル16の粒度とします。 ひとマスの大きさは約500メートル四方で、おおよそ地域メッシュの4次に相当します。 なお原典のデータは必ずしも4次だけとは限らず、もっと細かいデータも適宜変換して使っています。
ではなぜその粒度にしたのかといえば、次に述べる、データを扱う側の都合です。
WebGL2.0の制約
地図タイルの生成(描画)には、WebGL2を利用して高速化を図ります。 したがって、データはWebGL2で扱える形式やサイズにせねばなりません。
形式はRGBA8UI(RGBA・各8ビット符号なし整数)テクスチャー画像とします。 そしてサイズは、4,096×4,096ピクセルを上限とします。
WebGL2が扱えるテクスチャー形式は仕様上は多岐にわたるものの、実際には環境・実装に左右されます。 任意の形式が表面的には受け入れられたように見えても、あまり信用できません(エラーにならないのが曲者)。 その形式にネイティブに対応しておらず、float型が内部で整数化されてしまったり、節約のつもりでグレースケールにしてもRGBAに拡張されてしまったりすることがあるのです。 どの環境でも確実に使え、かつ無駄のないテクスチャーはRGBA8UIあたりと考えておくのが無難です。
4,096×4,096ピクセルの上限も同様で、このサイズはWebGL2で最低限扱えることが保証されています。 いまどきそんな最低要件に合わせる必要があるのかといえば、大ありです。 iOS版Safariがまさにその最低の水準なので。 最近の機種では多少緩和されているかもしれませんが(?)、古い機種を使っている人もまだたくさんいるでしょう。 毎度足を引っ張るiOS版Safariですが、前向きに考えれば、これで動くよう作っておけば他のほとんどの環境でも動きそうです。
さて、日本全域をおおよそ3千キロメートル四方とすると、4,096×4,096ピクセルにすっぽり収まるのは3次メッシュ(1キロ四方格子)ということになります。 しかし、1つの格子の情報量を1バイトとしてRGBAのそれぞれに格納すれば、その2×2倍の粒度、すなわち4次メッシュが収まります。 データの粒度を4次メッシュ相当としたのはこれが理由です。

なお実際には、4,096×4,096ピクセルのテクスチャーに、4次メッシュ相当のデータ1つだけでなく、3次メッシュ相当のデータも1つ一緒に詰め込んでいます。 平均すると、4次相当メッシュ1格子あたり10ビットの情報量があるわけです。
そこまでしなくても、テクスチャーを複数枚使えばラクに情報量を増やせるではないか。 そのとおりなのですが、iOS版Safariはテクスチャーサイズだけでなくサイトに割り当てられる総メモリー量そのものが非常に少なく、MapLibre GL JS等ウェブ地図ライブラリーもリソースを相当消費することを考えると、できるだけ節約するに越したことはありません。 また一般論として、リソースを安易に無駄遣いしていると、将来拡張する余地が減ってしまいます。
地図タイル生成のからくり
地図タイルのリクエスト応答にはサービスワーカーを使います。 最大のミソですが、方法は前記事と同じため割愛。 そして地図タイルの描画には、上のとおりWebGL2を使います。
マップチップの描画など大したことないのにWebGLの1ではなく2を選ぶ理由は、2が普及し尽くしており逆に1を選ぶ理由がないからです。 ……それでは身もフタもないので重要な理由をひとつ挙げると、頂点シェーダー内で入力テクスチャーを参照できるからです(1でもできるがオプション扱い)。
16×16ピクセル単位のマップチップを描画するのに、フラグメントシェーダー側でピクセル毎にいちいち入力テクスチャーつまりメッシュデータを読んで描くべきマップチップを判じるのは無駄でしかありません。 他方、頂点シェーダー側でマップチップを決めてしまえば処理量は圧倒的に少なくなります。 地図タイル1つにつきマップチップは16×16=256個なので、各チップを4頂点の正方形ポリゴンとすれば1024回、1頂点のポイントスプライトとすればたった256回の処理で済むのです。 もしフラグメントシェーダー側で処理したら、256×256ピクセル=65,536回です。
ところでマップチップをポリゴンとするかポイントスプライトとするかは、それぞれ一長一短あるため目的に応じて決めます。 じぱんぐライクでは、始めはポリゴンとして作っていましたが、途中でポイントスプライトに変えました。 ポリゴンなら変形など諸々ラクなうえボクセルへの拡張も容易だったりしますが、じぱんぐライクはシンプルな2D表現に徹します。
さらに作りこむ
目指す挙動とデータ構成と地図タイルの生成方法とが決まったので、あとは粛々と実装するだけ。 面倒ではありますが、枯れた技術の組み合わせにすぎず、質的に新奇で困難なことはありません(本当に難しいのは、わかりやすく使いやすいユーザーインターフェイス開発のほう)。
ただ、ネタというのは徹底しないと面白くありません。 せっかく地図タイルを動的に生成しているのだから、4次相当メッシュを超える詳細な地形をプロシージャル生成し、いかにもゲームらしい「人間サイズ」にまでズームインできるようにしてみます。
その目標の人間サイズは、約2メートル四方と定めます。 4次相当メッシュ約500メートル四方の格子を、切りよく256×256マスに分割した大きさです。
地形のプロシージャル生成
地理に詳しいかたもプログラマーのかたも、「地形は再帰的な自己相似形(フラクタル)っぽい」ことはご存じでしょう。 再帰処理プログラミングの解説では、必ずといっていいほど地形生成のトピックが紹介されます。
わたしが生まれる前の教科書にさえ載っている古典的な知見・手法ですが、じぱんぐライクにおける地形生成は基本的に、それを素直に応用しているだけ。
具体的には、たとえば基となるメッシュ格子が「森林」だとしたら、それを分割した256×256マスもまずすべて森林であるとして、その四辺を自己相似的に揺らぐ周期と振幅でずらすのです。
なんとも安直な処理ですが、結果はじぱんぐライクをご覧のとおり、けっこうそれらしい感じになりました。 さすが古典だけあり強力な効果ですね。

なおお察しのとおり、このプロシージャル生成処理は頂点シェーダー内で行っています。 さすがにCPUには重荷なので。 地図タイルの描画にWebGL2を導入し、処理量のより少なく済むポイントスプライトを選んだ最大の理由は、実はこのためでした。 もしプロシージャル生成までしないのなら、はんけトケのヒートマップと同じく2DキャンバスにCPUで描く方法でもとくに問題なかったでしょう。
町のプロシージャル生成は難しい
いっぽう建物用地について、これを村や町や都市らしくプロシージャル生成するのは難しく、いろいろ試行錯誤したものの、素朴なアルゴリズムを一様に適用するにとどまっています。
何が難しいかといって、なにしろ面積があまりにも広すぎ、何をどう工夫してみても、結局は単調な景色が延々続いてしまうのです。 また四角いマップチップを並べる表現上、斜めや曲線的な配置をしにくい、つまり俯瞰ではそれらしく見えてもズームインすると破綻してしまう(見苦しかったり無秩序に見えたりする)ことも解決を難しくしました。
L-systemや都市形態学などの知見、ゲーム開発者会議のそれこそ各種ローグライク作者の発表なども参考にはなるでしょうが、一朝一夕にものになるわけもなく、即時応答の動的生成ではそもそも実行できる処理の複雑さに限界があります。 よってこの件は割り切り、むしろより単純にして軽さをとる方針としました。

乱数の一貫性・再現性に注意
プロシージャル生成の内容は左様に行きあたりばったりで参考にならないかもしれませんが、一般的な注意がひとつあります。 処理に用いる乱数についてです。
疑似乱数・ノイズのたぐいをシェーダー内で生成する際、float型を使うと、環境による型精度の違いで異なる値が返ることがあります。 特殊効果に使う乱数ならかまいませんが、地形生成においては困りますね。 同じ場所なのに、端末によって違う地形が表示されてしまうことになります。
型精度を指定する仕組みは一応あるものの、実際の効果は当てになりません。 よって主要な地形生成に関わる疑似乱数の生成には、整数の式やあらかじめ用意したランダムテクスチャー(要は乱数表)を使うことにしました。 主要でない部分はこの限りではありませんが、大して問題にならないでしょう。
よく知られていることですがついでに触れておくと、JavaScriptの標準乱数にはそもそも再現性がありません(意図的な仕様らしい)。 CPU側処理の乱数にも再現性を求めるなら、適当な疑似乱数式を自前で導入する必要があります。
小さなままのマップチップ
景色の単調さのもうひとつの原因は、マップチップの種類の少なさです。 とはいえ単調さを破るほどのチップ数となると膨大で、簡単に用意できるものではありません(チップ自体をプロシージャル生成する手もあるけれど……)。
昔ながらの次善策として、パレット替えがあります。 じぱんぐライクの内蔵マップチップデータはインデックスカラーで、任意のパレットで描画できる仕組みになっています。 地域や緯度(≒気候)ごとに色を変えてみようと企んだのですが、しかしその程度では結局単調さを免れそうにないのと、次に述べるユーザーの自作マップチップを表示できる機能との兼ね合いで見送りました。
自作マップチップ表示機能
ユーザーの自作マップチップ(タイルセット)で地図を表示するのは、じぱんぐライクにとって重要な機能と考えています。 需要の有無はさておき、それが可能なのは地図タイルのクライアント側生成ならではで、つまりは技術的アイデンティティーを象徴するためです。
ただし、マップチップを一般ユーザーに作ってもらうとなると、あまり複雑な仕様にはできません。 一般的な画像編集ソフトでは、さまざまな独自属性が含まれるゲーム用テクスチャーどころか、パレット替えを見越したインデックスカラー画像すら作るのは難しいでしょう。 したがって、ファイル形式は最もありふれたフルカラーPNGとし、パレット替えの仕様をなくしたのです。
玄人筋には物足りないでしょうが、より多くの人に使ってもらえるよう間口を広げることを優先しました。 マップチップの種類を増やしたくない(むしろできるだけ減らしたい)のも、同じ理由からです。
おわりに
説明は以上です。
こんなものの作り方に正解などなく、たとえばベクターデータを解釈してマップチップを配置したっていいし、そもそもマップチップにこだわらなくてもいいわけです。 書いておいて何ですが、こうした各論は大して重要ではありません。 重要なのは、「クライアントサイド地図タイル生成」や「少ない情報をいかに豊かに見せるか」や「プロシージャル生成による補完」といった考え方をわがものとし、いかに応用するかです。 たとえばサーバー配信とこうしたクライアント側生成とを組み合わせれば、配信情報を大幅に減らしたり、よりリッチで楽しい表現ができたりするかもしれません。
もっといえば、というかわたし本来の志向としては、「非伝統的な地図表現」や「あいまい・架空・役に立たない地図」を作る自由、そうした多様な地図を今や「サーバーレスで簡単・安価に公開できる」ことを示したかったのです。 そんな文句をただ叫んでみたところでだれも見向きもしないので、実証するネタとしてじぱんぐライクを作ってみた次第。 果たして成功しているかわかりませんが、自分ではけっこう気に入っています。


用語・注釈
- 地図タイル、XYZ形式、サービスワーカー
- 前回の記事を参照。
- マップチップサイズについて
- じぱんぐライクではマップチップのサイズを最終的に可変とした(地図タイルラスターサイズを変えてつじつまを合わせる)。 ただし不具合が生じる余地が増えそうなため無段階とはせず、16×16、12×12、8×8ピクセルの3種にのみ対応している。
- クラスター
- 地図アプリにおいては、表示・操作に難があるほど多量の情報群を階層的にまとめて代表表示する働きを指す。 具体機能としては別だがわかりやすい例は、たとえば地図上の行政区のラベルがズームレベルに応じて市区町村名・都道府県名・国名に切り替わるなど。
- WebGL
- GPUを駆動する、ウェブ標準のグラフィックスライブラリー。
WebGLでマップチップのような2D表現をするのは、数学の勉強から始めずに済み入門にも適するはず。 ちなみに、後継と目される「WebGPU」は最近(2025年末ごろ)ようやく全主要ブラウザーが対応した模様。 - 頂点/フラグメントシェーダー
- 「各ポリゴン頂点の画面上座標や属性情報を決定し」、「そのポリゴンをピクセルとして描画する」GPUの処理工程における、前者のコードを頂点シェーダー、後者のコードをフラグメントシェーダーと呼ぶ。
- iOS版Safariについて
- 他の記事でも触れてきたように、何かと厄介。
ブラウザーの多様性は歓迎するものの、ウェブ標準仕様の解釈が“多様”だったり、仕様を取り入れるのが他より数年単位で遅かったり、機器が高価なくせに石器時代のごとき制約があったり、バグが多いのに更新が遅かったり、にもかかわらず他のブラウザーに対して排他的・市場独占的だったりするのは端的に困る。
WebKit(基礎となるオープンソースのブラウザーエンジン)は問題ないのにSafariだけおかしいことも。
なおiOS26版には画像展開に関する目立たないが(それだけにタチが悪い)とんでもないバグがあり、はんけトケのデータ処理はほぼ作り直しを余儀なくされた。 このバグに気づかず誤ったデータを垂れ流しているウェブアプリは多そう。 - プロシージャル(手続き的)生成
- データをアルゴリズムで生成する手法。
手作業を省いたり、無数のバリエーションを生み出したりするために用いられる。
有用な反面、数値的にはユニークでもユーザーにとっては単調でしかない“無数のバリエーション”の乱造に陥る危険もある(「プロシージャル・オートミール」問題)。 - 地形のプロシージャル生成について
- プロシージャルな地形生成やディテールの補完は、大規模なマップを持つ現代のコンピューターゲームでは当たり前に行われている。 その先駆けとして知られるのが、1980年ごろアメリカで開発されたダンジョン探索RPG『Rogue』。 ダンジョンが毎度プロシージャル生成され、繰り返し新鮮に遊ぶことができる。 このコンセプトを受け継ぐゲームは「Roguelike」と総称され、オリジナルの時代に倣ったレトロな表現のものも多い。 じぱんぐライクの命名はこれに由来し(プロシージャル生成部分は架空の地図となるため「日本もどき」との意味合いもある)、表現もいわゆるJRPGではなくローグライクに寄せている。 そのほうがより素朴で制作・開発もしやすい。
- 「CPUには重荷」について
- 実際に試したわけではない。 ターゲットを高性能機器に限ればCPUにさせてもいいかもしれない。 地図タイルの生成処理は常時走るものではないし、地図タイルサーバーからロードするより目立って遅くなければ問題ない。 マップチップ個別にではなく包括的に処理するCPU向けプロシージャルアルゴリズムも考え得るし、WebAssemblyで効率化する余地もある。 とはいえ、せっかく使えるのにGPUを活用しない手はないと思う。
- 自作マップチップ表示機能について
- わが国でドット絵地図に求められているのはむしろJRPG風、言ってしまえば『ドラクエ』風だろう。
しかし権利上そのまま再現するわけにはいかず、また仮にそうしてみたところで、ぱっと見わかりやすい反面いまさら感があり興味に欠けるのではないか(ゲームに非はなくあくまで地図ネタとして)。
よってこちらとしては採用しないが、欲しければユーザーご自身でどうぞ、と委ねるために用意した機能でもあった。
裏返していえば、マップチップの自作をユーザーに動機づけるためにはデフォルトはドラクエ風でないほうがよい(自作の楽しみにとっておくほうがよい)、と考えられなくもない。

- サーバーレス
- 「サーバー構成を何かしら省ける」ことを誇張した営業用語で、実態はさまざま。 ブラウザーに応答する存在が皆無なわけでは無論ない。 ここでは、「大量のデータを提供する地図タイルサーバーを用意しなくてよい」との意味で使っている。
- 「安価に公開」について
- じぱんぐライクのファイル一式は現時点で3メガバイト強(MapLibre等含む)、すべて静的ファイルでデータベースシステム不使用、初回ロード後は通信量も少ない(オフライン動作可)。 したがって各種クラウドサーバーや“サーバーレス”CDN類の無料枠に余裕で収まり、固有のサーバーサイド実行環境に依存することもない。 レンタルサーバーを借りるとしてもごく安いプランで充分、小型マイコンでの自己ホスティングも無理なく可能。 独自ドメインで公開するならドメイン費用がかかる程度。
(最終更新:2026.1.14)