October 15, 2023

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

Обычно в разработке мобильных проектов я всегда обходился без Unit-тестирования и думал, что можно делать игры с минимальным кол-вом багов, если соблюдать принципы SOLID и шаблоны GRASP. Типа: "Зачем писать дополнительные тесты и тратить на это время? Лучше сделаю следующую фичу. Потом же все равно проект будет развиваться и это все нужно будет перепиливать вместе с тестами. Плюс механики в мобильных играх итак достаточно примитивные — что там тестировать?".

В общем Test-Driven Development для меня была просто теория, которую хорошо было бы знать и использовать в крайних ситуациях. И вот эта ситуация настала...

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

Оказалось, что фундамент этих механик строится на различных математических и логических алгоритмах, которые комбинируются вместе игровой логикой. Особенно сложной задачей оказалась реализация алгоритма поиска пути, который обеспечивает оптимальный поиск пути по клеточной сетке. Это был ад — AStar в квадрате!!!

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

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

Так я и сделал. Определил список задач и начал реализовывать алгоритмы шаг за шагом. В результате у меня получилась Ground система, которая представляет собой клеточное поле и класс GroundPathFinder, который ищет поиск пути (Рис. 1).

Рис.1.

Для тестирования алгоритмов я делал различные карты-заглушки (Рис. 2 и 3).

Рис. 2.
Рис. 3.

После успешных тестов я сделал демо-проект, в котором протестировал работоспособность системы на 80 юнитах, сделал оптимизацию и снова запустил Unit-тесты (Рис. 4).

Рис. 4.

Все, модуль можно интегрировать в полноценный проект (Рис. 5).

Рис. 5.

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