Как TDD помогает мне делать RTS

Не моя 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], где значение каждой ячейки хранит проходимость этой клетки. Каждая клетка имеет размер 1×1, таким образом, индекс клетки указывает ее положение в пространстве на осях 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, можно на курсах в Отус.

© Habrahabr.ru