В предыдущем рассказе я описал базовые шаги по настройке проекта. Прочитайте его, если еще не сделали этого.
В этот раз речь пойдет о немного более сложных вещах, таких, как управление состоянием, компоненты высшего порядка и Gamepad API.
Шаг 6. Движение
Как вообще-то передвигать персонажа (который есть набор отдельных 3D-объектов) в пространстве? Я уже рассказывал ранее в 4 шаге о том, об объединении объектов в группу. Еще раз: вы можете перемещать группу с линейной скоростью; в сумме с подходящей анимацией это будет выглядеть как ходьба или бег, в зависимости от вашей задумки.
То есть если стоит задача передвигать NPC в каком-то направлении, скажем, на север, то нужно всего лишь:
Повернуть его в этом направлении
Мало-по-малу передвигать
Самый удобный, по-моему, способ - это использовать композицию. Для этого я создам файл /hoc/Movable.jsx
со следующим поведением:
Этот компонент берет всех потомков и перемещает их в направлении вектора heading в каждом новом кадре. Свойство Velocity отвечает за то, как далеко он это делает. Обратите внимание на onMove: о нем мы поговорим чуть позже.
Кстати, добавился еще хелпер /helpers/getRotation.js
который делает некоторую магию школы кватернионов, чтобы собственно поворачивать группу.
Теперь, если обернуть персонажа в компонент <Movable />
то он будет двигаться. Зацените демо:
Шаг 7. Поддержка геймпада
Раньше, вплоть до начала карантина, у меня не было геймпада. Я купил его просто попробовать. И оказалось, что это довольно удобный способ управления (никогда бы не подумал, но Diablo II: Resurrected с геймпадом - это что-то с чем-то). А в chrome поддержка геймпадов была еще давным-давно. Так что же мешает поэкспериментировать?
Gamepad API - довольно простая штука; можно даже брать код из примеров. Но чтобы заморачиваться еще меньше, я буду использовать библиотеку Gamepads. Используя контекст в реакте можно просто описать весь связующий код в одном месте и получать информацию о нажатиях кнопок, направлении и силе нажатия на стики прямо в компоненте Movable. Вот тут можно посмотреть реализацию: /GamepadControls.jsx
— колбеки для геймпада и клавиш WASD на клавиатуре (ссылка); /Player.jsx
— небольшая обертка над моделью игрока, которая и будет реагировать на ваши действия (ссылка).
Теперь я заменю всех NPC в index.js
этим:
<GamepadControls>
<Player />
</GamepadControls>
И все!
Используйте геймпад, если он у вас, конечно, есть, или клавиатуру.
Следующий важный вопрос: "Как соединить всех зомби, игрока и файрболы? Как враги узнают, где находится игрок?". И ответ - вы его знаете - глобальное состояние.
Шаг 8. Управление состоянием
Бесспорно, никакой фронтед-проект в 2021 не обходится без слоя управления состоянием. Игры не исключение. Вы, конечно, можете брать вашу любимую библиотеку, пусть то Redux, MobX, effector или что-то еще. В этом вообще никакой разницы. Я выбрал Zustand.
Все сущности можно представить коллекциями. Добавляем сущность (зомби или файрбол) в нужную коллекцию, рендерим, потом в нужный момент (файрбол врезается в зомби, или улетает слишком далеко) просто удаляем. Думаю, все не раз это проделывали, только сущности были другими, к примеру, товары в интернет-магазине.
Добавились следующие хранилища в папке /store
:
Надеюсь, названия методов говорят сами за себя. Player хранит позицию (она должна быть доступна всем) и жизни игрока; zombies - id каждого врага, их местоположение и hp. Наконец, fireballs используется для управления количеством и направлениями всех файрболов в сцене.
Теперь компонент Player выглядит так:
А Fireball - так:
И еще зацените компонент Zombie особенно вот эти места:
Теперь зомби преследуют вас, куда бы вы ни пошли.
Полный код этого примера доступен тут, за одним исключением: в codesandbox какие-то проблемы с парсингом .fbx, так что все модели там заменены на цветные кубики.
Спасибо за внимание! В следующий раз я разберу вопрос столкновений и UI.
Дополнение
Есть вероятность, что ваш геймпад не заработает в демке. Проблема в маппинге. У меня самый простой геймпад со стандартным маппингом, а за нестандартным обращайтесь к документации библиотеки Gamepads.