Generación del mundo procesal en unidad

La generación de mundos en Unity se refiere al proceso de creación o generación procedimental de mundos, terrenos, paisajes o entornos virtuales dentro del motor de juego Unity. Esta técnica se usa comúnmente en varios tipos de juegos, como juegos de mundo abierto, juegos de rol, simulaciones y más, para crear dinámicamente mundos de juego vastos y diversos.

Unity proporciona un marco flexible y una amplia gama de herramientas y API para implementar estas técnicas de generación mundial. Se pueden escribir scripts personalizados usando C# para generar y manipular el mundo del juego o utilizar funciones integradas Unity como el sistema Terrain, funciones de ruido e interfaces de scripting para lograr los resultados deseados. Además, también hay recursos de terceros y plugins disponibles en el Unity Asset Store que pueden ayudar en las tareas de generación de mundos.

Hay varios enfoques para la generación de mundos en Unity, y la elección depende de los requisitos específicos del juego. A continuación se muestran algunos métodos utilizados habitualmente:

  • Generación procesal de terreno con ruido Perlin
  • Autómata celular
  • Diagramas de Voronoi
  • Colocación de objetos procesales

Generación procesal de terreno con ruido Perlin

La generación procesal de terreno en Unity se puede lograr utilizando varios algoritmos y técnicas. Un enfoque popular es utilizar ruido Perlin para generar el mapa de altura y luego aplicar varias técnicas de textura y follaje para crear un terreno realista o estilizado.

El ruido Perlin es un tipo de ruido gradiente desarrollado por Ken Perlin. Genera un patrón suave y continuo de valores que parecen aleatorios pero tienen una estructura coherente. El ruido Perlin se utiliza ampliamente para crear terrenos, nubes, texturas y otras formas orgánicas de aspecto natural.

En Unity, se puede utilizar la función 'Mathf.PerlinNoise()' para generar ruido Perlin. Toma dos coordenadas como entrada y devuelve un valor entre 0 y 1. Al muestrear ruido Perlin en diferentes frecuencias y amplitudes, es posible crear diferentes niveles de detalle y complejidad en el contenido del procedimiento.

Aquí hay un ejemplo de cómo implementar esto en Unity:

  • En el editor Unity, vaya a "GameObject -> 3D Object -> Terrain". Esto creará un terreno predeterminado en la escena.
  • Cree un nuevo script C# llamado "TerrainGenerator" y adjunte al objeto del terreno. Aquí hay un script de ejemplo que genera un terreno procesal usando ruido 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;
    }
}
  • Adjunte el script "TerrainGenerator" al objeto Terreno en el Editor Unity.
  • En la ventana del inspector del objeto de terreno, ajuste el ancho, la altura, la escala, los desplazamientos y la intensidad del ruido para modificar la apariencia del terreno generado.
  • Presione el botón Reproducir en el editor Unity y el terreno procedimental debería generarse basándose en el algoritmo de ruido Perlin.

Generación Unity Terrain con ruido Perlin.

Nota: Este script genera un mapa básico de altura del terreno utilizando ruido Perlin. Para crear terrenos más complejos, modifique el script para incorporar algoritmos de ruido adicionales, aplique técnicas de erosión o suavizado, agregue texturas o coloque follaje y objetos según las características del terreno.

Autómata celular

Los autómatas celulares son un modelo computacional que consta de una cuadrícula de celdas, donde cada celda evoluciona en función de un conjunto de reglas predefinidas y los estados de sus celdas vecinas. Es un concepto poderoso utilizado en diversos campos, incluidos la informática, las matemáticas y la física. Los autómatas celulares pueden exhibir patrones de comportamiento complejos que surgen de reglas simples, lo que los hace útiles para simular fenómenos naturales y generar contenido procedimental.

La teoría básica detrás de los autómatas celulares involucra los siguientes elementos:

  1. Cuadrícula: una cuadrícula es una colección de celdas dispuestas en un patrón regular, como una celosía cuadrada o hexagonal. Cada celda puede tener un número finito de estados.
  2. Vecinos: cada celda tiene celdas vecinas, que normalmente son sus celdas adyacentes inmediatas. La vecindad se puede definir en función de diferentes patrones de conectividad, como las vecindades de von Neumann (arriba, abajo, izquierda, derecha) o Moore (incluida la diagonal).
  3. Reglas: El comportamiento de cada celda está determinado por un conjunto de reglas que especifican cómo evoluciona en función de su estado actual y los estados de sus celdas vecinas. Estas reglas normalmente se definen mediante declaraciones condicionales o tablas de búsqueda.
  4. Actualización: El autómata celular evoluciona actualizando el estado de cada celda simultáneamente según las reglas. Este proceso se repite de forma iterativa, creando una secuencia de generaciones.

Los autómatas celulares tienen varias aplicaciones del mundo real, que incluyen:

  1. Simulación de fenómenos naturales: Los autómatas celulares pueden simular el comportamiento de sistemas físicos, como dinámica de fluidos, incendios forestales, flujo de tráfico y dinámica de poblaciones. Al definir reglas apropiadas, los autómatas celulares pueden capturar los patrones y dinámicas emergentes observados en los sistemas del mundo real.
  2. Generación de contenido procedimental: Los autómatas celulares se pueden utilizar para generar contenido procedimental en juegos y simulaciones. Por ejemplo, pueden emplearse para crear terreno, sistemas de cuevas, distribución de vegetación y otras estructuras orgánicas. Se pueden generar entornos complejos y realistas especificando reglas que gobiernen el crecimiento y la interacción de las células.

Aquí hay un ejemplo simple de implementación de un autómata celular básico en Unity para simular el juego de la vida:

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;
    }
}
  • Adjunte el script "CellularAutomaton" a un GameObject en la escena Unity y asigne una celda prefabricada al campo 'cellPrefab' en el inspector.

Autómata celular en Unity.

En este ejemplo, una cuadrícula de celdas está representada por una matriz booleana, donde 'true' indica una celda viva y 'false' representa una celda muerta. Las reglas del juego de la vida se aplican para actualizar la cuadrícula y la representación visual de las celdas se actualiza en consecuencia. El método 'CreateCells()' crea un GameObject para cada celda y el método 'UpdateCells()' actualiza el color de cada GameObject según el estado de la cuadrícula.

Nota: Este es sólo un ejemplo básico y existen muchas variaciones y extensiones de autómatas celulares que se pueden explorar. Las reglas, los comportamientos de las celdas y las configuraciones de la cuadrícula se pueden modificar para crear diferentes simulaciones y generar varios patrones y comportamientos.

Diagramas de Voronoi

Los diagramas de Voronoi, también conocidos como teselaciones de Voronoi o particiones de Voronoi, son estructuras geométricas que dividen un espacio en regiones según la proximidad a un conjunto de puntos llamados semillas o sitios. Cada región en un diagrama de Voronoi consta de todos los puntos en el espacio que están más cerca de una semilla en particular que de cualquier otra semilla.

La teoría básica detrás de los diagramas de Voronoi involucra los siguientes elementos:

  1. Semillas/Sitios: Las semillas o sitios son un conjunto de puntos en el espacio. Estos puntos pueden generarse aleatoriamente o colocarse manualmente. Cada semilla representa un punto central de una región de Voronoi.
  2. Células/regiones de Voronoi: Cada célula o región de Voronoi corresponde a un área del espacio que está más cerca de una semilla en particular que de cualquier otra semilla. Los límites de las regiones están formados por las bisectrices perpendiculares de los segmentos de línea que conectan las semillas vecinas.
  3. Triangulación de Delaunay: Los diagramas de Voronoi están estrechamente relacionados con la triangulación de Delaunay. La triangulación de Delaunay es una triangulación de los puntos semillas de modo que ninguna semilla esté dentro del círculo circunstante de ningún triángulo. La triangulación de Delaunay se puede utilizar para construir diagramas de Voronoi y viceversa.

Los diagramas de Voronoi tienen varias aplicaciones en el mundo real, entre ellas:

  1. Generación de contenido procedimental: Los diagramas de Voronoi se pueden utilizar para generar terreno procedimental, paisajes naturales y formas orgánicas. Al utilizar las semillas como puntos de control y asignar atributos (como elevación o tipo de bioma) a las células de Voronoi, se pueden crear entornos realistas y variados.
  2. Diseño de juegos: Los diagramas de Voronoi se pueden utilizar en el diseño de juegos para dividir el espacio con fines de juego. Por ejemplo, en los juegos de estrategia, los diagramas de Voronoi se pueden utilizar para dividir el mapa del juego en territorios o zonas controladas por diferentes facciones.
  3. Búsqueda de rutas e IA: los diagramas de Voronoi pueden ayudar en la búsqueda de rutas y la navegación con IA al proporcionar una representación del espacio que permite un cálculo eficiente de la semilla o región más cercana. Se pueden utilizar para definir mallas de navegación o mapas de influencia para agentes de IA.

En Unity, hay varias formas de generar y utilizar diagramas de Voronoi:

  1. Generación de procedimientos: los desarrolladores pueden implementar algoritmos para generar diagramas de Voronoi a partir de un conjunto de puntos semilla en Unity. Se pueden utilizar varios algoritmos, como el algoritmo de Fortune o el algoritmo de relajación de Lloyd, para construir diagramas de Voronoi.
  2. Generación de terreno: Los diagramas de Voronoi se pueden utilizar en la generación de terreno para crear paisajes diversos y realistas. Cada celda de Voronoi puede representar una característica del terreno diferente, como montañas, valles o llanuras. Se pueden asignar atributos como elevación, humedad o vegetación a cada celda, lo que da como resultado un terreno variado y visualmente atractivo.
  3. Partición de mapas: Se pueden emplear diagramas de Voronoi para dividir los mapas del juego en regiones con fines de juego. Es posible asignar diferentes atributos o propiedades a cada región para crear zonas de juego distintas. Esto puede resultar útil para juegos de estrategia, mecánicas de control territorial o diseño de niveles.

Hay paquetes y activos Unity disponibles que proporcionan la funcionalidad de diagrama de Voronoi, lo que facilita la incorporación de funciones basadas en Voronoi en proyectos Unity. Estos paquetes suelen incluir algoritmos de generación de diagramas de Voronoi, herramientas de visualización e integración con el sistema de renderizado Unity.

Aquí hay un ejemplo de cómo generar un diagrama de Voronoi 2D en Unity usando el algoritmo de 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;
            }
        }
    }
}
  • Para usar este código, cree una esfera prefabricada y asígnela al campo seedPrefab en el inspector Unity. Ajuste las variables numSeeds y diagramaSize para controlar el número de semillas y el tamaño del diagrama.

Diagrama de Voronoi en Unity.

En este ejemplo, el script VoronoiDiagram genera un diagrama de Voronoi colocando aleatoriamente puntos semilla dentro del tamaño de diagrama especificado. El método 'GenerateVoronoiDiagram()' calcula las celdas de Voronoi en función de los puntos iniciales, y el método 'VisualizeVoronoiDiagram()' crea una instancia de un GameObject de esfera en cada punto de las celdas de Voronoi, visualizando el diagrama.

Nota: Este ejemplo proporciona una visualización básica del diagrama de Voronoi, pero es posible ampliarlo aún más agregando características adicionales, como conectar los puntos de las celdas con líneas o asignar diferentes atributos a cada celda para generar terreno o con fines de juego.

En general, los diagramas de Voronoi ofrecen una herramienta versátil y poderosa para generar contenido procedimental, dividir espacio y crear entornos interesantes y variados en Unity.

Colocación de objetos procesales

La colocación procedimental de objetos en Unity implica generar y colocar objetos en una escena algorítmicamente, en lugar de posicionarlos manualmente. Es una técnica poderosa que se utiliza para diversos fines, como poblar entornos con árboles, rocas, edificios u otros objetos de forma natural y dinámica.

Aquí hay un ejemplo de colocación de objetos procesales en 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;
    }
}
  • Para usar este script, cree un GameObject vacío en la escena Unity y adjunte el script "ObjectPlacement". Asigne el objeto prefabricado y ajuste los parámetros 'numObjects' y 'spawnArea' en el inspector para que se ajusten a los requisitos. Al ejecutar la escena, los objetos se colocarán de forma procesal dentro del área de generación definida.

Colocación de objetos procesales en Unity.

En este ejemplo, el script 'ObjectPlacement' es responsable de colocar objetos en la escena de forma procesal. Al campo 'objectPrefab' se le debe asignar la casa prefabricada del objeto a colocar. La variable 'numObjects' determina el número de objetos que se colocarán y la variable 'spawnArea' define el área en la que se colocarán los objetos aleatoriamente.

El método 'PlaceObjects()' recorre el número deseado de objetos y genera posiciones de generación aleatorias dentro del área de generación definida. Luego crea una instancia del objeto prefabricado en cada posición aleatoria con una rotación aleatoria.

Nota: Es posible mejorar aún más este código incorporando varios algoritmos de ubicación, como ubicación basada en cuadrícula, ubicación basada en densidad o ubicación basada en reglas, según los requisitos específicos del proyecto.

Conclusión

Las técnicas de generación de procedimientos en Unity proporcionan herramientas poderosas para crear experiencias dinámicas e inmersivas. Ya sea generando terrenos usando ruido Perlin o algoritmos fractales, creando diversos entornos con diagramas de Voronoi, simulando comportamientos complejos con autómatas celulares o poblando escenas con objetos colocados procedimentalmente, estas técnicas ofrecen flexibilidad, eficiencia e infinitas posibilidades para la generación de contenido. Al aprovechar estos algoritmos e integrarlos en proyectos Unity, los desarrolladores pueden lograr una generación de terreno realista, simulaciones realistas, entornos visualmente atractivos y mecánicas de juego atractivas. La generación de procedimientos no sólo ahorra tiempo y esfuerzo, sino que también permite la creación de experiencias únicas y en constante cambio que cautivan a los jugadores y dan vida a mundos virtuales.

Artículos sugeridos
Importancia de la narración en el desarrollo de juegos de Unity
Cómo pintar árboles en el terreno en Unity
Cómo hacer un juego inspirado en FNAF en Unity
Comparación de entornos de desarrollo 2D y 3D en Unity
Dominar el componente de transformación de Unity
API de secuencias de comandos de Unity y Unity Pro
Unidad trabajando con prefabricados