Процедурне генерування світу в єдності

Генерація світу в Unity стосується процесу створення або процедурної генерації віртуальних світів, ландшафтів, ландшафтів або середовищ у ігровому движку Unity. Ця техніка зазвичай використовується в різних типах ігор, таких як ігри з відкритим світом, рольові ігри, симуляції тощо, щоб динамічно створювати величезні та різноманітні ігрові світи.

Unity надає гнучку структуру та широкий спектр інструментів і API для впровадження цих методів створення світу. Можна писати власні сценарії за допомогою C# для генерування та керування ігровим світом або використовувати вбудовані функції Unity, такі як система Terrain, шумові функції та інтерфейси сценаріїв, щоб досягти бажаних результатів. Крім того, на Unity Asset Store доступні сторонні активи та плагіни, які можуть допомогти у створенні світу.

У Unity є кілька підходів до генерації світу, і вибір залежить від конкретних вимог гри. Ось кілька поширених методів:

  • Процедурна генерація ландшафту з шумом Perlin
  • Стільникові автомати
  • Діаграми Вороного
  • Процедурне розміщення об’єктів

Процедурна генерація ландшафту з шумом Perlin

Процедурна генерація рельєфу в Unity може бути досягнута за допомогою різних алгоритмів і методів. Одним із популярних підходів є використання шуму Перліна для створення карти висот, а потім застосування різних технік текстурування та листя для створення реалістичного або стилізованого рельєфу.

Шум Перліна — це тип градієнтного шуму, розроблений Кеном Перліном. Він генерує гладку безперервну модель значень, які виглядають випадковими, але мають узгоджену структуру. Шум Перліна широко використовується для створення природних ландшафтів, хмар, текстур та інших органічних форм.

У Unity можна використовувати функцію 'Mathf.PerlinNoise()' для генерування шуму Перліна. Він приймає дві координати як вхідні дані та повертає значення від 0 до 1. Відбираючи шум Перліна на різних частотах і амплітудах, можна створити різні рівні деталізації та складності процедурного вмісту.

Ось приклад того, як реалізувати це в Unity:

  • У редакторі Unity перейдіть до "GameObject -> 3D Object -> Terrain". Це створить місцевість за замовчуванням у сцені.
  • Створіть новий сценарій C# під назвою "TerrainGenerator" і прикріпіть його до об’єкта місцевості. Ось приклад сценарію, який генерує процедурну місцевість за допомогою шуму Перліна:
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;
    }
}
  • Приєднайте сценарій "TerrainGenerator" до об’єкта Terrain у редакторі Unity.
  • У вікні інспектора для об’єкта рельєфу налаштуйте ширину, висоту, масштаб, зміщення та інтенсивність шуму, щоб налаштувати вигляд створеного рельєфу.
  • Натисніть кнопку Відтворити в редакторі Unity, і процедурний рельєф має бути згенерований на основі шумового алгоритму Перліна.

Генерація Unity Terrain з шумом Перліна.

Примітка. Цей сценарій створює базову карту висот рельєфу за допомогою шуму Перліна. Щоб створити більш складний рельєф, змініть сценарій, щоб включити додаткові шумові алгоритми, застосувати методи ерозії або згладжування, додати текстуру або розмістити листя та об’єкти на основі особливостей рельєфу.

Стільникові автомати

Клітинний автомат — це обчислювальна модель, яка складається з сітки клітинок, де кожна клітинка розвивається на основі набору попередньо визначених правил і станів сусідніх клітинок. Це потужна концепція, яка використовується в різних областях, включаючи інформатику, математику та фізику. Клітинні автомати можуть демонструвати складні шаблони поведінки, що випливають із простих правил, що робить їх корисними для моделювання природних явищ і створення процедурного вмісту.

Основна теорія клітинних автоматів включає такі елементи:

  1. Сітка: сітка — це набір комірок, розташованих за регулярним візерунком, наприклад квадратна або шестикутна решітка. Кожна клітина може мати кінцеву кількість станів.
  2. Сусіди: кожна комірка має сусідні комірки, які зазвичай є її безпосередніми сусідами. Околиці можна визначити на основі різних моделей зв’язності, таких як околиці фон Неймана (вгору, вниз, ліворуч, праворуч) або Мура (включаючи діагональ).
  3. Правила: поведінка кожної клітини визначається набором правил, які визначають, як вона розвивається на основі її поточного стану та станів сусідніх клітин. Ці правила зазвичай визначаються за допомогою умовних операторів або таблиць пошуку.
  4. Оновлення: клітинний автомат розвивається, оновлюючи стан кожної клітини одночасно відповідно до правил. Цей процес повторюється ітеративно, створюючи послідовність поколінь.

Стільникові автомати мають різні реальні застосування, зокрема:

  1. Моделювання природних явищ: клітинні автомати можуть моделювати поведінку фізичних систем, таких як динаміка рідини, лісові пожежі, транспортні потоки та динаміка населення. Визначаючи відповідні правила, клітинні автомати можуть фіксувати виникаючі закономірності та динаміку, що спостерігаються в системах реального світу.
  2. Генерація процедурного вмісту: Стільникові автомати можна використовувати для створення процедурного вмісту в іграх і симуляціях. Наприклад, їх можна використовувати для створення рельєфу, систем печер, розподілу рослинності та інших органічних структур. Складні та реалістичні середовища можна створити, вказавши правила, які керують ростом і взаємодією клітин.

Ось простий приклад реалізації базового клітинного автомата в Unity для симуляції гри життя:

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;
    }
}
  • Приєднайте сценарій "CellularAutomaton" до об’єкта GameObject у сцені Unity та призначте префаб комірки до поля 'cellPrefab' в інспекторі.

Стільниковий автомат в Unity.

У цьому прикладі сітка комірок представлена ​​логічним масивом, де 'true' вказує на живу комірку, а 'false' — мертву комірку. Для оновлення сітки застосовуються правила гри життя, а візуальне представлення комірок відповідно оновлюється. Метод 'CreateCells()' створює GameObject для кожної клітинки, а метод 'UpdateCells()' оновлює колір кожного GameObject на основі стану сітки.

Примітка. Це лише базовий приклад, і існує багато варіацій і розширень клітинних автоматів, які можна вивчити. Правила, поведінку комірок і конфігурації сітки можна змінювати для створення різних моделювань і генерування різних шаблонів і поведінки.

Діаграми Вороного

Діаграми Вороного, також відомі як мозаїка Вороного або розділи Вороного, — це геометричні структури, які ділять простір на області на основі близькості до набору точок, які називаються початковими елементами або вузлами. Кожна область на діаграмі Вороного складається з усіх точок у просторі, які знаходяться ближче до конкретного насіння, ніж до будь-якого іншого насіння.

Основна теорія діаграм Вороного містить такі елементи:

  1. Насіння/сайти: Насіння або сайти — це набір точок у просторі. Ці точки можна генерувати випадковим чином або розміщувати вручну. Кожне насіння представляє центральну точку для регіону Вороного.
  2. Клітини/регіони Вороного: кожна клітина або регіон Вороного відповідає ділянці простору, яка знаходиться ближче до певного насіння, ніж до будь-якого іншого насіння. Межі областей утворюють перпендикулярні бісектриси відрізків, що з'єднують сусідні насіння.
  3. Тріангуляція Делоне: діаграми Вороного тісно пов’язані з тріангуляцією Делоне. Тріангуляція Делоне — це тріангуляція початкових точок так, що жодна початкова частина не знаходиться всередині описаного кола будь-якого трикутника. Триангуляцію Делоне можна використовувати для побудови діаграм Вороного, і навпаки.

Діаграми Вороного мають різні реальні застосування, зокрема:

  1. Процедурне генерування вмісту: діаграми Вороного можна використовувати для створення процедурного ландшафту, природних ландшафтів і органічних форм. Використовуючи насіння як контрольні точки та призначаючи атрибути (наприклад, висоту або тип біома) клітинам Вороного, можна створити реалістичне та різноманітне середовище.
  2. Ігровий дизайн: діаграми Вороного можна використовувати в ігровому дизайні для розділення простору в ігрових цілях. Наприклад, у стратегічних іграх діаграми Вороного можна використовувати для поділу ігрової карти на території або зони, контрольовані різними фракціями.
  3. Пошук шляху та штучний інтелект: діаграми Вороного можуть допомогти у пошуку шляху та навігації штучним інтелектом, надаючи представлення простору, що дозволяє ефективно обчислювати найближче насіння або регіон. Їх можна використовувати для визначення навігаційних сіток або для впливу на карти для агентів ШІ.

У Unity є кілька способів створення та використання діаграм Вороного:

  1. Процедурна генерація: розробники можуть реалізувати алгоритми для генерації діаграм Вороного з набору вихідних точок у Unity. Для побудови діаграм Вороного можна використовувати різні алгоритми, такі як алгоритм Форчуна або алгоритм релаксації Ллойда.
  2. Створення рельєфу: діаграми Вороного можна використовувати для створення рельєфу для створення різноманітних і реалістичних ландшафтів. Кожна комірка Вороного може відображати різні особливості рельєфу, такі як гори, долини чи рівнини. Атрибути, такі як висота, вологість або рослинність, можна призначити кожній клітинці, що призводить до різноманітної та візуально привабливої ​​місцевості.
  3. Розбиття карти: діаграми Вороного можна використовувати для розділення ігрових карт на регіони для ігрових цілей. Кожному регіону можна призначити різні атрибути або властивості, щоб створити окремі ігрові зони. Це може бути корисно для стратегічних ігор, механізмів територіального контролю або дизайну рівнів.

Доступні пакунки та ресурси Unity, які забезпечують функціональність діаграми Вороного, що полегшує включення функцій на основі Вороного в проекти Unity. Ці пакети часто включають алгоритми створення діаграми Вороного, засоби візуалізації та інтеграцію з системою візуалізації Unity.

Ось приклад створення двовимірної діаграми Вороного в Unity за допомогою алгоритму 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;
            }
        }
    }
}
  • Щоб використовувати цей код, створіть префаб сфери та призначте його до поля seedPrefab в інспекторі Unity. Налаштуйте змінні numSeeds і diagramSize, щоб керувати кількістю насінин і розміром діаграми.

Діаграма Вороного в Unity.

У цьому прикладі сценарій VoronoiDiagram генерує діаграму Вороного шляхом випадкового розміщення початкових точок у межах указаного розміру діаграми. Метод 'GenerateVoronoiDiagram()' обчислює клітинки Вороного на основі вихідних точок, а метод 'VisualizeVoronoiDiagram()' створює екземпляр ігрового об’єкта сфери в кожній точці клітинок Вороного, візуалізуючи діаграму.

Примітка. У цьому прикладі представлена ​​базова візуалізація діаграми Вороного, але її можна розширити, додавши додаткові функції, наприклад з’єднавши точки клітинок лініями або призначивши різні атрибути кожній клітинці для створення рельєфу чи ігрових цілей.

Загалом, діаграми Вороного пропонують універсальний і потужний інструмент для створення процедурного вмісту, розподілу простору та створення цікавого та різноманітного середовища в Unity.

Процедурне розміщення об’єктів

Процедурне розміщення об’єктів у Unity включає алгоритмічне створення та розміщення об’єктів у сцені, а не їх позиціонування вручну. Це потужна техніка, яка використовується для різних цілей, як-от заповнення середовища деревами, скелями, будівлями чи іншими об’єктами природним і динамічним способом.

Ось приклад розміщення процедурного об’єкта в 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;
    }
}
  • Щоб використовувати цей сценарій, створіть порожній GameObject у сцені Unity і прикріпіть сценарій "ObjectPlacement" до нього. Призначте префаб об’єкта та налаштуйте параметри 'numObjects' і 'spawnArea' в інспекторі відповідно до вимог. Під час запуску сцени об’єкти будуть розміщуватися процедурно в межах визначеної зони появи.

Розміщення процедурного об’єкта в Unity.

У цьому прикладі сценарій 'ObjectPlacement' відповідає за процедурне розміщення об’єктів у сцені. Полю 'objectPrefab' слід призначити префаб об’єкта для розміщення. Змінна 'numObjects' визначає кількість об’єктів, які потрібно розмістити, а змінна 'spawnArea' визначає область, у якій об’єкти будуть розташовані випадковим чином.

Метод 'PlaceObjects()' перебирає потрібну кількість об’єктів і генерує випадкові позиції появи в межах визначеної області появи. Потім він створює префаб об’єкта в кожній випадковій позиції з випадковим обертанням.

Примітка: можна додатково вдосконалити цей код, включивши різні алгоритми розміщення, наприклад розміщення на основі сітки, розміщення на основі щільності або розміщення на основі правил, залежно від конкретних вимог проекту.

Висновок

Методи процедурної генерації в Unity надають потужні інструменти для створення динамічних і захоплюючих вражень. Незалежно від того, чи йдеться про створення рельєфу за допомогою шуму Перліна чи фрактальних алгоритмів, створення різноманітних середовищ за допомогою діаграм Вороного, моделювання складної поведінки за допомогою клітинних автоматів або заповнення сцен процедурно розміщеними об’єктами, ці методи пропонують гнучкість, ефективність і безмежні можливості для генерації вмісту. Використовуючи ці алгоритми та інтегруючи їх у проекти Unity, розробники можуть досягти реалістичного створення ландшафту, реалістичних симуляцій, візуально привабливого середовища та захоплюючої механіки ігрового процесу. Процедурна генерація не тільки економить час і зусилля, але й дозволяє створювати унікальні та постійно мінливі враження, які захоплюють гравців і оживляють віртуальні світи.

Рекомендовані статті
Важливість сторітелінгу в розробці ігор Unity
Unity Scripting API та Unity Pro
Twitter Поради щодо єдності
Як малювати дерева на місцевості в Unity
Як імпортувати анімацію в Unity
Вибір відповідного Skybox для вашого середовища в Unity
Стратегії захисту ігор Unity від піратства