【ウディタ講座】ローグライクなダンジョン自動生成の作り方

ローグライクゲーの可能性は、インディーズで今まさにゴリゴリを開拓されております。

高度なものをいきなり作るのは大変ですが、ふしぎのダンジョン系の基本的なダンジョン自動生成のやり方からつかんでいくのはよい経験になるかもしれません。

勉強してみたので、その過程を書き残しておきます。

再開拓されつつあるローグライクというジャンル

さまざまなローグライク

さいきんはインディーズ発でローグライク〇〇〇というゲームが増えてきました。ローグライクACT、ローグライクSRPG、ローグライクRPG……となんでもござれなのです。

 

下記リンクにそういったものがまとまっております。

STEAMでおすすめする中毒性抜群のローグライクゲーム18選

『片道勇者』もラインナップに!

 

ウディタでのローグライク

『片道勇者』も名作ローグライクであることはもちろん、ローグライクを作り続け『箱庭フロンティア』で第8回ウディコンで総合2位を記録したこよるさん旧サイト新サイト)というローグライク名人もいらっしゃいます。

なんと同作品は2016年の窓の社ゲーム大賞というフリゲとしての名誉もゲット。紹介もこちらが詳しいです。

そう、ウディタはローグライクに強いのです。

 

ウディタのコモンだけでダンジョン自動生成を作る方法

はじめに

上記のローグライク名人さんなどが、すでに自動生成用コモンを配布してらっしゃいます。手っ取り早くランダムなダンジョンを導入したい場合はそちらのコモンをお借りしてしまいましょう(→公式のコモン配布ページ

 

次のような方は、このページで筆者の試行錯誤にお付き合いいただければと思います。

  • 自動生成の理屈を知っておきたい
  • コモン導入時にデータベースや通常変数の設定はなるべく避けたい

理屈がわかればウディタ以外のツールでも再現可能になるでしょうし、あるいは改造OKな配布コモンのアレンジもしやすくなるはず。

なお、おおまかな流れは、コチラのこよるさんの説明ページを参考にしています。

 

MEMO
もちろん、筆者も勉強中の身です。みなさんが読んでいるうちにもっと良い方法が思いついたりもすると思いますし、そうあって欲しいと願います。

 

1.空間分割

まずはじめに、適当な大きさのマップと階段イベントを作りましょう。

50×50の草っぱらに階段とウルファールさんだけが置かれていて、階段に触れるたび自動生成コモンが呼び出されて、この原っぱがダンジョンに変わるわけですね。

 

さて本番。

この空間分割とは、つまり50×50のマップにランダムに仕切りを置くことです。

それだけならメチャクチャ簡単そうですね。

マップ全体をいちどまっさらにしたあと(レイヤー1~3をチップ0で塗りつぶし)、乱数と回数ループによって、仕切り線(適当なチップをそれ専用にする)を引いていきます。

 

分割するだけなら、ざっくりソースにするとこんな感じ。

回数ループが二重になるのが少し難しそうですが、キモは「仕切り線を引く回数とその長さをランダムに振って対応している」だけです。

MEMO
ここでは仮にレイヤー1~3を真っ白いチップで書き換えていますが、のちの仕切り線や部屋を置くレイヤーによっては、真っ白チップで隠れて見えなくなります。適宜、使用チップやレイヤーを工夫していただければと思います。

 

このコモンだけ呼び出し続けるとこんな感じになります。

(見通しをよくするためにマップ拡大率を50%にしています。黒い部分は拡大率100%に戻せば直ります)

 

ふしぎのダンジョンっぽく部屋を置いていくことを考えると、マップの端に部屋がくっついてしまうのは見栄えがよくありませんね。

あらかじめ画面の左端・上端にも仕切り線をひいておいて、部屋を生成する際の目印として目安のカンバンチップを設置するように改良してみましょう。

※仕切り線もカンバンチップも最終的にはすべて消えます

ソースはこんな感じ。

設置した目安カンバンの座標と仕切り線どうしの間隔は文字列変数に記憶して、部屋生成コモンへ受け渡します。

2.部屋生成

空間分割によって仕切り線が引かれました。

仕切られた空間ごとに「ランダムなサイズの床のカタマリ」を置けば部屋になる……というのが、このパートの眼目です。

こういうふうに、床(グレーのチップ)をランダムに設置してもらえたらOK。

さて、部屋(床のカタマリ)をランダムで設置するには、「仕切り線が引かれた座標」と「仕切り線ごとの間隔」をおぼえておく必要があります(でないと部屋が仕切り線をブチ抜いてしまうので)。

ここで可変データベースなんかに情報を放り込んでおくとラクなんですね。

しかし講座や配布コモンに頼るのなら、コモンを読み込み(インポート)するだけで設定が全部済んで欲しいと思うのも人情です。ソースコードは長くなりますが、データベース要らずで済むようにどうにかしてみましょう。

というか、そのために「空間分割コモン」で目安の座標と仕切り線どうしの間隔を文字列変数に入れておいてもらったのです。

このあたりの処理が、本記事の独自性といえるかもしれません。

さて、空間分割コモンから渡された文字列変数(目安のリスト)の中身はこんな感じになっています。

  • 目安1のX座標
  • 目安1のY座標
  • 目安1から次の仕切り線までのヨコの広さ
  • 目安1から次の仕切り線までのタテの広さ
  • 目安2のX座標
  • 目安2のY座標
  • 目安2から次の仕切り線までのヨコの広さ
  • 目安2から次の仕切り線までのタテの広さ
  • 目安3の……

このリスト(文字列変数)がカラになるまで、4行1セットで座標を切り出していけば、すべての目安の位置をチェックしたことになるでしょう。

ここまでわかっていれば、「目安カンバンと次の仕切り線の間のどこかの座標」を乱数で選び、その座標からランダムな広さの床のカタマリを延ばすことが可能です。

「部屋の左上のカド座標(x,y)」と「部屋のタテ・ヨコの広さ」を決めるサイコロの最小値~最大値を決めていくイメージですね。

作りたいダンジョンのサイズ感に合わせ、このあたりの規則は変わってくるでしょう。単純に、数学的により美しい書き方も無数にあるかと思います。ともかく、こんな感じに部屋の設置ができました。

 

ここでも、「部屋の左上のカド座標(x,y)」と「部屋のタテ・ヨコの広さ」の生成結果の変数を文字列変数に記憶して、次のコモンへ渡していきましょう。

3.通路生成

参考 1.6. 通路を作ろう第 1章迷路の自動生成アルゴリズム

通路生成については、こちらのページの考え方を参考にしています。

空間分割の仕切り線を、通路の下書きに使ってしまうのです。

とはいえ、上記のサイトさんほどスマートには作れないので、ここからはかなりの力業です。

 

■仕切り線に触れるまで部屋の「足」をのばす

部屋生成コモンから渡されてきた新しいリストは、次のような内容になっています。

  • 実際に生成した部屋Aの左上のカドのX座標
  • 実際に生成した部屋Aの左上のカドのY座標
  • 実際に生成した部屋Aの左上のヨコの長さ
  • 実際に生成した部屋Aの左上のタテの長さ
  • 実際に生成した部屋Bの…

部屋生成と同じ要領で、各種情報を切り出して処理しましょう。

 

部屋の上下左右の4方向に仕切り線が存在する可能性がありますので、左上カドの座標をもとに多少ランダムに位置を動かしながら、4方向に「1マス幅の床」=「通路」をのばしていきます。

足を延ばす方向のマップチップをひとつずつ調べていき、画面端(座標0か49)にぶつかる前に仕切り線が見つかったら、調べてきたマップチップを床に書き換えてしまいましょう。

まずはこれが「部屋が仕切り線にむけて伸ばした足」になります。

(左端・上端は座標0なので足はのびていません)

 

実際に処理を書いてみるとこんな感じ。

 

 

■仕切り線を通路にする

上のスクショで通路ができたように見えますが、仕切り線は最終的に消えてしまいます。

仕切り線を通して「足」と「足」を結ぶ必要がありますね。

考え方としては、下の図のイメージ通り。黄色い線で結ばれた「足」たちのように、仕切り線上でペアにできる「足」を探してはその間のチップを書き換えていきます。

(最初から偶然くっついてくれている足のペアもありますね。また、たぶん画面外にまだまだつながる「足」のペアがあるのです)

 

では、やっていきましょう。

「┣通路生成」コモンのループ終了後に、「足のペアをつなげるコモン」(方向別に2種類)を作って呼び出すようにします。

足のペアをつなげるには、仕切り線のラインを1マスずつなぞりながら、「足」(床チップ)を見つけるたびにフラグを書き換えてチップ処理を行うことになりそうですので、こんなコモンを先に用意しました。

 

「┃┗リスト内容のチップ置換」

■ループ開始
|■条件分岐(文字): 【1】 CSelf5[リスト]が “” と同じ 【2】 CSelf5[リスト]が “\n” と同じ
|-◇分岐: 【1】 [ CSelf5[リスト] “” と同じ ]の場合↓
| |■ループ中断
| |■
|-◇分岐: 【2】 [ CSelf5[リスト] “\n” と同じ ]の場合↓
| |■ループ中断
| |■
|◇分岐終了◇
|■文字列操作:CSelf6[切り出し受け] =<上1行切出> CSelf5[リスト] |■変数操作: CSelf10[X] = CSelf6[切り出し受け] + 0
|■文字列操作:CSelf6[切り出し受け] =<上1行切出> CSelf5[リスト] |■変数操作: CSelf11[Y] = CSelf6[切り出し受け] + 0
|■マップチップ上書き: [ レイヤー 3 / X CSelf10[X] / Y CSelf11[Y] ] から[ 横1 / 縦1 ] をチップ[ 0 ]で上書き
|■マップチップ上書き: [ レイヤー 1 / X CSelf10[X] / Y CSelf11[Y] ] から[ 横1 / 縦1 ] をチップ[ 23 ]で上書き
|■
◇ループここまで◇◇

受けとったリストを分解して座標に直し、その場所のチップを書き換えるものです。

 

あとは地道にマップチップを調べて、座標をおぼえたりフラグを切り替えたりの処理を愚直に書きました。

 

これで通路と通路、つまり部屋どうしがそれらしくつながってくれました。

通路どうしがつながって、部屋がきれいな四角形に見えなくなることもまれにあるかと思います。このあたりは好き好きで、仕切り線を2マス幅にするとか、目安カンバンをおくあたりをさらにx+1,y+1と座標を調整するなど、各人工夫することになるでしょう。

一応の完成

ランダム生成ということもあってデバッグ不足で未発見の不具合もありそうですが……さておき、おおもとのコモンの最後で、仕切り線やカンバンを消してしまえば、ざっくりとソレらしい自動生成のダンジョンになります。

でき……た……!

基本の理屈を学ぶという当初の目的はおおよそはたせたようです。

 

MEMO
壁のチップを置きたい場合なども、部屋生成コモンの返り値の文字列変数(各部屋の左上カド座標)なんかをもとに処理を作っていけるのではないかと思います。

ほそく

イベント位置のランダム化

階段イベントの位置も毎回変わって欲しいですよね。その処理を作ってみましょう。

部屋生成時の結果リスト(文字列)は、部屋ひとつあたり4行の変数を記憶していました。

ということは仮にリストが20行あるなら、4で割った値の「5」がいま生成された部屋の数ということがわかります。

それに即して処理を組んでみました。

部屋の数N(リスト行数/4)を取得したら、1~Nの乱数によって「階段をどの部屋に設置するか」がランダムで決定できます。部屋の番号に応じて元のリストから情報を切り出し、部屋の位置・サイズに合わせた範囲のランダム座標に階段イベントを場所移動させればOK……という流れ。

 

ちなみに、任意の文字列の行数を数えるコモンはこんな感じ。

■変数操作: CSelf4[結果] = 0 + 0
■ループ開始
|■条件分岐(文字): 【1】 CSelf5[対象文]が “” と同じ
|-◇分岐: 【1】 [ CSelf5[対象文] “” と同じ ]の場合↓
| |■ループ中断
| |■
|-◇上記以外
| |■文字列操作:CSelf6[切り出し受け] =<上1行切出> CSelf5[対象文] | |■変数操作: CSelf4[結果] += 1 + 0
| |■
|◇分岐終了◇
|■
◇ループここまで◇◇

 

主人公の位置や敵やアイテムのイベントもおなじ要領であちこちに散らしていくことができるでしょう。

階段のおかれた部屋番号を記憶しておけば、他のイベントを動かす際に「階段イベントとは必ず違う部屋にする」といった調整もできるはず(主人公が初期配置でいきなり階段を見つける確率を操作したりも)。

 

 

関連講座

さらにランダムな装飾を施したいなら

【ウディタ講座】自動生成ダンジョンの自然な装飾法

 

 

ミニマップ表示なんかはこちらを参考に

【ウディタ講座】超低負荷ミニマップ

 

『片道勇者』のマップ生成

そうそう『片道勇者』でも、ついこのあいだ自動生成の説明動画が投稿されましたね。

元記事はこちら

勉強になります。