Не моя RTS :)
Не моя RTS :)

Привет, Хабр! Меня зовут Игорь, и я Unity Developer. В этой статье хотел бы поделиться кейсом, как Test Driven Development помогает мне разрабатывать RTS игру.

Обычно в разработке мобильных проектов я всегда обходился без Unit-тестирования и думал, что написание тестов — это пустая трата времени и сил. Ну, типа, зачем? "Механики в мобильных играх итак примитивные — что там тестировать? Лучше пойду-сделаю следующую фичу. Итак сроки горят!.."

Поэтому на протяжении последних трех лет мой подход к разработке игр был довольно прост: "Соблюдай принципы SOLID-KISS, и все будет ОК!" В общем так и было, пока не начал делать крупный проект...

Когда я начал делать механики для стратегии, то внезапно осознал, какой огромный объем работ необходимо проделать, чтобы просто реализовать механики перемещения юнитов по карте. Оказалось, что фундамент этих механик строится на различных математических и логических алгоритмах, которые комбинируются вместе игровой логикой. Таким образом, чтобы написать хороший алгоритм поиска пути, нужно как минимум написать алгоритм A* и алгоритм линейной трассировки, а те будут базироваться на реализации клеточного поля, по которому будут ходить юниты. Дополнительно к этому нужно будет сделать оптимизацию.

В общем, я понимал, что с нуля не смогу написать такое решение, поэтому его нужно будет декомпозировать на более простые задачи и решать их шаг за шагом, проверяя работоспособность каждой. Проверять работоспособность, запуская PlayMode в Unity каждый раз, оказалось не очень удобно, и тут вспомнил, что есть TDD, который меня спасет!

Для тех, кто не знает, Test-Driven Development (TDD) — это методология разработки программного обеспечения, которая подразумевает создание тестов для кода до написания самого кода. Процесс TDD — это повторяющийся цикл, который включает в себя следующие этапы: Написание теста -> Написание кода-> Рефакторинг кода.

  1. Сначала разработчик пишет тест, который определяет ожидаемое поведение функции или модуля, который он только собирается написать. Этот тест обычно начинается с того, что показывает, как код будет использоваться с точки зрения другой части системы.

  2. Затем разработчик запускает этот тест, и он должен провалиться, так как соответствующего кода еще нет.

  3. Потом разработчик пишет минимальное количество кода, необходимого для прохождения этого теста. Цель — сделать тест успешным, чтобы код выполнил ожидаемую функцию.

  4. После написания кода разработчик запускает тест еще раз. Теперь тест должен успешно пройти, подтверждая, что код работает правильно.

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

Этот процесс циклически повторяется для каждой новой функции или модуля, добавляемого в проект.

В результате так и был реализован алгоритм. Определил список задач и начал их решать друг за другом по TDD:

  • Задача №1. Реализация клеточного поля. Под капотом оно представляет собой матрицу bool[x,z], где значение каждой ячейки хранит проходимость этой клетки. Каждая клетка имеет размер 1x1, таким образом, индекс клетки указывает ее положение в пространстве на осях XZ.

  • Задача №2. Реализация алгоритмов для работы с клеточным полем: проверка на проходимость клетки, получение соседних клеток и угловых точек для построения графа пути и т.д..

  • Задача №3. Реализация алгоритма A* для поиска пути. Тут я хотел убедиться, что идея с полем-матрицей работает и юниты правильно перемещаются по клеточкам.

  • Задача №4. Реализация алгоритма линейной трассировки по клеточному полю. Это нужно для того, чтобы юниты не ходили по клеткам, в тех ситуациях, когда можно пройти напрямую.

  • Задача №5. Реализация финального алгоритма поиска пути, который базируется на клеточном поле, комбинирует A* и линейную трассировку.

После реализации каждого метода писал несколько тестов, которые проверяют его работоспособность. Таким образом, маленькими итерациями получилось решение из двух классов: GroundArea (клеточное поле) и GroundPathFinder (алгоритм поиска пути):

Тесты для класса GroundArea
Тесты для класса GroundArea
Тесты для класса GroundPathFinder
Тесты для класса GroundPathFinder

Чтобы проверять работоспособность различных методов, нужны были тестовые GroundArea. Поэтому я сделал отдельный класс GroundAreaSubstitutions, в котором разметил различные вариации клеточных полей:

 Заглушки клеточных полей (Substitutions)
Заглушки клеточных полей (Substitutions)

Сами тесты для поиска пути выглядели так:

Тесты для поиска пути
Тесты для поиска пути

После успешных тестов создал новый Unity проект, в котором протестировал работоспособность системы на 80 юнитах. Увидев спайки в окне профайлера, сделал оптимизацию.

Тестовый проект для проверки системы поиска путей
Тестовый проект для проверки системы поиска путей

При оптимизации алгоритма я допустил несколько ошибок, которые сразу увидел на тестах:

Упавшие тесты алгоритма поиска пути
Упавшие тесты алгоритма поиска пути

Устранив все ошибки, у меня получилась отдельная сборка GroundSystem, которую можно использовать в Unity проекте вместе с модулем Mathematics:

Файлы модуля Ground System и зависимости
Файлы модуля Ground System и зависимости

Выводы

Таким образом, понял, что TDD крайне необходим для разработки сложных систем и алгоритмов. Вот какие плюсы я ощутил:

  1. Уверенность в работоспособности программы. Благодаря тестам можно быстро выявить ошибку и устранить ее. Это особенно полезно, когда алгоритмы тесно связанны друг с другом и при изменении кода в одном, могут возникнуть ошибки в другом.

  2. Написание кода по TDD делает систему простой и понятной. В результате разработки системы поиска пути у меня получилось всего 3 класса, которые видно выше на скрине.

  3. Модульность фичи за счет Assembly Definition. Такую систему можно легко переиспользовать в других проектах. При этом, когда скрипты находятся в отдельной сборке, это очень сильно дисциплинирует использовать только необходимые зависимости.

  4. Ощущение прогресса и обратная связь в разработке. Этот психологический момент, который позволяет чувствовать себя молодцом:)

На этом у меня все, спасибо за внимание :)

Прокачать навыки геймдев-разработчика, и в частности, изучить на практике методологию TDD, можно на курсах в Отус.

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


  1. marks
    18.10.2023 16:23
    +1

    О, еще два-три поста-заметки от OTUS на Хабре за сегодня и норма выполнена!


    1. HexGrimm
      18.10.2023 16:23
      +13

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


  1. SuperKozel
    18.10.2023 16:23
    +1

    Имхо, тдд плох тем, что забирает у разработчика способность прогнозировать, из-за чего разработчик, следующий полностью тдд тратит время на пустые шаги, увеличивая время до получения результата. 10 раз нужно удариться в стенку не смотря на то, что ты знаешь что там стена. Какая-то дебилизация что ли процесса разработки.


    1. StarKRE Автор
      18.10.2023 16:23
      +1

      Честно, не понял про способность прогнознозировать


    1. kota1921
      18.10.2023 16:23
      +1

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


    1. HexGrimm
      18.10.2023 16:23
      +1

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


  1. Qvxb
    18.10.2023 16:23

    Привет, Хабр! Меня зовут Игорь, и я Unity Developer. В этой статье хотел бы поделиться кейсом, как Test Driven Development помогает мне разрабатывать RTS игру.

    Что за РТС игра? Реально хотите сделать РТС потому что зов души?) Или так просто, делаете учебный проект чтобы опыта поднабрать?


    1. StarKRE Автор
      18.10.2023 16:23
      +3

      Зов души, мечта детства)


      1. Qvxb
        18.10.2023 16:23
        +1

        Вот я например, если б делал РТС то хотел бы чтобы в ней были какие-то новые механики которых не хватало в других РТС, а в вашем проекте есть какие-нибудь новые механики, фичи по сравнению с классикой wc3, sc2, C&C?


        1. StarKRE Автор
          18.10.2023 16:23
          +1

          Да, я оч хочу добавить механику командиров и механику различных активностей на карте)


      1. Pheasant25
        18.10.2023 16:23

        И когда и где выйдет, если не секрет?


        1. StarKRE Автор
          18.10.2023 16:23
          +1

          Веду разработку на канале: https://www.youtube.com@CodeCraftUnityEdition


  1. GrigorGri
    18.10.2023 16:23
    +7

    Звучит как просто "разработка с использованием тестов", а не TDD.

    После реализации каждого метода писал несколько тестов, которые проверяют его работоспособность.

    По TDD (как в статье написано) сперва пишется тест, потом реализация.


    1. StarKRE Автор
      18.10.2023 16:23

      Если говорить честно: и да, и нет. Для более сложных задач сначала писались тесты, а потом код, для более простых задач — сначала код, потом тесты)


  1. Ichimitsu
    18.10.2023 16:23

    Начал читать, думал что это про Техникал Дизайн Документ, думаю нифига, кто-то их еще пишет и делает по ним код, но статья оказалось про другое ))) надо бы в заголовке указать, чтобы не путалось. Палец вверх за ремарку про SOLID/KISS. В остальном статья полезна. В одной игре можно применять разные подходы, надо просто уметь варить из них кашу. Положил себе в копилку еще один.