廊下や部屋に依存しないダンジョン世代


15

ゲームの開始時に作成された手続き的に生成された世界でゲームを作成しています。グリッドで表されるいくつかの領域で構成されています(8x8、9x6、サイズは理想的には任意です)。これらの領域は、依存関係リストを介して互いに接続されることになっています。

そのグリッドの少なくとも3つのスペースがこれらの2つの領域の間に露出している場合、接続が存在します。その3つのスペース接続エリアの中央のセルには、エリア間の出入り口があります。

私はそれらを接続する方法を見つけようとしてきましたが、同時に考慮する必要のある領域が増えるにつれて、ますます複雑になります。

ペーパープロトタイピングをいくつか試しましたが、視覚的に行うのは非常に簡単なプロセスですが、コードによって同じ効率で部屋を配置できる優れた数式のセットは見つかりませんでした。

これが私が今苦労している「単純な」例です:

  • エリア「a」は「b」および「c」に接続する必要があります
  • エリア「b」は「a」と「d」に接続する必要があります
  • エリア「c」は「a」と「d」に接続する必要があります
  • エリア「d」は「b」および「c」に接続する必要があります

簡単にするために、リストに表示される順番で部屋を配置していることを考えてみましょう(他の人も試しました)。そこで、標準の手続き型ダンジョン生成アルゴリズムとしてこれにアプローチしています。

最初の領域なので、ボード上の任意の場所に「a」を配置します。次に、壁をランダムに選択します。その壁には何も接続されていないため、そこに「b」を配置できます。

ここで「c」を配置する必要がありますが、「a」はすでにボード上にあり、壁が占有されているため、別の壁に配置することにします。しかし、「d」が表示され、「b」と「c」にも接続する必要があるため、すべてのプレースメントが実行されるわけではありません。

同じ依存関係を持つ2つの部屋を反対側の壁に置くことはできないという制限を試みましたが、それでも成功を保証するものではありません。

また、エリアのサイズが異なる他のケースでは、反対側の壁にいるとうまくいきます。

また、有効なソリューションを除外するため、使用済みの壁を考慮しないことは欠陥のある仮定です。

Optimal Rectangle PackingやGraph Layoutアルゴリズムなど、他の手続き生成アルゴリズムなどの研究を調べてみましたが、通常、これらのアルゴリズムはこの問題のすべての制約を考慮しておらず、一緒に混ぜることは困難です。

適切な配置が見つかるまで領域とバックトラックを配置するなど、たくさんのアプローチを考えましたが、それらは試行錯誤に非常に依存しており、計算の面でコストがかかるようです。しかし、私が言及した最後の2つの問題に関する広範な研究を考えると、それが唯一/最良の解決策かもしれませんか?

私はちょうど誰かが過去に同様の問題を抱えていたか、またはこれを理解し、アルゴリズムをどこから始めるべきかについていくつかの指針を与えてくれるかどうかを見たかっただけです。または、失敗した場合は、設定した制約を緩めることを検討する必要があります。


部屋は完全に正方形でなければなりませんか?
wolfdawn

あなたが彼らが4つ以上の壁を持たなければならないのであれば、そうではありませんが、私は世界空間を簡素化するためにそれをしました。各エリアが占めるスペースを簡単に計算する必要があるため、必要なものをすべて配置できるかどうかがわかります。
ジョアナアルメイダ

回答:


6

これはクールな問題です。私はそれが部屋の配置のスペースで行動計画を使用して解決できると信じています。

定義州立次のように世界のを:

//State:
//    A list of room states.
//    Room state:
//      - Does room exist?
//      - Where is room's location?
//      - What is the room's size?

制約を次のように定義します。

 // Constraint(<1>, <2>):
 //  - If room <1> and <2> exist, Room <1> is adjacent to Room <2>

「隣接」とは、説明したとおりです(少なくとも3つの近隣を共有します)

A 制約があると言われている無効化二つの部屋があるときはいつでもない隣接し、両方の部屋が存在します。

次の場合に有効な状態を定義します。

// foreach Constraint:
//        The Constraint is "not invalidated".
// foreach Room:
//       The room does not intersect another room.

現在の状態を指定して、アクションを部屋の配置として定義します。アクションはある有効なアクションからの結果の状態が有効である時はいつでも。したがって、各状態のアクションのリストを生成できます。

// Returns a list of valid actions from the current state
function List<Action> GetValidActions(State current, List<Constraint> constraints):
    List<Action> actions = new List<Action>();
    // For each non-existent room..
    foreach Room room in current.Rooms:
        if(!room.Exists)
            // Try to put it at every possible location
            foreach Position position in Dungeon:
                 State next = current.PlaceRoom(room, position)
                 // If the next state is valid, we add that action to the list.
                 if(next.IsValid(constraints))
                     actions.Add(new Action(room, position));

さて、残っているのはグラフです。ここで、状態はノードであり、アクションはリンクです。目標は見つけることです国家両方有効に、そして部屋のすべてが配置されています。おそらく深さ優先検索を使用して、任意の方法でグラフを検索することにより、有効な配置を見つけることができます。検索は次のようになります。

// Given a start state (with all rooms set to *not exist*), and a set of
// constraints, finds a valid end state where all the constraints are met,
// using a depth-first search.
// Notice that this gives you the power to pre-define the starting conditions
// of the search, to for instance define some key areas of your dungeon by hand.
function State GetFinalState(State start, List<Constraint> constraints)
    Stack<State> stateStack = new Stack<State>();
    State current = start;
    stateStack.push(start);
    while not stateStack.IsEmpty():
        current = stateStack.pop();
        // Consider a new state to expand from.
        if not current.checked:
            current.checked = true;
            // If the state meets all the constraints, we found a solution!
            if(current.IsValid(constraints) and current.AllRoomsExist()):
                return current;

            // Otherwise, get all the valid actions
            List<Action> actions = GetValidActions(current, constraints);

            // Add the resulting state to the stack.
            foreach Action action in actions:
                State next = current.PlaceRoom(action.room, action.position);
                stateStack.push(next);

    // If the stack is completely empty, there is no solution!
    return NO_SOLUTION;

生成されるダンジョンの品質は、部屋とアクションが考慮される順序に依存します。おそらく、各段階で実行するアクションをランダムに並べ替えることで、興味深いさまざまな結果を得ることができます。これにより、状態アクショングラフをランダムにウォークスルーします。検索効率は、無効な状態をどれだけ早く拒否できるかに大きく依存します。有効なアクションを検索するたびに、制約から有効な状態を生成すると役立つ場合があります。


おもしろいのは、この解決策に言及する必要があります。先ほど友人と話をして、彼はおそらくツリーベースの検索アルゴリズムを調べるべきだと言っていましたが、このコンテキストでそれらをどのように使うかはわかりませんでした。あなたの投稿は目を見張るものでした!ブランチ生成を管理し、いくつかの最適化を行って不良ブランチをできるだけ早くカットすることは、確かに実行可能なソリューションのようです。
ジョアナアルメイダ

7

世代の優先順位が競合しています。レベルを生成する場合、最初の目標は、スケールに関係なく、平面(重なり合わない)の接続ポイントのウェブにする必要があります。次に、そのWeb内のポイントから部屋を作成します。一般的に言えば、最初に部屋の形状を作成するのは間違いです。最初に接続を作成してから、その中に収容できるルームフォームを確認します。

一般的なアルゴリズム

  1. 2D配列または画像を使用して、レベルをサポートするのに十分なサイズの量子化されたフロアグリッドを作成します。

  2. この空の床スペース全体でランダムに散乱点。各タイルで単純なランダムチェックを使用してポイントを取得するか、標準/ガウス分布を使用してポイントを散布できます。すべてのポイントに一意の色/数値を割り当てます。これらはIDです。(PSこのステップの後に、スペースを拡大する必要があると感じた場合は、必ず行ってください。)

  3. このような各発生点について、順に増分で単一ステップ(ステップあたり0.5〜1.0個/ピクセルの典型的速度)によって境界円又は境界が出矩形成長xy。すべての境界を同じステップでサイズ0からすべて並行して成長させるか、異なる時間および異なる速度で成長を開始して、より早く開始するもののサイズにバイアスをかけることができます(実生の成長を想像してください遅れて開始)。「成長」とは、新しく増加した境界を、それらの境界の開始点に固有の色/ IDで塗りつぶすことを意味します。これに対する隠phorは、マーカーペンを紙の裏に保持し、さまざまな色のインクブロットが出会うまで見続けることです。

  4. 成長ステップ中に、あるポイントと別のポイントの境界が衝突します。これは、これらの2つのポイントの境界の拡大を停止する必要があるポイントです。少なくとも、手順3で説明した統一的な意味で。

  5. すべてのポイントの境界を可能な限り大きくし、すべての成長プロセスを停止すると、完全に満たされているわけではないが、大部分が満たされるマップが作成されます。あなたは今、それらの空のスペースを詰めたいかもしれません。それは紙のシートに色を付けるかのように、私は白であると仮定します。

ポストプロセスのスペース充填

ステップ5で、さまざまな手法を使用して、残っている空のスペースまたは空白を埋めることができます。

  • 隣接する単一の色の付いた領域にスペースを要求し、その色を塗りつぶしてすべてを結合します。
  • まったく新しい領域を形成するように、未使用の新しい色/数字/ IDであふれます。
  • ラウンドロビンアプローチは、すでに満たされている各隣接領域が少し空のスペースに「成長」するようにします。動物が水飲み場の周りを飲んでいると考えてください。彼らは皆、いくらかの水を得ます。
  • 空のスペースを完全に埋めるのではなく、まっすぐな通路を使用して既存のエリアをリンクするためにそれを横切るだけです。

摂動

物事をよりオーガニックに見せるための最終ステップとして、エリアのエッジセルでさまざまな程度のエッジ摂動を行うことができます。重要な移動ルートをブロックしないようにしてください。

利子の酒のための理論

これは、上記で明示的にエッジを作成していないことを除いて、Voronoi Diagrams / Delaunay Triangulationで取られたアプローチに似ています-代わりに、境界領域が衝突すると成長が停止します。ボロノイ図は空間を埋めていることに気付くでしょう。これは、単に触れただけで成長を停止するのではなく、名目上のある程度の重なりがあるためです。あなたも同様に試すことができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.