見出し画像

ニンジャのゲームを作る - ③ローグライクなマップの生成

←前回

ダンジョンの構造を考える

画像1

ダンジョン生成のプログラムを書くにあたり、まずはこの完成予想図をもとに構造を考えていきましょう。唐突に質問です。このマップはで構成されているでしょうか?


……正解は部屋通路です。見たままですね。ですが、こうして切り分けて考えてみるのは、構造を考えるにあたってとても重要です。予想図通りのものを作るなら、壁を作るプログラム部屋を作るプログラム、そして通路を作るプログラムがあればいいと分かったのですから。

では、早速順番に取り掛かっていきましょう。


[1/3] 「壁を作るプログラム」を作る

まず「壁を作るプログラム」とは何か考えてみましょう。

前回説明したとおり、表示されている床と壁は、内部的にはによって構成されています。床が0壁が1です。となれば、壁を作るプログラムとはすなわち、1を並べ立てるプログラムということになります。

ここでもう一度、典型例のマップをご覧ください。

画像2

このマップは横に48マス縦に36マスの連なりによって構成されています。これはつまり、内部的には横に48文字縦に36行の1が連なっていることを意味します。

最終的にマップを完成させるには、48×36の文字列に、それぞれ適切な数字を当てはめて行かなければなりません。気が遠くなりそうです。ですが、今作る必要があるのは壁だけです。今作っているのは、壁を作るプログラムに過ぎないのですから。

そういうわけで、虚無の暗黒に囚われる前にササっと進めてしまいましょう。48個の1を横に、36個の1を縦に、それぞれ並べます。これを前回作ったプログラムに当てはめ、出力させると……

画像3

このように一面の壁が出来上がりました。ヤモト=サンは壁の中に埋まってしまいましたが、特に問題ありません。第一段階はこれで終了です。次は、この壁の中に部屋を作るプログラムを書いていきます。


[2/3]  「部屋を作るプログラム」を作る

さて、続けて一面の壁の中に部屋を作っていきます。ところで再度質問です。このプログラムにおける部屋とは何でしょう?


……正解は床の集まりです。一定数の床が長方形、ないしは四角形などに集まっている状態を、ここでは部屋と称します。

部屋が何かが定まったところで早速作っていきましょう。部屋を構成するのは床。つまりはです。先述の一面の壁マップに手を加え、一定範囲の壁を床に……1を0に置換すれば、部屋を作ることが出来ます。

まずは、置換のためのパラメータを設定します。偉そうに言っていますが、要は4つの数字を決めるだけです。部屋の左端上端、そして高さです。具体例を見てみましょう。

とりあえず左端14上端10幅4高さ10と設定しました。この数値を視覚化すると、以下のような範囲となります。

画像4

あとは、この通りの範囲の1を0に置き換えれば良いわけです。これで1つの部屋が完成します。が、流石に部屋が1つきりと言うのは寂しいですし、物足りません。なので、左端、上端、幅、高さを一定の範囲でランダムに生成し、部屋を繰り返し製作することにします。繰り返しはプログラムの得意分野です。

もちろん無制限に部屋が作られては収拾がつきませんので、部屋の最大数を別途定めます。今回は12にしました。プログラムはきっちり12回、以下の動作を実行します。

◆部屋作成プログラム◆
「ランダムに左端、上端、幅、高さを設定する」
「その範囲の1を0に置き換える」

これで12個の部屋が出来上がる……と良かったのですが。

画像5

途中で妙な事態になり、ストップさせました。3つの部屋が出来上がっていますが……なんだか違和感がありますね。ええ、そうです。表示されているのは4つの部屋です。部屋と部屋が重なってしまっているためです。

これを防ぐためには、プログラムの途中にチェックを挟みます。

◆部屋作成プログラム◆
「ランダムに左端、上端、幅、高さを設定する」
「その範囲の1を0に置き換える」

◆部屋作成プログラム◆
「ランダムに左端、上端、幅、高さを設定する」
「その範囲内に0があるかを確認し、あれば中断する」
「なければその範囲の1を0に置き換える」

実際の置換に移る前に、該当範囲に床があるかをチェック。あれば中断するようになりました。これで一安心……

画像6

ではありませんでした。部屋と部屋の重なりは回避されたはず。では何が起こったか……? 理由は簡単です。部屋と部屋がぴったり隣り合って配置されたため、不自然な形の1つの部屋があるように見えているのです。

先述の問題こそ解決されているものの、これで良いわけもありません。今度はチェックの範囲を拡大することで、隣り合うことを防止するとともに、通路に必要なスペースも確保することにします。

◆部屋作成プログラム◆
「ランダムに左端、上端、幅、高さを設定する」
「その範囲内に0があるかを確認し、あれば中断する」
「なければその範囲の1を0に置き換える」

◆部屋作成プログラム◆
「ランダムに左端、上端、幅、高さ(範囲A)を設定する」
「範囲Aを3マス拡大し(範囲B)、範囲B内に0があるかを確認し、あれば中断する」
「なければ範囲Aの範囲内の1を0に置き換える」

と、このように変更した結果……

画像7

無事、部屋を作成することに成功しました。「アレッ、プログラムが12回繰り返されるなら、部屋は12個じゃないの?」と思われるかもしれませんが、これでOKです。

なぜかと言うと、このプログラムには「中断」が組み込まれているからです。12回繰り返すとは設定したものの、実際12部屋はかなり多いです。そのため、部屋を作るチェックに失敗=中断とすることで、出来上がりの部屋数がランダムになるようにしたのです。

プログラムは12回の作成を繰り返し、1〜12(1度目のチェックは失敗する可能性がありません)の部屋を作成します。これは平均的に4〜6個程度に収束します。

それに、際立って大きい部屋が作られれば、それに応じてチェックの難易度も上がり、作られる部屋数も自然と減少します。不運なプレイヤーさんが大きな部屋だらけのマップを歩き回らされる……なんて事態が起こる可能性も減らせるのです。

部屋を作ることができたら、最後は通路を作って、マップを完成させましょう。


[3/3]  「通路を作るプログラム」を作る

いよいよ製作も終盤です。まずは例によって通路とは何か、を考えてみましょう。

通路は部屋と同じく、床によって構成されます。ですが、その目的が大きく違います。通路は部屋と部屋を行き来するための床なのです。となれば、部屋と部屋を結ぶ範囲の壁を床に置換えれば、通路を作ることができます。

ですが、通路を作成するにあたっては、いくつか考えるべきことがあります。例えば、どの部屋とどの部屋を結ぶべきか? です。

画像8

このように、下手にランダム指定すれば、マップの端から端まで繋がるような、異様に長い通路が出来上がってしまう可能性があります。ですので、今回は最寄りの部屋と部屋を結ぶことにします。

整理できたところで、製作に取り掛かりましょう。まずは最寄りの部屋がどれか、をどうやって判断するか、考えてみます。

最寄りの部屋を求める計算には、先述の部屋データ……左端、上端、幅、高さを利用します。まずはこれらのデータから、基準となる部屋の中心座標を求めます。偉そうに書きましたが、やることは「直径10cmの円がある時、半径は何cmか」の程度の計算です。

画像9

左端に幅の半分を足し合わせれば、横軸の中心点。上端に高さの半分を足し合わせれば、縦軸の中心点が求められます。あとは、これらの中心点を他の部屋のものと比較していきます。横と縦、それぞれの差の合計がもっとも小さくなる部屋が、最寄りの部屋です

画像10

基準部屋は横が11、縦も11。部屋1は横が、縦が24。横の差は、縦の差は13です。これを足し合わせると、差の合計は16あると分かります。他の部屋も同様に調べた結果、最小は部屋1の16だと分かりました。つまり、部屋1こそが最寄りの部屋です。

これで最寄りの部屋は見つかりました。しかし、これだけでは通路を作ることはできません。なぜかと言うと、部屋のどの方角から通路を作ればいいか、が分かっていないからです。

「そんなの一目瞭然じゃん!」と思われるかもしれませんが、プログラムに目はありません。ここはしっかりと数字で判断する方法を考え、プログラムに教えてあげましょう。

どの方角から通路を作ればいいか? 長いので今度からは最寄りの方角と呼ぶことにしましょう。最寄りの方角を求めるには、基準となる部屋の四方の座標を求め、最寄り部屋の対応する座標と比較します。……漢字が多く、少しややこしいので、順を追って見ていきましょう。

四方の座標とは、部屋の四方の端……つまりは左端と右端上端と下端を指します。このうち左端と上端はすでに分かっています(部屋を作る時に決めたので当然なのですが……)必要なのは残りの右端と下端の座標です。

では、ここでもう一度問題です。右端と下端を求めるにはどうすればいいでしょう? わからない場合は、部屋を作る際のプロセスに、答えが隠れてるかもしれません……


……正解は、部屋の左端上端に、高さを足し合わせることです。右端と下端を求めることができたら、続けて基準となる部屋と最寄りの部屋の対応する座標とは何か、について見てみましょう。


通路に必要なのは、部屋の四方と四方の間の距離です。これは右端と右端、下端と下端という意味ではありません。画像で見てみましょう。

画像11

こういう風に、同じ方角を比較しても、最短距離を調べるのに必要な結果は得られません。必要なのは、基準部屋の下端なら最寄り部屋の上端、基準部屋の右端なら最寄り部屋の左端と比較した数値です。

それで部屋と部屋の間の各方角の距離が求まります。あとはそれらをリストアップし、最小のものを調べれば、最寄りの方角が手に入ります!

これで通路作成に必要なデータが集まりました。あとは最寄りの部屋の、最寄りの方角へと通路を作るだけです。ここでは下に通路を作る場合を考えてみましょう。横の開始地点を決め、あとは基準部屋の下端と、最寄り部屋の上端の間を、一気に0に置換してしまえば……

画像12

通路が完成しました! これを各部屋で繰り返せばローグライク風マップの出来上がり……

画像13

……なのですが、どうにも味気ないですね。通路が単純な直線だと、なんだか見た目にも面白みに欠けますし、通路を塞いで敵を閉じ込め、曲がり角から斜めに矢を撃ち続けて倒すような、セコ……頭脳派なプレイもできなくなってしまいます。

ここは乗りかかった船です。通路を作る際、曲がり角が生まれるようにしてみましょう。色々と方法がありますが、ここは通路を半分づつ作り、合間を埋める方法を取りたいと思います。

やることは先ほどとほぼ同じです。まず、基準部屋の下端最寄り部屋の上端その間の範囲から、ちょうど真ん中の座標を取得します。これを中心点と呼びます。

画像14

中心点が決まれば、次は先ほどのように通路を作っていきます。ただし、今回は、横の開始点はそれぞれ別、通路の終着点は中心点までです。

画像15

通路が作成されました。続いて、中心点から左右に通路を広げていくことで、通路と通路の合間を埋めていきます。中心点の左右を、双方の横の開始点まで広げていけば……

画像16

曲がり角のついた通路が出来上がりました。今度こそマップを作るためのプログラムが完成です! ……ですが念のため、もう何度かダンジョンを生成してみましょう。予想外のバグが起こるかもしれません。

まあ、そんなことは滅多に……

画像17

起こりました。

……勘のいい読者さんの中には、途中で原因を気づかれていた方もいるかと思います。最寄り部屋同士をくっつけていった結果、離れた部屋同士がいっさい接続しないまま残ってしまったのです。水上フロアでもないのに、こうした孤立する部屋が存在しては困ります。

なので、今度はマップ内の部屋をすべて通路でつなぐ方法を考えてみましょう。


[4/3] 「部屋と部屋をひと続きにするプログラム」を作る

予想工程数をオーバーしてしまっていますが、続けましょう。

まずは例によって、マップ内の部屋がすべて通路でつながれているとはどういう状態かを考えてみましょう。すこし抽象的ですが、これは通路を通って、すべての部屋と行き来できる状態とも言い換えられます。

裏を返せば、完成後のマップを歩いてみて、すべての部屋を通ることができるかをチェックすれば、この問題の発生は防ぐことができそうです。では、そのチェックをどうやって行うか、考えてみましょう。

画像18

以前までの出来上がりの状態です。特定の部屋同士が仲良く結びついていますが、互いに交流はありません。当然、このままではすべての部屋と行き来することは不可能です。それは一目瞭然ですが、プログラムは知らないので教えてあげましょう。

それには必要なのは、今まで作成した部屋と通路のリストです。

画像19

通路のリストには、その通路がどの部屋とどの部屋をつなぐかがリストアップされています。これを用い、仮想的にすべての部屋を歩かせるテストを行ってみましょう。

まずはスタート地点を設定します。今回は部屋1です。そこから自身とつながっている通路を通り、その先の部屋からも同じように通路を通り、入れた部屋をリストアップしていきます。……うん?

あとは最終的に入れた部屋のリストと、すべての部屋のリストを見比べれば入れた部屋リストから漏れた部屋、すなわち繋がっていない部屋が……


ややこしい!


ややこしいので順を追って見ていきましょう。はじめにスタートする部屋を設定し終えたら、その部屋番号を含む通路を確認します。

画像20

今回は1本のみ、2の部屋へつながる通路が確認できました。それでは続けて、2の部屋からつながっている通路を確認します。ただし、すでに確認した通路……スタート地点へつながる通路は無視です。すると、このようになりました。

画像21

3の部屋を含む通路は2つ。ですが、これらはすでにチェック済み。ここで確認作業は終了です。最終的にリストアップされた部屋は「1・2・3」の3つ。これをすべての部屋のリストと見比べると、「4・5・6」が抜けていると分かります。

であれば、「4・5・6」「1・2・3」のいずれかをつなぐ通路を作れば、問題は改善するはずです。再度、通路を作るプログラムを起動し、「4・5・6」のどれかから……

画像22

確認済みの部屋「1・2・3」の中から最寄りの部屋を求め、通路を作ります。

状況が変わったので、もう一度チェックをしてみましょう。今度は「1→2→3→6→5→4」……とすべての部屋が網羅されています! 網羅できていない場合は、再度先ほどのプロセスを繰り返しますが、今回は不要です。これで今度こそ、マップ生成のプログラムは出来上がりです!


終わりに

今回はゲーム中に必要なダンジョンの生成を行いました。次はこのダンジョン内に、アイテムや階段などを配置していく手順を解説していこうと思います。(小説を書いたりするので、次回はすこし遅くなります)

続きを読む→

それは誇りとなり、乾いた大地に穴を穿ち、泉に創作エネルギーとかが湧く……そんな言い伝えがあります。