Глава V из серии руководств о том, как создать игру с нуля с помощью TypeScript и собственных API-интерфейсов браузера.
Добро пожаловать в последний выпуск этой главы! В прошлой статье мы представили OnclickComponent
, абстрактный класс, который служит мостом между уведомителем (GameInputComponent
) и реальными получателями. Сегодня мы собираемся добавить один такой приемник в нашу счастливую семью и, конечно же, накроем GameInputComponent
юнит-тестами.
В главе V «Система ввода» мы собираемся создать простую систему, которая даст игроку возможность общаться с игрой. Вы можете найти другие главы этой серии здесь:
- "Вступление"
- Глава I. Сущности и компоненты
- Глава II. Игровой цикл (Часть 1, Часть 2)
- Глава III. Сетка для рисования (Часть 1, Часть 2, Часть 3, Часть 4, Часть 5)
- Глава IV. Суда (Часть 1, Часть 2, Часть 3, Часть 4)
- Глава V. Система ввода (Часть 1, Часть 2, Часть 3)
- Глава VI. Поиск пути и движение
- Глава VII. Государственная машина
- Глава VIII. Система атаки: здоровье и урон
- Глава IX. Победа и поражение в игре
- Глава X. Вражеский AI.
Смело переходите в
input-2
ветку репозитория. Он содержит рабочий результат предыдущих постов и является отличной отправной точкой для этого.
Оглавление
- Вступление
- Представляем компонент Grid Onclick
- Сетка тестирования
- Тестирование компонента Onclick Grid
- Тестирование компонента ввода игры
- Внедрение зависимостей в игру
- Заключение
Вступление
Наша цель в этой главе - убедиться, что наша система ввода работает. Мы согласились с тем, что мы делаем это, временно отмечая Node
как активный, когда игрок щелкает по нему. Теперь, когда у нас есть надежная система уведомлений, мы можем ввести что-то вроде NodeOnclickComponent
, которое расширит OnclickComponent
и фактически установит флаг IsActive
. Но мы также можем сделать то же самое на один уровень выше, введя GridOnclickComponent
.
Есть 2 причины, по которым я хочу пойти по этому пути. Во-первых, Grid
имеет доступ ко всем Nodes
одновременно, что дает мне возможность «деактивировать» другие Nodes
, кроме того, по которому в данный момент щелкнули. Во-вторых, наш GameInputComponent
уведомляет только прямых дочерних элементов Game
, которыми в настоящий момент являются только Grid
и два Fleets
. Конечно, мы могли бы проксировать событие на Node
и Ship
, если захотим, но на данный момент это не обязательно. Давайте будем простыми. К счастью, расширение этой функциональности должно быть тривиальной задачей.
Представляем компонент Grid Onclick
Имея это в виду, мы начнем с добавления самого первого компонента для объекта Grid
:
Мы расширяем OnclickComponent
, который просит нас реализовать Awake
и Update
(согласно требованиям IComponent
), но также ClickOn
(согласно требованию OnclickComponent
). Прежде чем идти дальше, давайте удостоверимся, что у нас есть все бочки:
Если вы помните, в самой первой статье этой главы мы добавили метод Occupies
к сущности Node
. Этот метод проверяет, действительно ли указанная точка находится в границах этого Node
. Мы можем снова использовать эту проверку и установить флаг IsActive
для Node
:
А затем прикрепите компонент к Grid
, чтобы он заработал:
Вот и все! Если вы начинаете игру с npm start
и открываете браузер, вы сможете включать и выключать Nodes
, щелкая по ним:
Это было просто. Но мы еще не закончили, нам нужно написать несколько тестов, чтобы закрепить нашу победу.
Сетка тестирования
Это просто и должно быть вам знакомо. К настоящему времени мы провели подобное тестирование уже десяток раз. Я начну с создания макета:
И обновите соответствующий файл ствола:
Теперь мы можем обновить все тесты, которые использовались для непосредственного создания экземпляров Grid
. Сначала Fleet
макет фабрики:
А затем сама спецификация:
Отлично! И мы приземлились в идеальном месте. Давайте улучшим охват тестированием для Grid
, проверив, что все его компоненты пробуждаются и обновляются:
Все это должно показаться знакомым. На этом этапе наш код должен успешно скомпилироваться с npm start
, а все тесты должны пройти с npm t
:
Тестирование компонента Onclick Grid
Потрясающие! Наша следующая остановка - тест на GridOnclickComponent
. Первое, что нам нужно сделать, это подготовить спецификацию:
Здесь нет ничего сложного: мы создаем экземпляр компонента и поддерживаем его сущностью. Мы также проследим, чтобы Grid
проснулся и сделал все необходимые приготовления.
Теперь, имея все фиктивные данные, мы можем просто ожидать, что самый первый Node
станет активным, когда мы щелкнем в его границах:
Опять же, мы сможем успешно скомпилировать с npm start
и пройти все тесты с npm t
:
Тестирование компонента ввода игры
Это вишенка поверх нашего торта на сегодня. Мы некоторое время откладывали тестирование GameInputComponent
, но больше не будем! Я начну с представления спецификации, как обычно:
Как и в случае с GridOnclickComponent
, я создал экземпляр объекта, самого компонента и прикрепил их друг к другу. Компонент не несет особой ответственности, он просто «обрабатывает щелчок»:
Но как именно это проверить? HandleClick
- это частный метод, и мы не можем просто вызвать его. И это законно, потому что то, как именно компонент выполняет свои обязанности, - это его детали реализации, которые нас не волнуют. Что нас интересует, так это то, что каждая дочерняя сущность Game
, к которой прикреплено какое-либо OnclickComponent
, получает уведомление, когда мы щелкаем внутри холста.
Мы можем легко подделать щелчок мышью. Мы также можем с легкостью высмеять Canvas
, чтобы вернуть нам фальшивый балл:
Но как мы можем проверить, действительно ли был вызван OnclickComponent
ребенка? Что ж, мы знаем, что к Grid
прикреплен этот компонент. Мы также знаем, что он добавлен как первый дочерний элемент к Game
. Мы можем рискнуть и запросить самый первый элемент из Game.Entities
списка:
И это сработает! Как только вы запустите npm t
, вы увидите, что тесты пройдены:
Но есть загвоздка. Мы зависим от порядка детей в Game
. Этот тест вообще не касается этого. Более того, если по какой-то причине мы изменим порядок элементов, наш тест не пройдёт. И это проблема, поскольку мы не меняли никакой логики, относящейся к этому тесту. Другими словами, теперь мы получаем ложные негативы.
Есть много способов сделать это. Я бы предпочел прямой доступ к дочернему элементу, который, как я ожидаю, будет иметь необходимый компонент (в данном случае Grid
) и построить свой тест на его основе. Однако Game
не предоставляет нам такой функциональности. Мы можем решить эту проблему с помощью внедрения зависимостей.
Д.И. - старый друг тестирования. Это один из самых простых и эффективных способов улучшить тестируемость вашего кода. Я не буду тратить время на подробное объяснение того, как это работает. К счастью, на эту тему есть множество статей, книг, подкастов и видео. Я продолжу при условии, что вы, уважаемый читатель, знаете и понимаете, как работает DI.
Внедрение зависимостей в игру
Зависимости, которые нас интересуют в этом случае, являются дочерними по отношению к Game
: Grid
и Fleet
. Чтобы сделать их «инъекционными», я перемещаю их из Awake
и вместо этого помечаю constructor
как обязательные параметры:
Для этого потребуется обновить main
файл:
А также game.mock
:
Теперь, вернувшись в спецификацию, мы можем напрямую указать ссылку на Grid
mock и следить за ним:
И это работает! Ваш код должен компилироваться с npm start
, и все тесты должны пройти с npm t
. Но есть еще одна загвоздка ...
Наши тесты проходят, пока Grid
имеет GridOnclickComponent
. Это предположение снова выходит из-под контроля данного конкретного теста. В любой момент мы можем удалить это из Grid
и получить еще один ложноотрицательный результат.
Чтобы решить эту проблему, мы должны убедиться, что у mocked Grid
есть несколько OnclickComponent
. Мы не можем полагаться на GridOnclickComponent
, поэтому мы должны определить поддельный:
Отлично сделано! Теперь наш тест охватывает только то, что должно. На этом этапе наш код должен успешно скомпилироваться с npm start
, а все тесты должны пройти с npm t
:
Вы можете найти полный исходный код этого поста в
input-3
ветке репозитория.
Заключение
На этом завершается эта короткая глава. Мы узнали, как мы можем заполнить события щелчка от DOM body
до конкретного Node
и сделать их активными. Мы ввели абстрактную OnclickComponent
, которая дает нам возможность делегировать событие без необходимости хранить информацию о конкретных респондентах. И, конечно же, мы тщательно протестировали все новые функции.
Следующая глава посвящена движению. Мы собираемся глубоко задуматься над механикой нашей игры. Главный вопрос для нас: «Как игроки могут перемещать свои Ships
?» Планирую опубликовать его в феврале-марте 2021 года, с нетерпением жду встречи с вами!
Мне бы очень хотелось услышать ваши мысли! Если у вас есть комментарии, предложения, вопросы или любой другой отзыв, не стесняйтесь присылать мне в личном сообщении или оставьте комментарий ниже! Если вам нравится эта серия, поделитесь ею с другими. Это действительно помогает мне продолжать работать над этим. Спасибо, что прочитали, до встречи в следующий раз!
Это глава N из серии руководств «Создание игры с помощью TypeScript». Другие главы доступны здесь:
- "Вступление"
- Глава I. Компонентная система сущностей
- Глава II. Игровой цикл (Часть 1, Часть 2)
- Глава III. Сетка для рисования (Часть 1, Часть 2, Часть 3, Часть 4, Часть 5)
- Глава IV. Суда (Часть 1, Часть 2, Часть 3, Часть 4)
- Глава V. Система ввода (Часть 1, Часть 2, Часть 3)
- Глава VI. Поиск пути и движение
- Глава VII. Государственная машина
- Глава VIII. Система атаки: здоровье и урон
- Глава IX. Победа и поражение в игре
- Глава X. Вражеский AI.