Процедурна генерація рівнів для M.E.R.C. в Unity

Процедурна генерація рівнів для M.E.R.C. в Unity

Частина перша

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

Що таке M.E.R.C.? M.E.R.C. - це тактичний симулятор загону в реальному часі з видом зверху. Гравець одночасно управляє загоном з чотирьох найманців в антиутопічному світі Неотопії, віддає накази і активує особливі вміння. Кожен найманець загону має власні особливі бойові, технічні та хакерські навички, які необхідно використовувати в місіях. Візуально M.E.R.C. нагадує стиль «Того, хто біжить по лезу»: темні дощові нетрі та дахи міста з безліччю звивистих вулиць і неоновим освітленням. Сюжет полягає у війні могутніх корпорацій за контроль над Неотопією. Загін наймають для виконання різних завдань корпорацій, таких як викрадення вчених конкурентів або вбивство співробітників-перебіжчиків. Кожна отримана місія впливає на відносини з різними корпораціями і в результаті змінює ігровий світ. Враховуючи все це, давайте розглянемо вимоги до процедурної генерації рівнів.

Вимоги до рівнів

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

  • містити один основний маршрут
  • утримувати 1-3 тупикових шляхи для цілей і збирання видобутку
  • містити 1-3 короткі шляхи, що використовуються як альтернативні маршрути
  • генерувати випадкові об'єкти оформлення та укриттів у кожному фрагменті рівня
  • генерувати ворогів на підставі «графіка темпу»
  • генерувати різні типи і рівні ворогів на підставі складності місії
  • працювати з системою NavMesh Unity
  • бути детермінованим і генерувати абсолютно однакові рівні для спільної гри по мережі

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

Найскладнішим було дотриматися всіх умов, створивши при цьому цілісні і цікаві рівні. Як нам вдалося цього досягти? Якщо уявити готовий рівень з його структурою, схованками, темпом і цілями місій, то складно зрозуміти, як створити все це процедурно. Я вирішив, що простіше буде розбити рівень на шари. Треба сприймати процедурний рівень як створюваний у кілька проходів, кожен з яких додає рівню новий шар складності. Логічно, що можна почати процес з базового рівня. У нашому випадку це структура (маршрут) рівня.

Створення маршруту рівня

Перша проблема системи процедурних рівнів полягає в генеруванні цікавого основного маршруту, що містить тупики і короткі шляхи. Ми знайшли рішення в доповіді Зака Ейкмана (Zach Aikman) з Unite 2014 про генерування рівнів гри Galak-Z студією 17-BIT (повну доповідь можна подивитися тут).

Доповідь Зака дуже цікава, і я раджу її подивитися. Розповім про нього коротко: для генерування основного 2D-маршруту ми використовували модифікований алгоритм кривої Гільберта. Він створює цікавий і унікальний звивистий маршрут, що ідеально підходить в якості основи для наших рівнів. Розробники Galak-Z використовували його для маршруту в двомірному вигляді збоку, ми з його допомогою створювали двомірний вид зверху. Уявіть, що на ілюстрації нижче показана схема вулиць нашої гри у вигляді зверху.

(ілюстрація з доповіді про Galak-Z)

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

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

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

Після завершення генерування повного маршруту з короткими шляхами і тупиками ми перетворюємо його на список завантажуваних фрагментів рівнів. Кожен фрагмент рівня - це сцена Unity, тому кожній сцені ми дали назву за шаблоном, що визначає її конфігурацію. Створюючи рівень, ми перетворюємо кожну комірку карти на назву сцени, що відповідає шаблону. Шаблон містить тему фрагмента, з'єднання і позначення варіації. Наприклад, за шаблоном < тема > < з'єднання основного маршруту > _ < з'єднання короткого шляху > _ < з'єднання глухого кута > < варіація > фрагмент рівня може мати назву сцени "slums_03_-1_-1_A".

Тема - це візуальний стиль фрагментів рівнів. Всі фрагменти рівнів однієї теми повинні безшовно з'єднуватися з будь-яким іншим фрагментом такої ж теми. Наприклад, всі фрагменти рівнів теми «slums» (нетрі) повинні логічно і графічно поєднуватися один з одним в точках з'єднань. Ми також створили тему «rooftops» (даху будинків), в якій замість з'єднаних один з одним вулиць даху будівель з'єднані скатами і настилами. Зазвичай всі фрагменти однієї теми мають однаковий розмір. Наші фрагменти в середньому мають розмір 40x40 одиниць.

У відповідності зі структурою кривих Гільберта і згенерованих нами шляхів кожен фрагмент рівня матиме два з'єднання основного маршруту, нуль або одне з'єднання короткого шляху і нуль або одне з'єднання глухого кута. Фрагмент рівня ніколи не містить одночасно з'єднання короткого шляху і глухого кута, тому що вони ніколи не використовуються. Кожне з "єднання відповідає межі фрагмента рівня, і кожна грань позначена цифрою від нуля до трьох, як показано на малюнку нижче (для позначки неіснуючого з" єднання використовується "-1", наприклад, на малюнку немає з "єднання короткого шляху).

Наприклад, фрагмент рівня з «єднання основного маршруту 03 має з» єднання внизу (0) та праворуч (3). Позначка з'єднання основного шляху завжди має порядок збільшення (тобто «03», а не «30»). Важливо пам'ятати, що при збиранні фрагментів рівнів з'єднання необов'язково повинні вибудовуватися абсолютно рівно. Додавши деяким з'єднанням нерівності, ми збільшимо варіативність і зменшимо плавність з'єднання фрагментів. Однак з'єднання все одно повинні бути вирівняні один щодо одного, щоб шляхи обов'язково зістиковувалися.

Варіації дозволяють дизайнеру рівнів створювати різні версії одного фрагмента рівня. Наприклад, подивіться на дві варіації «A» і «B» з'єднання основного маршруту «01» без короткого шляху. При створенні рівня система випадково вибирає одну з варіацій кожного фрагмента рівня, забезпечуючи більшу візуальну різноманітність.

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

Буду радий більш детально обговорити цю тему і із задоволенням прийму будь-які пропозиції. Зі мною можна зв'язатися в Твіттері.

У другій частині статті «Процедурна генерація рівнів для M.E.R.C. в Unity» ми обговоримо вирішені нами проблеми висвітлення і NavMesh, а також процес генерування персонажів у місії на підставі темпу.

Image