Generația mondială procedurală în unitate

Generația mondială din Unity se referă la procesul de creare sau generare procedurală a lumi virtuale, terenuri, peisaje sau medii în cadrul motorului de joc Unity. Această tehnică este folosită în mod obișnuit în diferite tipuri de jocuri, cum ar fi jocuri open-world, RPG-uri, simulări și multe altele, pentru a crea dinamic lumi de joc vaste și diverse.

Unity oferă un cadru flexibil și o gamă largă de instrumente și API-uri pentru implementarea acestor tehnici de generație mondială. Se pot scrie scripturi personalizate folosind C# pentru a genera și manipula lumea jocului sau pot utiliza funcții încorporate Unity precum sistemul Terrain, funcțiile de zgomot și interfețele de scripting pentru a obține rezultatele dorite. În plus, există, de asemenea, active terțe și plugin-uri disponibile pe Unity Asset Store care pot ajuta la sarcinile de generație mondială.

Există mai multe abordări ale generației mondiale în Unity, iar alegerea depinde de cerințele specifice ale jocului. Iată câteva metode frecvent utilizate:

  • Generare procedurală de teren cu zgomot Perlin
  • Automate celulare
  • Diagrame Voronoi
  • Plasarea obiectelor procedurale

Generare procedurală de teren cu zgomot Perlin

Generarea de teren procedurală în Unity poate fi realizată folosind diverși algoritmi și tehnici. O abordare populară este utilizarea zgomotului Perlin pentru a genera harta înălțimii și apoi aplicarea diferitelor tehnici de texturare și frunziș pentru a crea un teren realist sau stilizat.

Zgomotul Perlin este un tip de zgomot de gradient dezvoltat de Ken Perlin. Acesta generează un model neted, continuu de valori care par aleatorii, dar au o structură coerentă. Zgomotul Perlin este utilizat pe scară largă pentru a crea terenuri cu aspect natural, nori, texturi și alte forme organice.

În Unity, se poate folosi funcția 'Mathf.PerlinNoise()' pentru a genera zgomot Perlin. Este nevoie de două coordonate ca intrare și returnează o valoare între 0 și 1. Prin eșantionarea zgomotului Perlin la frecvențe și amplitudini diferite, este posibil să se creeze diferite niveluri de detaliu și complexitate în conținutul procedural.

Iată un exemplu despre cum să implementați acest lucru în Unity:

  • În editorul Unity, accesați "GameObject -> 3D Object -> Terrain". Acest lucru va crea un teren implicit în scenă.
  • Creați un nou script C# numit "TerrainGenerator" și atașați-l la obiectul teren. Iată un exemplu de script care generează un teren procedural folosind zgomot Perlin:
using UnityEngine;

public class TerrainGenerator : MonoBehaviour
{
    public int width = 512;       // Width of the terrain
    public int height = 512;      // Height of the terrain
    public float scale = 10f;     // Scale of the terrain
    public float offsetX = 100f;  // X offset for noise
    public float offsetY = 100f;  // Y offset for noise
    public float noiseIntensity = 0.1f; //Intensity of the noise

    private void Start()
    {
        Terrain terrain = GetComponent<Terrain>();

        // Create a new instance of TerrainData
        TerrainData terrainData = new TerrainData();

        // Set the heightmap resolution and size of the TerrainData
        terrainData.heightmapResolution = width;
        terrainData.size = new Vector3(width, 600, height);

        // Generate the terrain heights
        float[,] heights = GenerateHeights();
        terrainData.SetHeights(0, 0, heights);

        // Assign the TerrainData to the Terrain component
        terrain.terrainData = terrainData;
    }

    private float[,] GenerateHeights()
    {
        float[,] heights = new float[width, height];

        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                // Generate Perlin noise value for current position
                float xCoord = (float)x / width * scale + offsetX;
                float yCoord = (float)y / height * scale + offsetY;
                float noiseValue = Mathf.PerlinNoise(xCoord, yCoord);

                // Set terrain height based on noise value
                heights[x, y] = noiseValue * noiseIntensity;
            }
        }

        return heights;
    }
}
  • Atașați scriptul "TerrainGenerator" la obiectul Teren din editorul Unity.
  • În fereastra Inspector pentru obiectul teren, ajustați lățimea, înălțimea, scara, decalajele și intensitatea zgomotului pentru a modifica aspectul terenului generat.
  • Apăsați butonul Play din editorul Unity, iar terenul procedural ar trebui apoi generat pe baza algoritmului de zgomot Perlin.

Generarea Unity Terrain cu zgomot Perlin.

Notă: Acest script generează o hartă de bază a înălțimii terenului folosind zgomotul Perlin. Pentru a crea terenuri mai complexe, modificați scriptul pentru a încorpora algoritmi suplimentari de zgomot, aplicați tehnici de eroziune sau netezire, adăugați texturare sau plasați frunziș și obiecte pe baza caracteristicilor terenului.

Automate celulare

Automatele celulare sunt un model de calcul care constă dintr-o grilă de celule, în care fiecare celulă evoluează pe baza unui set de reguli predefinite și a stărilor celulelor învecinate. Este un concept puternic folosit în diverse domenii, inclusiv informatică, matematică și fizică. Automatele celulare pot prezenta modele de comportament complexe care decurg din reguli simple, făcându-le utile pentru simularea fenomenelor naturale și generarea de conținut procedural.

Teoria de bază din spatele automatelor celulare implică următoarele elemente:

  1. Grid: O grilă este o colecție de celule aranjate într-un model regulat, cum ar fi o rețea pătrată sau hexagonală. Fiecare celulă poate avea un număr finit de stări.
  2. Vecini: Fiecare celulă are celule învecinate, care sunt de obicei celulele imediat adiacente. Cartierul poate fi definit pe baza diferitelor modele de conectivitate, cum ar fi cartierele von Neumann (sus, jos, stânga, dreapta) sau Moore (inclusiv diagonala).
  3. Reguli: Comportamentul fiecărei celule este determinat de un set de reguli care specifică modul în care evoluează pe baza stării sale curente și a stărilor celulelor învecinate. Aceste reguli sunt de obicei definite folosind instrucțiuni condiționale sau tabele de căutare.
  4. Update: Automatul celular evoluează prin actualizarea stării fiecărei celule simultan conform regulilor. Acest proces se repetă iterativ, creând o secvență de generații.

Automatele celulare au diverse aplicații în lumea reală, inclusiv:

  1. Simularea fenomenelor naturale: Automatele celulare pot simula comportamentul sistemelor fizice, cum ar fi dinamica fluidelor, incendiile forestiere, fluxul de trafic și dinamica populației. Prin definirea regulilor adecvate, automatele celulare pot capta modelele emergente și dinamica observate în sistemele din lumea reală.
  2. Generare de conținut procedural: Automatele celulare pot fi folosite pentru a genera conținut procedural în jocuri și simulări. De exemplu, ele pot fi folosite pentru a crea terenuri, sisteme de peșteri, distribuția vegetației și alte structuri organice. Mediile complexe și realiste pot fi generate prin specificarea regulilor care guvernează creșterea și interacțiunea celulelor.

Iată un exemplu simplu de implementare a unui automat celular de bază în Unity pentru a simula jocul vieții:

using UnityEngine;

public class CellularAutomaton : MonoBehaviour
{
    public int width = 50;
    public int height = 50;
    public float cellSize = 1f;
    public float updateInterval = 0.1f;
    public Renderer cellPrefab;

    private bool[,] grid;
    private Renderer[,] cells;
    private float timer = 0f;
    private bool[,] newGrid;

    private void Start()
    {
        InitializeGrid();
        CreateCells();
    }

    private void Update()
    {
        timer += Time.deltaTime;

        if (timer >= updateInterval)
        {
            UpdateGrid();
            UpdateCells();
            timer = 0f;
        }
    }

    private void InitializeGrid()
    {
        grid = new bool[width, height];
        newGrid = new bool[width, height];

        // Initialize the grid randomly
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                grid[x, y] = Random.value < 0.5f;
            }
        }
    }

    private void CreateCells()
    {
        cells = new Renderer[width, height];

        // Create a GameObject for each cell in the grid
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                Vector3 position = new Vector3(x * cellSize, 0f, y * cellSize);
                Renderer cell = Instantiate(cellPrefab, position, Quaternion.identity);
                cell.material.color = Color.white;
                cells[x, y] = cell;
            }
        }
    }

    private void UpdateGrid()
    {
        // Apply the rules to update the grid
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                int aliveNeighbors = CountAliveNeighbors(x, y);

                if (grid[x, y])
                {
                    // Cell is alive
                    if (aliveNeighbors < 2 || aliveNeighbors > 3)
                        newGrid[x, y] = false; // Die due to underpopulation or overpopulation
                    else
                        newGrid[x, y] = true; // Survive
                }
                else
                {
                    // Cell is dead
                    if (aliveNeighbors == 3)
                        newGrid[x, y] = true; // Revive due to reproduction
                    else
                        newGrid[x, y] = false; // Remain dead
                }
            }
        }

        grid = newGrid;
    }

    private void UpdateCells()
    {
        // Update the visual representation of cells based on the grid
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                Renderer renderer = cells[x, y];
                renderer.sharedMaterial.color = grid[x, y] ? Color.black : Color.white;
            }
        }
    }

    private int CountAliveNeighbors(int x, int y)
    {
        int count = 0;

        for (int i = -1; i <= 1; i++)
        {
            for (int j = -1; j <= 1; j++)
            {
                if (i == 0 && j == 0)
                    continue;

                int neighborX = x + i;
                int neighborY = y + j;

                if (neighborX >= 0 && neighborX < width && neighborY >= 0 && neighborY < height)
                {
                    if (grid[neighborX, neighborY])
                        count++;
                }
            }
        }

        return count;
    }
}
  • Atașați scriptul "CellularAutomaton" la un GameObject în scena Unity și atribuiți o celulă prefabricată câmpului 'cellPrefab' din inspector.

Automat celular în Unity.

În acest exemplu, o grilă de celule este reprezentată printr-o matrice booleană, unde 'true' indică o celulă vie și 'false' reprezintă o celulă moartă. Regulile jocului vieții sunt aplicate pentru a actualiza grila, iar reprezentarea vizuală a celulelor este actualizată în consecință. Metoda 'CreateCells()' creează un GameObject pentru fiecare celulă, iar metoda 'UpdateCells()' actualizează culoarea fiecărui GameObject pe baza stării grilei.

Notă: Acesta este doar un exemplu de bază și există multe variante și extensii ale automatelor celulare care pot fi explorate. Regulile, comportamentele celulelor și configurațiile grilei pot fi modificate pentru a crea diferite simulări și pentru a genera diverse modele și comportamente.

Diagrame Voronoi

Diagramele Voronoi, cunoscute și sub numele de teselații Voronoi sau partiții Voronoi, sunt structuri geometrice care împart un spațiu în regiuni bazate pe apropierea de un set de puncte numite semințe sau situri. Fiecare regiune dintr-o diagramă Voronoi constă din toate punctele din spațiu care sunt mai aproape de o anumită sămânță decât de orice altă sămânță.

Teoria de bază din spatele diagramelor Voronoi implică următoarele elemente:

  1. Semințe/Situri: Semințele sau siturile sunt un set de puncte din spațiu. Aceste puncte pot fi generate aleator sau plasate manual. Fiecare sămânță reprezintă un punct central pentru o regiune Voronoi.
  2. Celulele/Regiuni Voronoi: Fiecare celulă sau regiune Voronoi corespunde unei zone a spațiului care este mai aproape de o anumită sămânță decât de orice altă sămânță. Limitele regiunilor sunt formate din bisectoarele perpendiculare ale segmentelor de linie care leagă semințele învecinate.
  3. Triangulația Delaunay: Diagramele Voronoi sunt strâns legate de triangulația Delaunay. Triangulația Delaunay este o triangulare a punctelor de semințe astfel încât nicio sămânță să nu se afle în interiorul cercului circumferitor al vreunui triunghi. Triangulația Delaunay poate fi folosită pentru a construi diagrame Voronoi și invers.

Diagramele Voronoi au diverse aplicații în lumea reală, inclusiv:

  1. Generare de conținut procedural: Diagramele Voronoi pot fi utilizate pentru a genera teren procedural, peisaje naturale și forme organice. Folosind semințele ca puncte de control și atribuind atribute (cum ar fi elevația sau tipul de biom) celulelor Voronoi, pot fi create medii realiste și variate.
  2. Game Design: Diagramele Voronoi pot fi folosite în proiectarea jocului pentru a partiționa spațiul în scopuri de joc. De exemplu, în jocurile de strategie, diagramele Voronoi pot fi folosite pentru a împărți harta jocului în teritorii sau zone controlate de diferite facțiuni.
  3. Pathfinding și AI: Diagramele Voronoi pot ajuta la identificarea căii și la navigarea AI, oferind o reprezentare a spațiului care permite calcularea eficientă a celei mai apropiate semințe sau regiuni. Ele pot fi folosite pentru a defini rețele de navigare sau hărți de influență pentru agenții AI.

În Unity, există mai multe moduri de a genera și utiliza diagramele Voronoi:

  1. Generare procedurală: Dezvoltatorii pot implementa algoritmi pentru a genera diagrame Voronoi dintr-un set de puncte de bază în Unity. Diferiți algoritmi, cum ar fi algoritmul Fortune sau algoritmul de relaxare Lloyd, pot fi utilizați pentru a construi diagrame Voronoi.
  2. Terrain Generation: Diagramele Voronoi pot fi utilizate în generarea terenului pentru a crea peisaje diverse și realiste. Fiecare celulă Voronoi poate reprezenta o caracteristică diferită a terenului, cum ar fi munți, văi sau câmpii. Atribute precum altitudinea, umiditatea sau vegetația pot fi atribuite fiecărei celule, rezultând un teren variat și atrăgător din punct de vedere vizual.
  3. Partiționare hărți: Diagramele Voronoi pot fi folosite pentru a împărți hărțile de joc în regiuni în scopuri de joc. Este posibil să atribuiți diferite atribute sau proprietăți fiecărei regiuni pentru a crea zone de joc distincte. Acest lucru poate fi util pentru jocuri de strategie, mecanică de control teritorial sau design de nivel.

Există pachete și active Unity disponibile care oferă funcționalitate diagramă Voronoi, facilitând încorporarea caracteristicilor bazate pe Voronoi în proiectele Unity. Aceste pachete includ adesea algoritmi de generare a diagramelor Voronoi, instrumente de vizualizare și integrare cu sistemul de randare Unity.

Iată un exemplu de generare a unei diagrame Voronoi 2D în Unity folosind algoritmul Fortune:

using UnityEngine;
using System.Collections.Generic;

public class VoronoiDiagram : MonoBehaviour
{
    public int numSeeds = 50;
    public int diagramSize = 50;
    public GameObject seedPrefab;

    private List<Vector2> seeds = new List<Vector2>();
    private List<List<Vector2>> voronoiCells = new List<List<Vector2>>();

    private void Start()
    {
        GenerateSeeds();
        GenerateVoronoiDiagram();
        VisualizeVoronoiDiagram();
    }

    private void GenerateSeeds()
    {
        // Generate random seeds within the diagram size
        for (int i = 0; i < numSeeds; i++)
        {
            float x = Random.Range(0, diagramSize);
            float y = Random.Range(0, diagramSize);
            seeds.Add(new Vector2(x, y));
        }
    }

    private void GenerateVoronoiDiagram()
    {
        // Compute the Voronoi cells based on the seeds
        for (int i = 0; i < seeds.Count; i++)
        {
            List<Vector2> cell = new List<Vector2>();
            voronoiCells.Add(cell);
        }

        for (int x = 0; x < diagramSize; x++)
        {
            for (int y = 0; y < diagramSize; y++)
            {
                Vector2 point = new Vector2(x, y);
                int closestSeedIndex = FindClosestSeedIndex(point);
                voronoiCells[closestSeedIndex].Add(point);
            }
        }
    }

    private int FindClosestSeedIndex(Vector2 point)
    {
        int closestIndex = 0;
        float closestDistance = Vector2.Distance(point, seeds[0]);

        for (int i = 1; i < seeds.Count; i++)
        {
            float distance = Vector2.Distance(point, seeds[i]);
            if (distance < closestDistance)
            {
                closestDistance = distance;
                closestIndex = i;
            }
        }

        return closestIndex;
    }

    private void VisualizeVoronoiDiagram()
    {
        // Visualize the Voronoi cells by instantiating a sphere for each cell point
        for (int i = 0; i < voronoiCells.Count; i++)
        {
            List<Vector2> cell = voronoiCells[i];
            Color color = Random.ColorHSV();

            foreach (Vector2 point in cell)
            {
                Vector3 position = new Vector3(point.x, 0, point.y);
                GameObject sphere = Instantiate(seedPrefab, position, Quaternion.identity);
                sphere.GetComponent<Renderer>().material.color = color;
            }
        }
    }
}
  • Pentru a utiliza acest cod, creați o sferă prefabricată și atribuiți-o câmpului seedPrefab din inspectorul Unity. Ajustați variabilele numSeeds și diagramSize pentru a controla numărul de semințe și dimensiunea diagramei.

Diagrama Voronoi în Unitate.

În acest exemplu, scriptul VoronoiDiagram generează o diagramă Voronoi prin plasarea aleatorie a punctelor inițiale în dimensiunea specificată a diagramei. Metoda 'GenerateVoronoiDiagram()' calculează celulele Voronoi pe baza punctelor semințe, iar metoda 'VisualizeVoronoiDiagram()' instanțiază o sferă GameObject în fiecare punct al celulelor Voronoi, vizualizând diagrama.

Notă: Acest exemplu oferă o vizualizare de bază a diagramei Voronoi, dar este posibil să o extindeți mai mult prin adăugarea de caracteristici suplimentare, cum ar fi conectarea punctelor celulei cu linii sau atribuirea de atribute diferite fiecărei celule pentru generarea terenului sau în scopuri de joc.

În general, diagramele Voronoi oferă un instrument versatil și puternic pentru generarea de conținut procedural, partiționarea spațiului și crearea de medii interesante și variate în Unity.

Plasarea obiectelor procedurale

Plasarea procedurală a obiectelor în Unity implică generarea și plasarea obiectelor într-o scenă în mod algoritmic, mai degrabă decât poziționarea manuală a acestora. Este o tehnică puternică folosită în diverse scopuri, cum ar fi popularea mediilor cu copaci, pietre, clădiri sau alte obiecte într-o manieră naturală și dinamică.

Iată un exemplu de plasare a obiectelor procedurale în Unity:

using UnityEngine;

public class ObjectPlacement : MonoBehaviour
{
    public GameObject objectPrefab;
    public int numObjects = 50;
    public Vector3 spawnArea = new Vector3(10f, 0f, 10f);

    private void Start()
    {
        PlaceObjects();
    }

    private void PlaceObjects()
    {
        for (int i = 0; i < numObjects; i++)
        {
            Vector3 spawnPosition = GetRandomSpawnPosition();
            Quaternion spawnRotation = Quaternion.Euler(0f, Random.Range(0f, 360f), 0f);
            Instantiate(objectPrefab, spawnPosition, spawnRotation);
        }
    }

    private Vector3 GetRandomSpawnPosition()
    {
        Vector3 center = transform.position;
        Vector3 randomPoint = center + new Vector3(
            Random.Range(-spawnArea.x / 2, spawnArea.x / 2),
            0f,
            Random.Range(-spawnArea.z / 2, spawnArea.z / 2)
        );
        return randomPoint;
    }
}
  • Pentru a utiliza acest script, creați un GameObject gol în scena Unity și atașați scriptul "ObjectPlacement". Atribuiți obiectul prefabricat și ajustați parametrii 'numObjects' și 'spawnArea' în inspector pentru a se potrivi cerințelor. Când rulează scena, obiectele vor fi plasate procedural în zona de apariție definită.

Plasarea obiectelor procedurale în Unity.

În acest exemplu, scriptul 'ObjectPlacement' este responsabil pentru plasarea procedurală a obiectelor în scenă. Câmpul 'objectPrefab' ar trebui să fie alocat cu prefabricatul obiectului de plasat. Variabila 'numObjects' determină numărul de obiecte de plasat, iar variabila 'spawnArea' definește zona în care obiectele vor fi poziționate aleatoriu.

Metoda 'PlaceObjects()' parcurge numărul dorit de obiecte și generează poziții aleatorii de apariție în zona de apariție definită. Apoi instanțiază obiectul prefabricat în fiecare poziție aleatorie cu o rotație aleatorie.

Notă: Este posibil să îmbunătățiți și mai mult acest cod prin încorporarea diverșilor algoritmi de plasare, cum ar fi plasarea bazată pe grilă, plasarea bazată pe densitate sau plasarea bazată pe reguli, în funcție de cerințele specifice ale proiectului.

Concluzie

Tehnicile de generare procedurală în Unity oferă instrumente puternice pentru a crea experiențe dinamice și captivante. Fie că este vorba despre generarea de terenuri folosind zgomot Perlin sau algoritmi fractali, crearea de medii diverse cu diagrame Voronoi, simularea comportamentelor complexe cu automate celulare sau popularea scenelor cu obiecte plasate procedural, aceste tehnici oferă flexibilitate, eficiență și posibilități nesfârșite pentru generarea de conținut. Utilizând acești algoritmi și integrându-i în proiecte Unity, dezvoltatorii pot obține generarea realistă a terenului, simulări realiste, medii atractive din punct de vedere vizual și mecanisme de joc antrenante. Generarea procedurală nu numai că economisește timp și efort, dar permite și crearea de experiențe unice și în continuă schimbare, care captivează jucătorii și aduc lumi virtuale la viață.