Prosedyremessig verdensgenerasjon i enhet

Verdensgenerasjonen i Unity refererer til prosessen med å skape eller prosessuelt generere virtuelle verdener, terreng, landskap eller miljøer i Unity-spillmotoren. Denne teknikken brukes ofte i ulike typer spill, for eksempel spill i åpen verden, rollespill, simuleringer og mer, for dynamisk å skape enorme og mangfoldige spillverdener.

Unity gir et fleksibelt rammeverk og et bredt spekter av verktøy og APIer for implementering av disse verdensgenerasjonsteknikkene. Man kan skrive tilpassede skript ved å bruke C# for å generere og manipulere spillverdenen eller bruke Unity innebygde funksjoner som terrengsystemet, støyfunksjoner og skriptgrensesnitt for å oppnå de ønskede resultatene. I tillegg er det også tredjeparts ressurser og plugins tilgjengelig på Unity Asset Store som kan hjelpe til med verdensgenerasjonsoppgaver.

Det er flere tilnærminger til verdensgenerering i Unity, og valget avhenger av de spesifikke kravene til spillet. Her er noen vanlige metoder:

  • Prosedyremessig terrenggenerering med Perlin-støy
  • Mobilautomater
  • Voronoi-diagrammer
  • Prosedyremessig objektplassering

Prosedyremessig terrenggenerering med Perlin-støy

Prosedyremessig terrenggenerering i Unity kan oppnås ved hjelp av ulike algoritmer og teknikker. En populær tilnærming er å bruke Perlin-støy for å generere høydekartet og deretter bruke ulike teksturerings- og løvverksteknikker for å lage et realistisk eller stilisert terreng.

Perlin-støy er en type gradientstøy utviklet av Ken Perlin. Den genererer et jevnt, kontinuerlig mønster av verdier som virker tilfeldige, men som har en sammenhengende struktur. Perlin-støy er mye brukt for å lage naturlig utseende terreng, skyer, teksturer og andre organiske former.

I Unity kan man bruke funksjonen 'Mathf.PerlinNoise()' for å generere Perlin-støy. Den tar to koordinater som input og returnerer en verdi mellom 0 og 1. Ved å sample Perlin-støy ved forskjellige frekvenser og amplituder, er det mulig å skape forskjellige nivåer av detaljer og kompleksitet i det prosedyremessige innholdet.

Her er et eksempel på hvordan du implementerer dette i Unity:

  • I Unity Editor, gå til "GameObject -> 3D Object -> Terrain". Dette vil opprette et standardterreng i scenen.
  • Lag et nytt C#-skript kalt "TerrainGenerator" og fest det til terrengobjektet. Her er et eksempelskript som genererer et prosedyreterreng ved hjelp av Perlin-støy:
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;
    }
}
  • Fest "TerrainGenerator"-skriptet til Terrain-objektet i Unity Editor.
  • I inspektørvinduet for terrengobjektet justerer du bredden, høyden, skalaen, forskyvninger og støyintensiteten for å justere det genererte terrengets utseende.
  • Trykk på Play-knappen i Unity Editor, og det prosedyremessige terrenget skal da genereres basert på Perlin-støyalgoritmen.

Unity Terrenggenerering med Perlin-støy.

Merk: Dette skriptet genererer et grunnleggende terrenghøydekart ved hjelp av Perlin-støy. For å lage mer komplekse terreng, endre skriptet for å inkludere ytterligere støyalgoritmer, bruk erosjons- eller utjevningsteknikker, legg til teksturering, eller plasser løvverk og objekter basert på terrengets egenskaper.

Mobilautomater

Cellular automata er en beregningsmodell som består av et rutenett av celler, der hver celle utvikler seg basert på et sett med forhåndsdefinerte regler og tilstandene til nabocellene. Det er et kraftig konsept som brukes på forskjellige felt, inkludert informatikk, matematikk og fysikk. Cellulære automater kan vise komplekse atferdsmønstre som kommer fra enkle regler, noe som gjør dem nyttige for å simulere naturfenomener og generere prosedyreinnhold.

Den grunnleggende teorien bak cellulære automater involverer følgende elementer:

  1. Rutenett: Et rutenett er en samling av celler arrangert i et vanlig mønster, for eksempel et kvadratisk eller sekskantet gitter. Hver celle kan ha et begrenset antall tilstander.
  2. Naboer: Hver celle har naboceller, som vanligvis er dens umiddelbare tilstøtende celler. Nabolaget kan defineres basert på forskjellige tilkoblingsmønstre, for eksempel von Neumann (opp, ned, venstre, høyre) eller Moore (inkludert diagonale) nabolag.
  3. Regler: Oppførselen til hver celle bestemmes av et sett med regler som spesifiserer hvordan den utvikler seg basert på dens nåværende tilstand og tilstandene til nabocellene. Disse reglene er vanligvis definert ved hjelp av betingede setninger eller oppslagstabeller.
  4. Oppdatering: Den cellulære automaten utvikler seg ved å oppdatere tilstanden til hver celle samtidig i henhold til reglene. Denne prosessen gjentas iterativt, og skaper en sekvens av generasjoner.

Mobilautomater har forskjellige applikasjoner fra den virkelige verden, inkludert:

  1. Simulering av naturfenomener: Cellulære automater kan simulere atferden til fysiske systemer, slik som væskedynamikk, skogbranner, trafikkflyt og befolkningsdynamikk. Ved å definere passende regler kan cellulære automater fange opp de fremvoksende mønstrene og dynamikkene som observeres i systemer i den virkelige verden.
  2. Generering av prosedyreinnhold: Mobilautomater kan brukes til å generere prosedyreinnhold i spill og simuleringer. For eksempel kan de brukes til å lage terreng, hulesystemer, vegetasjonsfordeling og andre organiske strukturer. Komplekse og realistiske miljøer kan genereres ved å spesifisere regler som styrer vekst og interaksjon av celler.

Her er et enkelt eksempel på implementering av en grunnleggende mobilautomat i Unity for å simulere livets spill:

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;
    }
}
  • Fest "CellularAutomaton"-skriptet til et GameObject i Unity-scenen og tilordne en celleprefabrikert til feltet 'cellPrefab' i inspektøren.

Mobilautomat i Unity.

I dette eksemplet er et rutenett av celler representert av en boolsk matrise, der 'true' indikerer en levende celle og 'false' representerer en død celle. Reglene for livets spill brukes for å oppdatere rutenettet, og den visuelle representasjonen av celler oppdateres deretter. 'CreateCells()'-metoden oppretter et GameObject for hver celle, og metoden 'UpdateCells()' oppdaterer fargen på hvert GameObject basert på rutenetttilstanden.

Merk: Dette er bare et grunnleggende eksempel, og det er mange varianter og utvidelser til mobilautomater som kan utforskes. Reglene, celleatferden og rutenettkonfigurasjonene kan endres for å lage forskjellige simuleringer og generere forskjellige mønstre og atferd.

Voronoi-diagrammer

Voronoi-diagrammer, også kjent som Voronoi-tesselasjoner eller Voronoi-partisjoner, er geometriske strukturer som deler et rom i regioner basert på nærhet til et sett med punkter kalt frø eller steder. Hver region i et Voronoi-diagram består av alle punkter i rommet som er nærmere et bestemt frø enn noe annet frø.

Den grunnleggende teorien bak Voronoi-diagrammer involverer følgende elementer:

  1. Frø/steder: Frø eller steder er et sett med punkter i rommet. Disse punktene kan genereres tilfeldig eller plasseres manuelt. Hvert frø representerer et midtpunkt for en Voronoi-region.
  2. Voronoi-celler/regioner: Hver Voronoi-celle eller region tilsvarer et område av rommet som er nærmere et bestemt frø enn noe annet frø. Grensene til regionene er dannet av de vinkelrette halveringslinjene til linjesegmentene som forbinder nabofrø.
  3. Delaunay-triangulering: Voronoi-diagrammer er nært beslektet med Delaunay-triangulering. Delaunay-triangulering er en triangulering av frøpunktene slik at ingen frø er innenfor omkretssirkelen til noen trekant. Delaunay-trianguleringen kan brukes til å konstruere Voronoi-diagrammer, og omvendt.

Voronoi-diagrammer har forskjellige applikasjoner i den virkelige verden, inkludert:

  1. Generering av prosedyreinnhold: Voronoi-diagrammer kan brukes til å generere prosedyreterreng, naturlige landskap og organiske former. Ved å bruke frøene som kontrollpunkter og tilordne attributter (som høyde eller biomtype) til Voronoi-cellene, kan realistiske og varierte miljøer skapes.
  2. Spilldesign: Voronoi-diagrammer kan brukes i spilldesign for å dele opp plass for spillformål. For eksempel, i strategispill, kan Voronoi-diagrammer brukes til å dele spillkartet inn i territorier eller soner kontrollert av forskjellige fraksjoner.
  3. Pathfinding og AI: Voronoi-diagrammer kan hjelpe til med pathfinding og AI-navigering ved å gi en representasjon av plassen som muliggjør effektiv beregning av nærmeste frø eller region. De kan brukes til å definere navigasjonsmasker eller påvirke kart for AI-agenter.

I Unity er det flere måter å generere og bruke Voronoi-diagrammer på:

  1. Procedural Generation: Utviklere kan implementere algoritmer for å generere Voronoi-diagrammer fra et sett med startpunkter i Unity. Ulike algoritmer, for eksempel Fortunes algoritme eller Lloyd-relaksasjonsalgoritmen, kan brukes til å konstruere Voronoi-diagrammer.
  2. Terrenggenerering: Voronoi-diagrammer kan brukes i terrenggenerering for å skape mangfoldige og realistiske landskap. Hver Voronoi-celle kan representere en annen terrengfunksjon, for eksempel fjell, daler eller sletter. Attributter som høyde, fuktighet eller vegetasjon kan tildeles hver celle, noe som resulterer i et variert og visuelt tiltalende terreng.
  3. Kartpartisjonering: Voronoi-diagrammer kan brukes til å dele spillkart inn i regioner for spillformål. Det er mulig å tilordne forskjellige attributter eller egenskaper til hver region for å lage forskjellige spillsoner. Dette kan være nyttig for strategispill, territoriell kontrollmekanikk eller nivådesign.

Det er Unity-pakker og aktiva tilgjengelig som gir Voronoi-diagramfunksjonalitet, noe som gjør det enklere å inkorporere Voronoi-baserte funksjoner i Unity-prosjekter. Disse pakkene inkluderer ofte Voronoi-diagramgenereringsalgoritmer, visualiseringsverktøy og integrasjon med Unity-gjengivelsessystemet.

Her er et eksempel på å generere et 2D Voronoi-diagram i Unity ved å bruke Fortunes algoritme:

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;
            }
        }
    }
}
  • For å bruke denne koden, lag en sfære-prefab og tilordne den til seedPrefab-feltet i Unity-inspektøren. Juster numSeeds og diagramSize-variablene for å kontrollere antall frø og størrelsen på diagrammet.

Voronoi-diagram i Unity.

I dette eksemplet genererer VoronoiDiagram-skriptet et Voronoi-diagram ved å tilfeldig plassere frøpunkter innenfor den angitte diagramstørrelsen. Metoden 'GenerateVoronoiDiagram()' beregner Voronoi-cellene basert på frøpunktene, og metoden 'VisualizeVoronoiDiagram()' instansierer et sfære GameObject ved hvert punkt i Voronoi-cellene, og visualiserer diagrammet.

Merk: Dette eksemplet gir en grunnleggende visualisering av Voronoi-diagrammet, men det er mulig å utvide det ytterligere ved å legge til tilleggsfunksjoner, for eksempel å koble cellepunktene med linjer eller tilordne forskjellige attributter til hver celle for terrenggenerering eller spillformål.

Samlet sett tilbyr Voronoi-diagrammer et allsidig og kraftig verktøy for å generere prosedyreinnhold, partisjonere plass og skape interessante og varierte miljøer i Unity.

Prosedyremessig objektplassering

Prosedyremessig objektplassering i Unity innebærer å generere og plassere objekter i en scene algoritmisk, i stedet for å manuelt posisjonere dem. Det er en kraftig teknikk som brukes til ulike formål, for eksempel å befolke miljøer med trær, steiner, bygninger eller andre gjenstander på en naturlig og dynamisk måte.

Her er et eksempel på prosedyremessig objektplassering 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;
    }
}
  • For å bruke dette skriptet, lag et tomt GameObject i Unity-scenen og fest "ObjectPlacement"-skriptet til det. Tildel objektprefabrikken og juster parameterne 'numObjects' og 'spawnArea' i inspektøren for å passe til kravene. Når du kjører scenen, vil objektene bli plassert prosedyremessig innenfor det definerte gyteområdet.

Prosedyremessig objektplassering i Unity.

I dette eksemplet er 'ObjectPlacement'-skriptet ansvarlig for prosedyremessig plassering av objekter i scenen. 'objectPrefab'-feltet skal tilordnes prefabrikken til objektet som skal plasseres. Variabelen 'numObjects' bestemmer antall objekter som skal plasseres, og variabelen 'spawnArea' definerer området der objektene skal plasseres tilfeldig.

'PlaceObjects()'-metoden går gjennom ønsket antall objekter og genererer tilfeldige spawn-posisjoner innenfor det definerte spawn-området. Den instansierer deretter objektprefabrikken ved hver tilfeldig posisjon med en tilfeldig rotasjon.

Merk: Det er mulig å forbedre denne koden ytterligere ved å inkludere ulike plasseringsalgoritmer, for eksempel rutenettbasert plassering, tetthetsbasert plassering eller regelbasert plassering, avhengig av de spesifikke kravene til prosjektet.

Konklusjon

Prosedyregenereringsteknikker i Unity gir kraftige verktøy for å skape dynamiske og oppslukende opplevelser. Enten det er å generere terreng ved hjelp av Perlin-støy eller fraktale algoritmer, skape forskjellige miljøer med Voronoi-diagrammer, simulere kompleks atferd med mobilautomater, eller fylle scener med prosedyremessig plasserte objekter, tilbyr disse teknikkene fleksibilitet, effektivitet og uendelige muligheter for innholdsgenerering. Ved å utnytte disse algoritmene og integrere dem i Unity-prosjekter, kan utviklere oppnå realistisk terrenggenerering, naturtro simuleringer, visuelt tiltalende miljøer og engasjerende spillmekanikk. Prosedyregenerering sparer ikke bare tid og krefter, men gjør det også mulig å skape unike og stadig skiftende opplevelser som fengsler spillere og gir virtuelle verdener liv.

Foreslåtte artikler
Viktigheten av historiefortelling i Unity-spillutvikling
Må-ha generelle eiendeler for enhet
Hvordan male trær på terreng i enhet
Implementering av objektorientert programmering (OOP) konsepter i enhet
Twitter-tips for enhet
Hvordan importere animasjoner til Unity
Velg riktig Skybox for miljøet ditt i Unity