Procedurell världsgeneration i enhet

Världsgenerationen i Unity hänvisar till processen att skapa eller procedurgenerera virtuella världar, terräng, landskap eller miljöer inom spelmotorn Unity. Denna teknik används ofta i olika typer av spel, såsom spel i öppen värld, RPG, simuleringar och mer, för att dynamiskt skapa stora och mångfaldiga spelvärldar.

Unity ger ett flexibelt ramverk och ett brett utbud av verktyg och API:er för att implementera dessa världsgenererade tekniker. Man kan skriva anpassade skript med C# för att generera och manipulera spelvärlden eller använda Unity inbyggda funktioner som terrängsystemet, brusfunktioner och skriptgränssnitt för att uppnå önskade resultat. Dessutom finns det också tillgångar från tredje part och plugins tillgängliga på Unity Asset Store som kan hjälpa till i världsgenereringsuppgifter.

Det finns flera tillvägagångssätt för världsgenerering i Unity, och valet beror på spelets specifika krav. Här är några vanliga metoder:

  • Procedurell terränggenerering med Perlin-brus
  • Cellulär automat
  • Voronoi diagram
  • Procedurmässig objektplacering

Procedurell terränggenerering med Perlin-brus

Procedural terränggenerering i Unity kan uppnås med hjälp av olika algoritmer och tekniker. Ett populärt tillvägagångssätt är att använda Perlin-brus för att generera höjdkartan och sedan tillämpa olika texturerings- och lövverkstekniker för att skapa en realistisk eller stiliserad terräng.

Perlin-brus är en typ av gradientbrus som utvecklats av Ken Perlin. Det genererar ett jämnt, kontinuerligt mönster av värden som verkar slumpmässiga men har en sammanhängande struktur. Perlin-brus används ofta för att skapa naturliga terränger, moln, texturer och andra organiska former.

I Unity kan man använda funktionen 'Mathf.PerlinNoise()' för att generera Perlin-brus. Den tar två koordinater som indata och returnerar ett värde mellan 0 och 1. Genom att sampla Perlin-brus vid olika frekvenser och amplituder är det möjligt att skapa olika detaljnivåer och komplexitet i det procedurmässiga innehållet.

Här är ett exempel på hur man implementerar detta i Unity:

  • I Unity Editor, gå till "GameObject -> 3D Object -> Terrain". Detta kommer att skapa en standardterräng i scenen.
  • Skapa ett nytt C#-skript som heter "TerrainGenerator" och fästa det till terrängobjektet. Här är ett exempelskript som genererar en procedurterräng med Perlin-brus:
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;
    }
}
  • Bifoga "TerrainGenerator"-skriptet till Terrain-objektet i Unity Editor.
  • I inspektörsfönstret för terrängobjektet justerar du bredd, höjd, skala, förskjutningar och brusintensitet för att justera den genererade terrängens utseende.
  • Tryck på Play-knappen i Unity Editor, och den procedurmässiga terrängen ska sedan genereras baserat på Perlin-brusalgoritmen.

Unity Terränggenerering med Perlin-brus.

Obs: Det här skriptet genererar en grundläggande terränghöjdskarta med Perlin-brus. För att skapa mer komplexa terränger, ändra skriptet för att införliva ytterligare brusalgoritmer, tillämpa erosions- eller utjämningstekniker, lägg till texturering eller placera lövverk och objekt baserat på terrängens egenskaper.

Cellulär automat

Cellulära automater är en beräkningsmodell som består av ett rutnät av celler, där varje cell utvecklas baserat på en uppsättning fördefinierade regler och tillstånden för dess närliggande celler. Det är ett kraftfullt koncept som används inom olika områden, inklusive datavetenskap, matematik och fysik. Cellulära automater kan uppvisa komplexa beteendemönster som kommer från enkla regler, vilket gör dem användbara för att simulera naturfenomen och generera procedurinnehåll.

Den grundläggande teorin bakom cellulära automater involverar följande element:

  1. Rutnät: Ett rutnät är en samling celler ordnade i ett regelbundet mönster, till exempel ett kvadratiskt eller sexkantigt gitter. Varje cell kan ha ett ändligt antal tillstånd.
  2. Grannar: Varje cell har angränsande celler, som vanligtvis är dess omedelbart intilliggande celler. Grannskapet kan definieras utifrån olika anslutningsmönster, såsom von Neumann (upp, ner, vänster, höger) eller Moore (inklusive diagonala) stadsdelar.
  3. Regler: Varje cells beteende bestäms av en uppsättning regler som anger hur den utvecklas baserat på dess nuvarande tillstånd och tillstånden för dess närliggande celler. Dessa regler definieras vanligtvis med villkorliga uttalanden eller uppslagstabeller.
  4. Uppdatering: Den cellulära automaten utvecklas genom att uppdatera tillståndet för varje cell samtidigt enligt reglerna. Denna process upprepas iterativt, vilket skapar en sekvens av generationer.

Cellulära automater har olika verkliga tillämpningar, inklusive:

  1. Simulering av naturfenomen: Cellulära automater kan simulera beteendet hos fysiska system, såsom vätskedynamik, skogsbränder, trafikflöden och befolkningsdynamik. Genom att definiera lämpliga regler kan cellulära automater fånga de framväxande mönstren och dynamiken som observeras i verkliga system.
  2. Generering av procedurinnehåll: Cellulära automater kan användas för att generera procedurinnehåll i spel och simuleringar. Till exempel kan de användas för att skapa terräng, grottsystem, vegetationsfördelning och andra organiska strukturer. Komplexa och realistiska miljöer kan skapas genom att specificera regler som styr cellers tillväxt och interaktion.

Här är ett enkelt exempel på att implementera en grundläggande cellulär automat i Unity för att simulera livets spel:

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;
    }
}
  • Bifoga "CellularAutomaton"-skriptet till ett GameObject i Unity-scenen och tilldela en cellprefab till fältet 'cellPrefab' i inspektören.

Mobilautomat i Unity.

I det här exemplet representeras ett rutnät av celler av en boolesk array, där 'true' indikerar en levande cell och 'false' representerar en död cell. Reglerna för livets spel tillämpas för att uppdatera rutnätet, och den visuella representationen av celler uppdateras därefter. Metoden 'CreateCells()' skapar ett GameObject för varje cell, och metoden 'UpdateCells()' uppdaterar färgen på varje GameObject baserat på rutnätstillståndet.

Obs: Detta är bara ett grundläggande exempel, och det finns många varianter och tillägg till cellulära automater som kan utforskas. Reglerna, cellbeteendena och rutnätskonfigurationerna kan modifieras för att skapa olika simuleringar och generera olika mönster och beteenden.

Voronoi diagram

Voronoi-diagram, även kända som Voronoi-tesselationer eller Voronoi-partitioner, är geometriska strukturer som delar upp ett utrymme i regioner baserat på närhet till en uppsättning punkter som kallas frön eller platser. Varje region i ett Voronoi-diagram består av alla punkter i utrymmet som är närmare ett visst frö än något annat frö.

Den grundläggande teorin bakom Voronoi-diagram involverar följande element:

  1. Frön/platser: Frön eller platser är en uppsättning punkter i utrymmet. Dessa punkter kan genereras slumpmässigt eller placeras manuellt. Varje frö representerar en mittpunkt för en Voronoi-region.
  2. Voronoi-celler/regioner: Varje Voronoi-cell eller region motsvarar ett område i utrymmet som är närmare ett visst frö än något annat frö. Gränserna för regionerna bildas av de vinkelräta bisektorerna av linjesegmenten som förbinder närliggande frön.
  3. Delaunay-triangulering: Voronoi-diagram är nära besläktade med Delaunay-triangulering. Delaunay-triangulering är en triangulering av fröpunkterna så att inget frö är inuti den omgivna cirkeln av någon triangel. Delaunay-trianguleringen kan användas för att konstruera Voronoi-diagram och vice versa.

Voronoi-diagram har olika tillämpningar i den verkliga världen, inklusive:

  1. Generering av procedurinnehåll: Voronoi-diagram kan användas för att generera procedurmässig terräng, naturliga landskap och organiska former. Genom att använda fröna som kontrollpunkter och tilldela attribut (som höjd eller biomtyp) till Voronoi-cellerna, kan realistiska och varierande miljöer skapas.
  2. Speldesign: Voronoi-diagram kan användas i speldesign för att dela upp utrymme för speländamål. Till exempel, i strategispel kan Voronoi-diagram användas för att dela upp spelkartan i territorier eller zoner som kontrolleras av olika fraktioner.
  3. Pathfinding och AI: Voronoi-diagram kan hjälpa till med pathfinding och AI-navigering genom att tillhandahålla en representation av utrymmet som möjliggör effektiv beräkning av närmaste frö eller region. De kan användas för att definiera navigeringsnät eller påverka kartor för AI-agenter.

I Unity finns det flera sätt att generera och använda Voronoi-diagram:

  1. Procedurgenerering: Utvecklare kan implementera algoritmer för att generera Voronoi-diagram från en uppsättning startpunkter i Unity. Olika algoritmer, såsom Fortunes algoritm eller Lloyds relaxationsalgoritm, kan användas för att konstruera Voronoi-diagram.
  2. Terränggenerering: Voronoi-diagram kan användas i terränggenerering för att skapa mångsidiga och realistiska landskap. Varje Voronoi-cell kan representera olika terrängegenskaper, såsom berg, dalar eller slätter. Attribut som höjd, fukt eller vegetation kan tilldelas varje cell, vilket resulterar i en varierad och visuellt tilltalande terräng.
  3. Kartpartitionering: Voronoi-diagram kan användas för att dela upp spelkartor i regioner för speländamål. Det är möjligt att tilldela olika attribut eller egenskaper till varje region för att skapa distinkta spelzoner. Detta kan vara användbart för strategispel, territoriell kontrollmekanik eller nivådesign.

Det finns Unity-paket och tillgångar tillgängliga som tillhandahåller Voronoi-diagramfunktionalitet, vilket gör det lättare att införliva Voronoi-baserade funktioner i Unity-projekt. Dessa paket inkluderar ofta Voronoi-diagramgenereringsalgoritmer, visualiseringsverktyg och integration med Unity-renderingssystemet.

Här är ett exempel på att generera ett 2D Voronoi-diagram i Unity med hjälp av Fortunes algoritm:

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;
            }
        }
    }
}
  • För att använda den här koden, skapa en sfärprefab och tilldela den till seedPrefab-fältet i Unity-inspektören. Justera variablerna numSeeds och diagramSize för att kontrollera antalet frön och storleken på diagrammet.

Voronoi-diagram i Unity.

I det här exemplet genererar VoronoiDiagram-skriptet ett Voronoi-diagram genom att slumpmässigt placera startpunkter inom den angivna diagramstorleken. Metoden 'GenerateVoronoiDiagram()' beräknar Voronoi-cellerna baserat på fröpunkterna, och metoden 'VisualizeVoronoiDiagram()' instansierar ett sfäriskt GameObject vid varje punkt i Voronoi-cellerna och visualiserar diagrammet.

Obs: Det här exemplet ger en grundläggande visualisering av Voronoi-diagrammet, men det är möjligt att utöka det ytterligare genom att lägga till ytterligare funktioner, som att koppla ihop cellpunkterna med linjer eller tilldela olika attribut till varje cell för terränggenerering eller speländamål.

Sammantaget erbjuder Voronoi-diagram ett mångsidigt och kraftfullt verktyg för att generera procedurinnehåll, partitionera utrymme och skapa intressanta och varierade miljöer i Unity.

Procedurmässig objektplacering

Procedurmässig objektplacering i Unity involverar att generera och placera objekt i en scen algoritmiskt, snarare än att manuellt placera dem. Det är en kraftfull teknik som används för olika ändamål, som att befolka miljöer med träd, stenar, byggnader eller andra föremål på ett naturligt och dynamiskt sätt.

Här är ett exempel på procedurobjektplacering i 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;
    }
}
  • För att använda det här skriptet, skapa ett tomt GameObject i Unity-scenen och bifoga "ObjectPlacement"-skriptet till det. Tilldela objektet prefab och justera parametrarna 'numObjects' och 'spawnArea' i inspektören för att passa kraven. När du kör scenen kommer objekten att placeras procedurmässigt inom det definierade spawnområdet.

Procedurmässig objektplacering i Unity.

I det här exemplet är 'ObjectPlacement' skriptet ansvarigt för att procedurmässigt placera objekt i scenen. Fältet 'objectPrefab' bör tilldelas prefab för objektet som ska placeras. Variabeln 'numObjects' bestämmer antalet objekt som ska placeras, och variabeln 'spawnArea' definierar området där objekten kommer att placeras slumpmässigt.

Metoden 'PlaceObjects()' går igenom det önskade antalet objekt och genererar slumpmässiga spawnpositioner inom det definierade spawnområdet. Den instansierar sedan objektet prefab vid varje slumpmässig position med en slumpmässig rotation.

Obs: Det är möjligt att ytterligare förbättra den här koden genom att inkludera olika placeringsalgoritmer, såsom rutnätsbaserad placering, densitetsbaserad placering eller regelbaserad placering, beroende på projektets specifika krav.

Slutsats

Procedurgenereringstekniker i Unity ger kraftfulla verktyg för att skapa dynamiska och uppslukande upplevelser. Oavsett om det handlar om att generera terräng med Perlin-brus eller fraktala algoritmer, skapa olika miljöer med Voronoi-diagram, simulera komplexa beteenden med cellulära automater, eller fylla scener med procedurmässigt placerade objekt, erbjuder dessa tekniker flexibilitet, effektivitet och oändliga möjligheter för innehållsgenerering. Genom att utnyttja dessa algoritmer och integrera dem i Unity-projekt kan utvecklare uppnå realistisk terränggenerering, verklighetstrogna simuleringar, visuellt tilltalande miljöer och engagerande spelmekanik. Procedurgenerering sparar inte bara tid och ansträngning utan möjliggör också skapandet av unika och ständigt föränderliga upplevelser som fängslar spelare och ger virtuella världar liv.

Föreslagna artiklar
Betydelsen av storytelling i Unity Game Development
Måste ha tillgångar för allmänna ändamål för enhet
Implementera objektorienterad programmering (OOP) koncept i enhet
Enhet som arbetar med prefabs
Twittertips för enhet
Hur man målar träd på terräng i Unity
Hur man importerar animationer till Unity