Automi cellulari avanzati per generare grotte


8

Sto cercando di creare grotte in Unity. Per fare questo, sto cercando di usare automi cellulari. Ho trovato il seguente ( Rouge Basin Cellular Automata for Caves ) che ricorda quello che sto cercando di realizzare.

Tuttavia, il tutorial non è interamente quello che voglio. Voglio qualcosa di simile a ciò che viene prodotto da questo sito Web ( Don Jon Caves ) con l'impostazione "cavernosa" (vedi immagine sotto).inserisci qui la descrizione dell'immagine

Come puoi vedere nell'immagine, tutto è connesso. Ho provato numerosi metodi e librerie, tuttavia nulla ha funzionato.

Sono stato alle prese con questo problema per un po ', e apprezzerei qualsiasi guida in ogni caso.

Grazie

Risposte:


4

Non sono sicuro dell'approccio usato nell'esempio che mostri, ma ecco come probabilmente farei per creare qualcosa di simile ...

Innanzitutto, crea un grafico di rete non indirizzato, qualcosa del genere ...

Grafico di rete non indirizzato

Lo genereresti da una serie di nodi posizionati casualmente, incluso almeno uno che rappresenta l'entrata / uscita della tua caverna.

Ora che hai questo grafico, immagina se dovessi prima aprire una serie di passaggi lungo ciascun vertice - solo semplici passaggi diritti, non irregolari.

Ora hai praticamente una grotta, ma con pareti molto lisce. Sarebbe simile a questo dal grafico sopra ...

Linee di caverna

Quindi la cosa da fare è prendere quei muri e "eroderli" per creare muri ruvidi e irregolari. Prendendo l'esempio qui, questo è ciò che potresti ottenere ...

Grotta erosa

E se nel processo, entri in un'altra sala, quindi nessun problema: hai appena creato una nuova caverna!

L'immagine del grafico originale è tratta da http://mathinsight.org/undirected_graph_definition


È abbastanza facile posizionare i nodi in modo casuale, ma che tipo di metrica viene utilizzata per collegarli? Le persone di solito scelgono n nodi? O forse devono essere una certa vicinanza insieme?
Kyle Baran,

Se hai bisogno di una distribuzione semi-regolare inizia con una griglia perfetta, quindi randomizza le posizioni dei nodi +/- una certa distanza. Se ciò non bastasse, aggiungi alcune eccezioni casuali che raddoppiano la distanza casuale. Puoi aggiungere uno spessore casuale alle linee di connessione usando una trama di nuvola di plasma per scegliere lo spessore in modo apparentemente organico.
Stephane Hockenhull,

1
Il collegamento dei nodi è un altro problema separato. Ecco una domanda che ne discute -> mathematica.stackexchange.com/questions/11962/… Anche se le linee si incrociano, il metodo è ancora valido.
Tim Holt,

Dipende davvero dai requisiti. Se stai bene con qualunque cosa, puoi farlo in modo abbastanza semplice. Se vuoi un approccio complicato, puoi persino calcolare un minimo spanning tree e far terminare i corridoi se colpiscono un altro corridoio (ho fatto qualcosa di simile in un roguelike di Ruby che ho scritto una volta).
ashes999,

Genererei questo grafico come una road map probabalistica . Inizia creando una serie di "ostacoli" che sono considerati impraticabili. Questo può essere fatto usando Perlin Noise. Quindi, posizionare N nodi in modo casuale e uniforme nello spazio libero. Connettere ciascun nodo ai nodi K più vicini in modo tale che la connessione sia nello spazio libero. È probabile che il risultato sia collegato e sembrerà molto organico.
mklingen,

1

un modo per farlo è quello di raggruppare tutte le grotte con un insieme disgiunto e quindi rimuovere tutto tranne il più grande

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

qui è dove creo la mia lista cellulare e qualche volta rimuovo quelle piccole a volte combino più liste e uso anche queste liste per generare e delineare corpi d'acqua e flora (macchie di alberi, fiori, erba) e nebbia

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

ecco il codice che rimuove i piccoli gruppi dall'elenco

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

o se non rimuovi piccoli, metti le tue cose nella caverna più grande

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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.