Усовершенствованные сотовые автоматы для создания пещер

8

Я пытаюсь сделать пещеры в Unity. Для этого я пытаюсь использовать клеточные автоматы. Я нашел следующее ( Rouge Basin Cellular Automata for Caves ), которое напоминает то, что я пытаюсь выполнить.

Тем не менее, учебник не совсем то, что я хочу. Я хочу что-то вроде того, что производится на этом сайте ( Дон Джон Пещеры ) с настройкой «пещеристый» (см. Изображение ниже).введите описание изображения здесь

Как вы можете видеть на изображении, все связано. Я перепробовал множество методов и библиотек, однако ничего не помогло.

Я боролся с этой проблемой некоторое время, и я был бы признателен за любые указания, что когда-либо.

Спасибо

satvikb
источник

Ответы:

4

Я не уверен, что подход, использованный в примере, который вы показываете, но вот как я, вероятно, пошел бы о создании чего-то подобного ...

Сначала создайте неориентированный сетевой график, как-то так ...

Ненаправленный сетевой граф

Вы сгенерируете его из набора случайно расположенных узлов, включая, по крайней мере, один, представляющий вход / выход из вашей пещеры.

Теперь, когда у вас есть этот график, представьте, что вам нужно сначала открыть ряд проходов вдоль каждой вершины - просто простые прямые проходы, а не нерегулярные.

Теперь у вас есть пещера, но с очень гладкими стенами. Это будет выглядеть примерно так из приведенного выше графика ...

Пещерные линии

Поэтому нужно взять эти стены и «разрушить» их, чтобы создать грубые и неровные стены. Взяв пример здесь, это то, что вы можете получить ...

Пещера разрушена

И если в процессе вы проходите в другой зал, то нет проблем - вы только что создали новую пещеру!

Исходное графическое изображение взято с http://mathinsight.org/undirected_graph_definition

Тим Холт
источник
Достаточно легко разместить узлы случайным образом, но какой тип метрики используется для их соединения? Люди обычно выбирают n узлов? Или, может быть, они должны быть рядом друг с другом?
Кайл Баран
Если вам нужно полурегулярное распределение, начните с идеальной сетки, затем рандомизируйте положения узлов +/- на некотором расстоянии. Если этого недостаточно, добавьте несколько случайных исключений, которые удваивают случайное расстояние. Вы можете добавить некоторую случайную толщину к соединительным линиям, используя текстуру плазменного облака, чтобы выбрать толщину, казалось бы, органично.
Стефан Хоккенхалл
1
Подключение узлов является еще одной отдельной проблемой. Вот один вопрос, который обсуждает это -> mathematica.stackexchange.com/questions/11962/… Даже если линии пересекаются, метод все еще действителен.
Тим Холт
Это действительно сводится к требованиям. Если у вас все в порядке, вы можете сделать это довольно просто. Если вам нужен сложный подход, вы можете даже рассчитать минимальное остовное дерево и сделать так, чтобы коридоры заканчивались, если они попадают в другой коридор (я сделал нечто похожее на Ruby roguelike, который я написал однажды).
ashes999
Я бы сгенерировал этот график в качестве вероятностной дорожной карты . Начните с создания набора «препятствий», которые считаются непроходимыми. Это можно сделать с помощью Perlin Noise. Затем разместите N узлов случайно и равномерно в свободном пространстве. Соедините каждый узел с его K ближайшими узлами так, чтобы соединение находилось в свободном пространстве. Результат, скорее всего, будет связан, и будет выглядеть очень органично.
mklingen
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);
}
Rakka Rage
источник