Всем доброго времени суток! Я бы хотел вам рассказать историю своей новой игры-головоломки NeoAngle, а также поделиться опытом импортирования, хранения и генерации уровней в Unity.

Начну с краткой предыстории 5-летней давности, когда я решил заняться геймдевом, впервые познакомившись с языком программирования action script 3.0 (для разработки flash-игр) в университете.

Закончив семестр, я решил, что смог бы осилить собственную игру. Так, посидев какое-то время за блокнотом и карадашом, пришла идея головоломки с треугольниками, где игроку нужно путем переворачивания треугольного камня заполнить заданную форму и закончить уровень на финишной клетке.

Таким образом, затратив неделю на разработку в одиночку, была выпущена моя первая flash-игра SeaQuest.



И понесло меня разрабатывать дальше. Выпустив еще несколько портальных флэшек, я вернулся к идее с треугольниками, что привело к новой части, где геймплей был полностью переработан, а именно добавлены дополнительные интерактивные объекты: кнопки с препятствиями, телепорты и вращатели. Цель уровня также изменилась, теперь нужно было собрать все жемчужины и прийти к финишу.


Результат под названием Stone Quest

На этом линейка логических игр завершилась и была успешно мной позабыта.

Далее последовал обещанный обвал флэш индустрии, который перекинул меня в unity разработку для мобильных. И вот, в декабре 2016 года, совершенно случайно мне в голову вернулась мысль о треугольно-ориентированных головоломках. Особенно подтолкнуло к разработке то, что геймплей отлично подходит под тачскрин. Было решено использовать механику предыдущей игры, но с другой стилизацией.

Вот такая предыстория, которая привела к активной полуторамесячной разработке. Большая часть работы, как ни странно, ушла на геймдизайн. Из предыдущей версии почти все уровни мне показались недостаточно интересными, хотя возможности геймплея имеют гораздо больший потенциал. В следствие чего разработка началась с редактора уровней, который я написал за вечер на старом добром флэше, с возможностью тестирования написанных уровней и их экспорта/импорта в xml.

Результат работы в редакторе продемонстрирован ниже:



Прежде чем перейти к работе с Unity, мне необходимо было убедиться в том, что я смогу предоставить достаточное количество уровней для мобильной игры, которая требует разнообразный контент. Поэтому первые полторы недели я даже не открывал Unity, а работал в своем редакторе, параллельно занимаясь графикой. К слову, был выбран набирающий популярность ретро-стиль synthwave 80-ых. Он достаточно прост в исполнении, являясь при этом очень привлекательным.



Таким образом, создав допустимый набор уровней, я приступил к их портированию в Unity, продолжив тратить часть времени на доработку и создание новых.

В связи с этим, возникли следующие вопросы: каким образом импортировать, хранить и генерировать уровни в Unity из xml файла?

Найденных вариантов решения было три:

1. При старте игры вычитывать xml файл и каждый раз динамически создавать уровни на runtime.
2. В edit mode сгенерировать уровни и создать на каждый по сцене.
3. В edit mode сгенерировать уровни и создать для каждого префаб.

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

Для работы с объектами в edit mode добавим кастомную панель в редакторе Unity. Для этого создаем класс наследуемый от EditorWindow в папке Assets/Editor и добавляем туда следующий метод:

[MenuItem ("Window/Level Loader")] // Level Loader - название панели в списке Window. 
public static void ShowWindow () {
    EditorWindow.GetWindow(typeof(LevelLoader)); // LevelLoader - имя созданного класса
}

Сразу же проверим, что панель создана в Window -> Level Loader:



Далее, в методе OnGUI можно начинать добавлять кнопки и поля для нашего окна.

Базовые примеры:

void OnGUI() {
    GUILayout.Label ("Custom Label", EditorStyles.boldLabel); // добавление заголовка

    // создание поля для выбора GameObject
    GameObject customGO = EditorGUILayout.ObjectField(customGO, typeof(GameObject), true) as GameObject; 

    int customInt = EditorGUILayout.IntField(customInt); // создание поля для ввода int значения

    EditorGUILayout.Space(); // добавление вертикального отступа

    if (GUILayout.Button("Custom Button")) // добавление кнопки с обработчиком нажатия
    {
        ButtonHandler();
    }
}

Больше информации по компонентам можно найти в официальной документации. А я продолжу описание моего Level Loader’a, продемонстрировав принцип работы ниже:



Сперва, мы вычитываем уровни из xml файла, путем нажатия на Read Levels, после этого создается выпадающий список с возможностью выбора уровня и появляются дополнительные контролы, которые позволяют сгенерировать уровень на сцене, создать префаб выбранного уровня, пересоздать префабы всех уровней, показать/скрыть номера полей (для тестирования отрисовки и отслеживания ошибок).

Для создания объектов на сцене в edit mode используется стандартная функция Instantiate, а для удаления DestroyImmediate.

Какое-то время я создавал префабы уровней вручную, перетягивая их со сцены в папку Resources. Однако, это быстро мне надоело и я полез в интернет за информацией о том, как создавать префабы в edit mode программными средствами. Ниже конструкция, позволяющая это сделать:

private void CreateLevelPrefab() {
    GameObject levelGO = GameObject.FindGameObjectWithTag("Level").gameObject; 
    // игровой объект Level содержит сгенерированный на сцене уровень

    Object levelPrefab = EditorUtility.CreateEmptyPrefab("Assets/Resources/"+levelGO.name+".prefab");
    EditorUtility.ReplacePrefab(levelGO, levelPrefab, ReplacePrefabOptions.ConnectToPrefab);
}

Далее, в режиме игры уровни добавляются на сцену следующим образом:

GameObject levelGO = Instantiate (Resources.Load ("Level"+levelNum) as GameObject);

Так, этапы добавления уровней получились следующие:

  • Создание/редактирование уровня во flash-редакторе с последующим экспортом в xml
  • Считывание xml уровня в unity
  • Генерация уровня на сцене в edit mode
  • Создание/обновление префаба уровня в папке Resources

На этом всё. Для меня это был первый опыт работы с объектами в режиме редактирования. Буду рад ознакомиться с вашими идеями по поводу реализации редактора уровней прямо в Unity, миновав Flash.

Спасибо за внимание! Поиграть в игру можно в Google Play по запросу NeoAngle.
Поделиться с друзьями
-->

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


  1. stargazr
    27.02.2017 16:40

    Вы в курсе, что ваша игра вообще не находится по запросам retrowave и synthwave?


    А так да, выглядит замечательно, и играется интересно. Удачи в продвижении.
    Реквестирую игры, инспирированные стилями минимал-синт и дарквейв :)


    1. ian_phobos
      27.02.2017 16:44

      Спасибо, буду иметь в виду про поиск по данным запросам!


  1. Sirion
    27.02.2017 23:17

    А для писи не планируете издать? Выглядит весьма привлекательно, но вот не люблю я мобилкогеймерство. ничего не могу с собой, окаянным, поделать.


    1. ian_phobos
      28.02.2017 09:00
      +1

      Пока не планирую, сначала посмотрю как пойдет на мобилках


  1. velik97
    28.02.2017 08:59
    +1

    Недавно слушал лекцию на Unity MeetUp в Москве, как раз по теме сериализации данных в Unity. Так вот, там рассказывали про ScriptableObject (далее SO). SO это что-то вроде Monobehaviour, только без всех callback'ов типа Start и Update и, что самое главное, оно может сохранять изменения, сделанные в игровой сессии после ее завершения, в отличие от Monobehaviour, который сбрасывает все поля после завершения игровой сессии на изначальные. Таким образом при помощи SO можно создать редактор уровней внутри самого Unity без xml.
    Вообще SO был как раз разработан для таких решений, а использование Prefab'ов для хранения данных считают неким костылем.

    P.S. Если будет желание создать редактор уровней для игрока (чтоб он сам смог создавать свои уровни), одним SO не обойдешься, так как состояние SO можно сохранять только в Ediotr'е.


    1. ian_phobos
      28.02.2017 09:01

      Спасибо за информацию, но я не совсем понимаю каким образом хранить уровни не в префабах, используя ScriptableObject?


      1. velik97
        28.02.2017 11:27

        Сам уровень в SO хранить не выйдет, так как SO не имеет Transform, соответсвенно не может иметь «детей». Можно хранить в SO информацию об уровне, например двумерный массив int'ов, а потом генерировать уровень из этого массива. По сути SO выступает здесь вместо xml.
        Остается вопрос, в чем же преимущество SO перед обычным xml? SO использует внутренние, бинарные (если не изменяет память) протоколы сериализации и десериализации, которые очень хорошо оптимизированы.
        Но думается мне, что для таких несложных объектов, как уровни в вашей игре, это не играет большую роль. Поэтому я скорее делюсь еще одним способом, который активно продвигает Unity, а не советую вам что-то поменять.
        Но все же один вопрос у меня остается, зачем вы создаете префабы? Почему бы не хранить в asset'ах просто xml файлы, а потом в рантайме генерировать уровни из них?


        1. ian_phobos
          28.02.2017 11:43

          Идея ясна, спасибо за объяснение.
          А для процессора явно проще на сцену добавить один сгенерированный префаб уровня, чем каждую клетку игрового поля по отдельности. Я только что замерил ради интереса время:
          — на создание уровня из xml уходит в среднем 0.1 сек
          — на добавление префаба уровня на сцену в рантайме 0.05 сек.


  1. Kavabunga
    02.03.2017 09:41

    Отличная статья, спасибо! Насчёт дублирования интерфейса и других объектов если каждый уровень в отдельной сцене — я решил эту проблему скриптом LevelStarter, который хранил нужные для конкретного уровня настройки и заодно создавал интерфейс и проверял наличие нужных синглтонов (мне это пришло в разработке для возможности старта с любой сцены). Но это все было из-за лени создавать редактор.


    1. ian_phobos
      02.03.2017 09:42

      Всегда пожалуйста :)
      Интересное решение, но все равно, насколько я знаю, загрузка сцены более трудоемкая операция, чем смена префаба