洞窟を生成する高度なセルラーオートマトン


8

Unityで洞窟を作ろうとしています。これを行うために、私はセルオートマトンを使用しようとしています。私が達成しようとしていることに似ている次の(洞窟のルージュ盆地セルラーオートマトン)を見つけました。

しかし、チュートリアルは完全に私が望んでいるものではありません。このWebサイト(Don Jon Caves)が「海綿」の設定で作成したもの(下の画像を参照)が欲しい。ここに画像の説明を入力してください

画像でわかるように、すべてが接続されています。私は数多くの方法とライブラリを試しましたが、何もうまくいきませんでした。

私はしばらくの間この問題に取り組んできましたが、これまでにどんなガイダンスもありがたいと思います。

ありがとう

回答:


4

あなたが示す例で使用されているアプローチはわかりませんが、これはおそらく同じようなものを作成する方法です...

まず、次のような無向ネットワークグラフを作成します...

無向ネットワークグラフ

ランダムに配置されたノードのセットから生成し、洞窟の入り口/出口を表すノードを少なくとも1つ含めます。

これでこのグラフができたので、まず各頂点に沿って一連の通路を開く場合を想像してください。不規則ではなく、単純な直線の通路です。

これで基本的に洞窟ができましたが、壁は非常に滑らかです。上のグラフからは次のようになります...

洞窟ライン

したがって、次に行うことは、それらの壁を取り、それらを「侵食」して粗くて不規則な壁を作成することです。ここで例をとると、これはあなたが得るかもしれないものです...

侵食された洞窟

そして、その過程で別のホールまで浸食されても問題ありません-新しい洞窟を作成しました!

元のグラフ画像はhttp://mathinsight.org/undirected_graph_definitionからのものです


ノードをランダムに配置するのは簡単ですが、ノードの接続にはどのようなメトリックが使用されますか?人々は通常n個のノードを選びますか?それとも、彼らは一緒に特定の近さでなければなりませんか?
カイルバラン14

半規則的な分布が必要な場合は、完全なグリッドから始めて、ノードの位置をランダム化します+/-ある距離。それが十分でない場合は、ランダムな距離を2倍にするランダムな例外をいくつか追加します。プラズマクラウドテクスチャを使用して接続線にランダムな厚さを追加し、有機的に見える厚さを選択できます。
ステファンホッケンハル14

1
ノードの接続は別の問題です。これについて説明する1つの質問-> mathematica.stackexchange.com/questions/11962/… 線が交差していても、メソッドは有効です。
Tim Holt

それは本当に要件に帰着します。何でもいい場合は、これをかなり簡単に行うことができます。複雑なアプローチが必要な場合は、最小全域木を計算して、コリドーが別のコリドーにぶつかった場合にコリドーを終了させることもできます(Rubyのローグライクで同様のことを1回書いた)。
ashes999 2015年

私はこのグラフを確率論的ロードマップとして生成します。通行不可能と見なされる「障害物」のセットを作成することから始めます。これは、Perlin Noiseを使用して行うことができます。次に、N個のノードをランダムかつ均一に自由空間に配置します。各ノードをK個の最も近いノードに接続して、接続が空きスペースになるようにします。結果は関連している可能性が高く、非常に有機的に見えます。
mklingen 2015年

1

これを行う1つの方法は、すべての洞窟を互いに素なセットでグループ化し、最大のものを除いてすべてを削除することです。

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class DisjointSet
{
    private List<int> _parent;
    private List<int> _rank;
    public DisjointSet(int count)
    {
        _parent = Enumerable.Range(0, count).ToList();
        _rank = Enumerable.Repeat(0, count).ToList();
    }
    public int Find(int i)
    {
        if (_parent[i] == i)
            return i;
        else
        {
            int result = Find(_parent[i]);
            _parent[i] = result;
            return result;
        }
    }
    public void Union(int i, int j)
    {
        int fi = Find(i);
        int fj = Find(j);
        int ri = _rank[fi];
        int rj = _rank[fj];
        if (fi == fj) return;
        if (ri < rj)
            _parent[fi] = fj;
        else if (rj < ri)
            _parent[fj] = fi;
        else
        {
            _parent[fj] = fi;
            _rank[fi]++;
        }
    }
    public Dictionary<int, List<int>> Split(List<bool> list)
    {
        var groups = new Dictionary<int, List<int>>();
        for (int i = 0; i < _parent.Count; i++)
        {
            Vector2 p = PathFinder.Instance.TilePosition(i);
            if (PathFinder.Instance.InsideEdge(p) && list[i])
            {
                int root = Find(i);
                if (!groups.ContainsKey(root))
                {
                    groups.Add(root, new List<int>());
                }
                groups[root].Add(i);
            }
        }
        return groups;
    }
}

ここで、セルラーリストを作成し、小さいリストを削除する場合があります。複数のリストを結合することもあります。また、これらのリストを使用して、水域と植物相(樹木、花、草)と霧の生成と輪郭を描きます。

private List<bool> GetCellularList(int steps, float chance, int birth, int death)
{
    int count = _width * _height;
    List<bool> list = Enumerable.Repeat(false, count).ToList();
    for (int y = 0; y < _height; y++)
    {
        for (int x = 0; x < _width; x++)
        {
            Vector2 p = new Vector2(x, y);
            int index = PathFinder.Instance.TileIndex(p);
            list[index] = Utility.RandomPercent(chance);
        }
    }
    for (int i = 0; i < steps; i++)
    {
        var temp = Enumerable.Repeat(false, count).ToList();
        for (int y = 0; y < _height; y++)
        {
            for (int x = 0; x < _width; x++)
            {
                Vector2 p = new Vector2(x, y);
                int index = PathFinder.Instance.TileIndex(p);
                if (index == -1) Debug.Log(index);
                int adjacent = GetAdjacentCount(list, p);
                bool set = list[index];
                if (set)
                {
                    if (adjacent < death)
                        set = false;
                }
                else
                {
                    if (adjacent > birth)
                        set = true;
                }
                temp[index] = set;
            }
        }
        list = temp;
    }
    if ((steps > 0) && Utility.RandomBool())
        RemoveSmall(list);
    return list;
}

これは、リストから小さなグループを削除するコードです

private void UnionAdjacent(DisjointSet disjoint, List<bool> list, Vector2 p)
{
    for (int y = -1; y <= 1; y++)
    {
        for (int x = -1; x <= 1; x++)
        {
            if (!((x == 0) && (y == 0)))
            {
                Vector2 point = new Vector2(p.x + x, p.y + y);
                if (PathFinder.Instance.InsideEdge(point))
                {
                    int index = PathFinder.Instance.TileIndex(point);
                    if (list[index])
                    {
                        int index0 = PathFinder.Instance.TileIndex(p);
                        int root0 = disjoint.Find(index0);
                        int index1 = PathFinder.Instance.TileIndex(point);
                        int root1 = disjoint.Find(index1);
                        if (root0 != root1)
                        {
                            disjoint.Union(root0, root1);
                        }
                    }
                }
            }
        }
    }
}
private DisjointSet DisjointSetup(List<bool> list)
{
    DisjointSet disjoint = new DisjointSet(_width * _height);
    for (int y = 0; y < _height; y++)
    {
        for (int x = 0; x < _width; x++)
        {
            Vector2 p = new Vector2(x, y);
            if (PathFinder.Instance.InsideEdge(p))
            {
                int index = PathFinder.Instance.TileIndex(p);
                if (list[index])
                {
                    UnionAdjacent(disjoint, list, p);
                }
            }
        }
    }
    return disjoint;
}
private void RemoveSmallGroups(List<bool> list, Dictionary<int, List<int>> groups)
{
    int biggest = 0;
    int biggestKey = 0;
    foreach (var group in groups)
    {
        if (group.Value.Count > biggest)
        {
            biggest = group.Value.Count;
            biggestKey = group.Key;
        }
    }
    var remove = new List<int>();
    foreach (var group in groups)
    {
        if (group.Key != biggestKey)
        {
            remove.Add(group.Key);
        }
    }
    foreach (var key in remove)
    {
        FillGroup(list, groups[key]);
        groups.Remove(key);
    }
}
private void FillGroup(List<bool> list, List<int> group)
{
    foreach (int index in group)
    {
        list[index] = false;
    }
}
private void RemoveSmall(List<bool> list)
{
    DisjointSet disjoint = DisjointSetup(list);
    Dictionary<int, List<int>> groups = disjoint.Split(list);
    RemoveSmallGroups(list, groups);
}
private bool IsGroupEdge(List<bool> list, Vector2 p)
{
    bool edge = false;
    for (int y = -1; y <= 1; y++)
    {
        for (int x = -1; x <= 1; x++)
        {
            if (!((x == 0) && (y == 0)))
            {
                Vector2 point = new Vector2(p.x + x, p.y + y);
                if (PathFinder.Instance.InsideMap(point))
                {
                    int index = PathFinder.Instance.TileIndex(point);
                    if (!list[index])
                    {
                        edge = true;
                    }
                }
            }
        }
    }
    return edge;
}

または小さなものを取り除かない場合は、ものを最大の洞窟に入れてください

private List<int> Biggest(List<bool> list)
{
    DisjointSet disjoint = DisjointSetup(list);
    Dictionary<int, List<int>> groups = disjoint.Split(list);
    RemoveSmallGroups(list, groups);
    IEnumerator<List<int>> enumerator = groups.Values.GetEnumerator();
    enumerator.MoveNext();
    List<int> group = enumerator.Current;
    return group;
}

...

public int TileIndex(int x, int y)
{
    return y * Generator.Instance.Width + x;
}
public Vector2 TilePosition(int index)
{
    float y = index / Generator.Instance.Width;
    float x = index - Generator.Instance.Width * y;
    return new Vector2(x, y);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.