Search Unity

タイルマップと併用可能なプロシージャル生成パターン(後編)

, 6月 7, 2018

前編では、パーリンノイズやランダムウォークなどの各種メソッドを使用してプロシージャルな手法で地形の高さを決める方法をいくつかご紹介しました。本記事では、プロシージャル生成による洞窟の作成方法をいくつかご紹介します。利用可能なバリエーションについても、ある程度ご理解いただけると思います。

本記事で取り扱っている例は、すべてこちらのプロジェクト内にあります。アセットをダウンロードしてプロシージャル生成アルゴリズムを試してみてください。

後編である本記事でも、前編と同じ規則が当てはまります。以下にこの規則を再掲します。

  • タイルとそれ以外の要素の識別はバイナリを使用して行われます。1 が ON、0 が OFF となります。
  • すべてのマップは整数の 2 次元配列内に保存されます。この配列は(レンダー時以外は)各関数の最後でユーザーに戻されます。
  • 配列を扱う関数である GetUpperBound() を使用して各マップの高さと幅を取得することで、各関数内に含まれる変数の数を減らし、コードをよりクリーンにします。
  • 私はよく Mathf.FloorToInt() を使用します。なぜなら、タイルマップの座標系が左下から始まっており、Mathf.FloorToInt() を使用することで数字を四捨五入して整数にすることができるからです。
  • 本記事内に掲載しているコードは全て C# で記述されています。

パーリンノイズ

前編の記事ではパーリンノイズを使って地形の高さを決める方法をいくつかご紹介しました。幸運なことに、洞窟もパーリンノイズで作成することができます。これは新しいパーリンノイズ値を 1 つ取得することによって行われます。この値が、現在の位置に特定の係数を乗じたパラメーターを受け取ります。この係数は 0 と 1 の間の値です。この係数の値が大きいほど、生成されるパーリンノイズが粗くなります。その後、この値を四捨五入して 0 か 1 の整数にし、それをマップ配列内に保存します。以下で実装をご覧ください。

 

シードではなく係数を使用する理由は、0 と 0.5 の間の値で乗算する方が、生成されたパーリンノイズの見た目の質が向上するからです。値が低いほど結果の見た目が粗くなります。以下の GIF 画像をご覧ください。係数の値が 0.01 のものから 0.25 のものまで、段階的に値を増加させた結果画像を比較していただけます。

この GIF 画像を見ると、パーリンノイズの生成は、実際には同じパターンの表示範囲が 1 コマごとに拡大されているに過ぎないことが分かります。

ランダムウォーク

前編の記事では、プラットフォームが高くなるか低くなるかは「コイン投げ」によって決まることをご説明しました。本記事では、この同じ概念に「左右」の選択肢が追加されます。このバリエーションのランダムウォークアルゴリズムによって、洞窟の作成が可能になります。これは、ランダムに方向を決め、位置を移動してタイルを削除することで行われます。消去する必要のある床の数に達するまでこのプロセスを繰り返します。現段階では 4 方向(上・下・左・右)しか使用していません。

この関数は以下を行うことで開始されます。

  1. 開始位置を特定する
  2. 削除しなければならないフロアタイルの数を計算する
  3. 開始位置のタイルを削除する
  4. フロアの数に 1 を加える

 

次に、while ループに移ります。これにより洞窟が生成されます。

何が行われているのか?

まず最初に、進むべき方向を乱数によって決定しています。次に、switch case ステートメントでその新しい方向をチェックします。このステートメント内で、該当の位置が壁かどうかをチェックします。壁でなければ、そのタイルを配列から削除します。これを、必要なフロアの数に達するまで続けます。最終的には以下のようになります。

 

この関数のカスタムバージョンとして、斜め方向も含まれるものも作成しました。この関数用のコードは少し長いので、ご覧になりたい方は本記事の冒頭に掲載されているプロジェクトへのリンクをご利用ください。

DirectionalTunnel

DirectionalTunnel はマップの一つの端から開始して反対側の端に向かってトンネル(穴)を生成します。トンネルの曲線とラフネスを関数内に入力して制御可能です。またトンネル各部の長さの最小値と最大値も設定できます。実装は下記のようになります。

何が行われているのか?

まず始めに幅の値を設定します。この幅の値として設定した数の正負を逆転させた数から元の数までループを回し、必要な幅のトンネルができます。ここではその値を 1 にしています。これでループが回され、結果として合計の幅が 3 になります(-1、0、1 と値が変わるため)。

次に、x 軸上の開始位置が設定されます。これは、マップの幅の中央を取得することによって行われます。この時点で最初の 2 つの値が設定されたので、マップの開始部分のトンネルが生成できます。

ここで、マップの残りの部分の生成に移りましょう。

乱数を生成して、ラフネスの値と照らし合わせてチェックし、乱数がラフネスの値を超えていればパスの幅を変更できます。また幅を小さくし過ぎていないかもチェックします。次の短いコードで、トンネリングを行いながらマップの処理を続けます。各ステップごとに以下が行われます。

  1. 曲線の値と照らし合わせてチェックするために新しい乱数を生成する。上記のチェック同様、これが曲線の値を超えていればパスの中央点を変更できます。また、マップの端の外に出ていないかどうかもチェックします。
  2. 最後に、生成した新しいセクションに穴を空けていきます。

最終的にこの実装は以下のような結果になります。

セルオートマトン

セルオートマトンはセルの近傍を使用して現在のセルが on (1) であるか off (0) であるかを決定します。これらの近傍のベースはランダムに生成されたセルのグリッドを使用しています。ここではこの最初のグリッドを C# の Random.Next 関数を使用して生成します。

セルオートマトンの実装にはいくつか異なるものがあるので、このベースグリッドを生成するための別の関数を作成しました。以下がその関数です。

この関数内では、グリッドに壁を持たせるかどうかも設定できます。それ以外は比較的単純です。フィルレートに照らし合わせて乱数をチェックし、現在のセルが on であるか off であるか特定します。結果は以下のようになります。

ムーア近傍

ムーア近傍は、初期設定のセルオートマトン生成を平滑化するために使用されます。ムーア近傍は以下のようになっています。

近傍の規則は以下の通りです。

  • 近傍のチェックを全方向において行う
  • 近傍がアクティブなタイルである場合、周囲のタイルに 1 つ追加する。
  • 近傍がアクティブなタイルでない場合、何も行わない。
  • セルの周囲にタイルが 5 つ以上ある場合、そのセルをアクティブタイルにする。
  • セルの周囲にあるタイルがちょうど 4 つである場合は、タイルをそのままにする。
  • マップ内のすべてのタイルをチェックし終わるまで繰り返す。

ムーア近傍のチェックのための関数は以下の通りです。

タイルのチェックが終了したら、平滑化の関数内の情報の使用へと進みます。ここでも、初期設定のセルオートマトン生成同様に、マップのエッジを壁にするかどうか設定できます。

この関数内で重要なのは、マップ全体の平滑化を一定回数行うための for ループがあることです。これが最終的なマップの見た目を向上させます。

このアルゴリズムは、(例えば穴と穴の間にブロックが 2 つしかない場合などに)穴同士を繋げて修正することができます。

ノイマン近傍

ノイマン近傍も、セルオートマトン生成の一般的な実装メソッドのひとつです。この生成では、ムーア生成で使用したものよりもシンプルな近傍を使用します。近傍は以下のようになっています。

この近傍の規則は以下の通りです。

  • タイルの周囲の直接の近傍をチェックする(斜め方向は含めない)。
  • セルがアクティブであれば、カウントに 1 つ追加する。
  • セルが非アクティブであれば、何も行わない。
  • 3 つ以上の近傍がある場合は、現在のセルをアクティブにする。
  • 近傍が 1 つ以下の場合は、現在のセルを非アクティブにする。
  • 近傍の数がちょうど 2 つであれば、現在のセルを修正しない。

2 つ目の結果は 1 つ目の結果と原則的に同じですが、近傍エリアが拡大されます。

近傍のチェックには以下の関数を使用します。

近傍がいくつあるかの結果が取得できたら、配列の平滑化に移ることができます。上述のムーア近傍の場合と同様、入力された必要な回数の平滑化のイテレーションを行うための for ループがあります。

結果は以下のように、ムーア近傍よりも大幅に粗くなります。

ここでもムーア近傍と同様、この生成に加えて更に追加スクリプトを実行し、マップ各部の間の接続を向上させることも可能です。

まとめ

本記事が、皆様がプロジェクトにプロシージャル生成を使用するきっかけになれば嬉しく思います。まだプロジェクトをダウンロードされていない方は、こちらから是非ダウンロードしてください。プロシージャル生成マップについてより詳しく学びたい場合は Procedural Generation Wiki(英語) および Roguebasin.com(英語)をご覧になることをお勧めします。

プロシージャル生成を使用して何か面白いものを作成されたなら、ぜひ Twitter あるいは下のコメント欄で私に教えてください!

 

Unite Berlin での 2 次元プロシージャル生成についての講演

より深く掘り下げて学べるライブデモ・セッションに参加したいですか? 6 月 20 日、Unite Berlin 会場のホールのミニシアターで、タイルマップと併用可能なプロシージャル生成パターンについてお話します。また講演後も会場におりますので、直接お話しましょう!(※訳注:Unite Berlin は 6 月 21 日、盛況のうちに閉幕しました)

コメント受付を終了しました。

  1. I will say that calling that method Cellular Automata is a bit misleading, because what you actually are doing is just generating noise.

    1. Yeah, that part is just bootstrapping the cellular automata – it’s not the actual algorithm at all. What follows are the actual rules for the automata.

      However, the implementation is flawed, because cellular automata is supposed to read from one buffer, and write to another. Now it’s actually modifying the map while it’s reading it. So the end results would be different depending on which direction the rules are processed.

      Otherwise it’s a great primer for PCG. Too bad it’s littered with errors and mistakes.

  2. Isaac Surfraz

    6月 7, 2018 6:26 pm

    YAY! My favourite blog post series has returned :)

    More procgen articles please! Some to do with 3d stuff such as scattering and placement, heightmap generation, and computational geometry (such as fitting x boxes in a space etc) would be great eventually :)