← Назад к вопросам

Как реализовать процедурную генерацию уровней в Unity?

1.8 Middle🔥 182 комментариев
#C# и ООП

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Процедурная генерация уровней в Unity

Процедурная генерация (ПГ) — это алгоритмическое создание игрового контента, а не его ручное проектирование. В Unity существует несколько подходов, которые я применяю в зависимости от типа игры и требуемой сложности.

Основные подходы

1. Клеточные автоматы для пещер и подземелий

Идеально подходит для генерации пещероподобных структур. Алгоритм работает на сетке, где каждая ячейка — это стена или пол.

public class CaveGenerator : MonoBehaviour
{
    public int width = 50;
    public int height = 50;
    public string seed;
    public bool useRandomSeed = true;
    [Range(0,100)] public int randomFillPercent = 45;
    
    private int[,] map;
    
    void Start() {
        GenerateMap();
    }
    
    void GenerateMap() {
        map = new int[width, height];
        RandomFillMap();
        
        for (int i = 0; i < 5; i++) {
            SmoothMap();
        }
        
        // Визуализация результата
        DrawMap();
    }
    
    void RandomFillMap() {
        if (useRandomSeed) seed = Time.time.ToString();
        System.Random rng = new System.Random(seed.GetHashCode());
        
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                // Края карты всегда стены
                if (x == 0 || x == width-1 || y == 0 || y == height-1) {
                    map[x, y] = 1;
                } else {
                    map[x, y] = (rng.Next(0,100) < randomFillPercent) ? 1 : 0;
                }
            }
        }
    }
    
    void SmoothMap() {
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                int neighbourWallTiles = GetSurroundingWallCount(x, y);
                
                if (neighbourWallTiles > 4) map[x,y] = 1;
                else if (neighbourWallTiles < 4) map[x,y] = 0;
            }
        }
    }
}

2. Генерация на основе комнат

Часто используется для roguelike-игр. Алгоритм состоит из нескольких этапов:

  • Создание комнат со случайными размерами и позициями
  • Разделение пространства (Binary Space Partitioning)
  • Соединение комнат коридорами с помощью алгоритмов A* или минимального остовного дерева
  • Размещение объектов (враги, предметы, выходы)

3. Шум Перлина для ландшафтов

Для создания органичных ландшафтов и heightmaps:

public float[,] GenerateNoiseMap(int mapWidth, int mapHeight, float scale) {
    float[,] noiseMap = new float[mapWidth, mapHeight];
    
    if (scale <= 0) scale = 0.0001f;
    
    for (int y = 0; y < mapHeight; y++) {
        for (int x = 0; x < mapWidth; x++) {
            float sampleX = x / scale;
            float sampleY = y / scale;
            float perlinValue = Mathf.PerlinNoise(sampleX, sampleY);
            noiseMap[x, y] = perlinValue;
        }
    }
    
    return noiseMap;
}

Ключевые принципы реализации

Псевдослучайность — использование сидов для воспроизводимости:

Random.InitState(seed.GetHashCode());

Слои абстракции:

  1. Генерация макрокосма (общая структура уровня)
  2. Генерация мезокосма (комнаты, коридоры)
  3. Генерация микрокосма (детали, объекты)

Оптимизация:

  • Использование Object Pooling для повторного использования объектов
  • Отложенная генерация частей уровня по мере продвижения игрока
  • Многопоточность через C# Job System или Task.Run() для тяжелых вычислений

Расширенные техники

  • Wave Function Collapse — алгоритм, который учитывает локальные паттерны
  • Грамматики форм (Shape Grammars) для архитектурных стилей
  • Генеративные состязательные сети (GAN) через Barracuda для ML-подхода

Интеграция с Unity

Для production-проектов я обычно создаю систему из следующих компонентов:

[CreateAssetMenu(fileName = "LevelGenerationSettings", menuName = "Procedural/Level Settings")]
public class LevelGenerationSettings : ScriptableObject
{
    public int seed;
    public Vector2Int levelSize;
    public RoomSettings roomSettings;
    public CorridorSettings corridorSettings;
    public SpawnableObject[] spawnableObjects;
    
    [System.Serializable]
    public class SpawnableObject {
        public GameObject prefab;
        [Range(0f, 1f)] public float spawnChance;
        public int minCount;
        public int maxCount;
    }
}

Практические советы

  • Начинайте с простого прототипа и постепенно добавляйте сложность
  • Создайте инструменты отладки — визуализацию seed, перегенерацию в runtime
  • Разделяйте данные и логику — используйте ScriptableObjects для настроек
  • Тестируйте экстремальные значения — очень маленькие/большие уровни
  • Кэшируйте результаты генерации, если они не меняются во время игры

Важнейший аспект — сохранять баланс между случайностью и игровым балансом. Слишком случайные уровни могут быть несправедливыми, а слишком предсказуемые — скучными. Я обычно добавляю правила валидации, которые проверяют сгенерированный уровень на проходимость и баланс, и при необходимости перегенерируют проблемные участки.