Autômatos celulares avançados para gerar cavernas

8

Estou tentando fazer cavernas na Unity. Para fazer isso, estou tentando usar autômatos celulares. Encontrei o seguinte ( Autômato celular para cavernas da Rouge Basin ) que se assemelha ao que estou tentando realizar.

No entanto, o tutorial não é exatamente o que eu quero. Quero algo parecido com o que é produzido por este site ( Don Jon Caves ) com a configuração "cavernosa" (veja a imagem abaixo).insira a descrição da imagem aqui

Como você pode ver na imagem, tudo está conectado. Eu tentei vários métodos e bibliotecas, mas nada funcionou.

Estou lutando com esse problema há algum tempo e gostaria de receber qualquer orientação.

obrigado

satvikb
fonte

Respostas:

4

Não tenho certeza da abordagem usada pelo exemplo que você mostra, mas aqui está como eu provavelmente criaria algo semelhante ...

Primeiro, crie um gráfico de rede não direcionado, algo como isto ...

Gráfico de rede não direcionada

Você o geraria a partir de um conjunto de nós colocados aleatoriamente, incluindo pelo menos um que represente a entrada / saída da caverna.

Agora que você tem esse gráfico, imagine se você primeiro abrir um conjunto de passagens ao longo de cada vértice - apenas passagens retas simples, não irregulares.

Agora você basicamente tem uma caverna, mas com paredes muito lisas. Seria algo parecido com isto no gráfico acima ...

Linhas de caverna

Portanto, o que se deve fazer é pegar essas paredes e "corroê-las" para criar paredes ásperas e irregulares. Tomando o exemplo aqui, é isso que você pode obter ...

Caverna erodida

E se, no processo, você entrar em outro corredor, não há problema - você acabou de criar uma nova caverna!

A imagem original do gráfico é de http://mathinsight.org/undirected_graph_definition

Tim Holt
fonte
É fácil colocar os nós aleatoriamente, mas que tipo de métrica é usada para conectá-los? As pessoas geralmente escolhem n nós? Ou talvez eles tenham que ter certa proximidade juntos?
Kyle Baran
Se você precisar de uma distribuição semi-regular, comece com uma grade perfeita, então randomize as posições dos nós +/- a alguma distância. Se isso não for suficiente, adicione algumas exceções aleatórias que dobram a distância aleatória. Você pode adicionar uma espessura aleatória às linhas de conexão usando uma textura de nuvem de plasma para selecionar a espessura de uma maneira aparentemente orgânica.
Stephane Hockenhull
11
A conexão dos nós é outro problema separado. Aqui está uma pergunta que a discute -> mathematica.stackexchange.com/questions/11962/… Mesmo que as linhas se cruzem, o método ainda é válido.
Tim Holt
Realmente se resume a requisitos. Se você estiver bem com o que quer, você pode fazer isso de maneira bastante simples. Se você deseja uma abordagem complicada, pode até calcular uma árvore de abrangência mínima e fazer com que os corredores terminem se eles atingirem outro corredor (fiz algo semelhante em Ruby, como escrevi uma vez).
precisa saber é o seguinte
Eu geraria esse gráfico como um mapa probabalístico . Comece criando um conjunto de "obstáculos" considerados intransitáveis. Isso pode ser feito usando o Perlin Noise. Em seguida, coloque N nós aleatoriamente e uniformemente no espaço livre. Conecte cada nó aos K nós mais próximos, de modo que a conexão esteja no espaço livre. É provável que o resultado esteja conectado e parecerá muito orgânico.
mklingen
1

Uma maneira de fazer isso é agrupar todas as cavernas com um conjunto separado e remover todas, exceto as maiores

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;
    }
}

Aqui é onde eu crio minha lista de celulares e, às vezes, removo as pequenas. Combino várias listas às vezes e também as uso para gerar e delinear corpos de água e flora (manchas de árvores, flores, grama) e nevoeiro

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;
}

aqui está o código que remove os pequenos grupos da lista

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;
}

ou se você não remover pequenos basta colocar suas coisas na maior caverna

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
fonte