Статьи

Создание игры с Three.js, React и WebGL

Я делаю игру под названием « Харизма Хамелеон» . Она построена с использованием Three.js, React и WebGL. Это введение в то, как эти технологии работают вместе, используя рендеринг с тремя реакциями (сокращенно R3R).

Ознакомьтесь с Руководством для начинающих по WebGL и началу работы с React и JSX здесь, на SitePoint, для ознакомления с React и WebGL. В этой статье и сопровождающем коде используется синтаксис ES6 .

Руки держат красную таблетку и синюю таблетку, с хамелеоном на одной руке.

Как это все началось

Некоторое время назад Пит Хант пошутил над созданием игры с использованием React на IRC-канале #reactjs:

Могу поспорить, мы могли бы сделать шутер от первого лица с React!
У врага есть <Head /> <Body> <Legs> и т. Д.

Я смеялся. Он смеялся. Все прекрасно провели время. «Кто на земле сделает это?» — подумал я.

Спустя годы, это именно то, что я делаю.

Геймплей GIF от Харизмы Хамелеон

Харизма Хамелеон — игра, в которой вы собираете бонусы, которые заставляют вас сжаться, чтобы решить бесконечный фрактальный лабиринт. Я был разработчиком React в течение нескольких лет, и мне было любопытно, есть ли способ управлять Three.js с помощью React. Вот когда R3R попался на глаза.

Зачем реагировать?

Я знаю, о чем ты думаешь: почему? Приколи меня на мгновение. Вот несколько причин, по которым стоит использовать React для управления 3D-сценой:

  • «Декларативные» представления позволяют вам четко отделить рендеринг сцены от игровой логики.
  • Дизайн легко рассуждать о таких компонентах, как <Player /> , <Wall /> , <Level /> и т. Д.
  • «Горячая» (живая) перезагрузка игровых активов. Изменяйте текстуры и модели и смотрите, как они обновляются вживую на вашей сцене!
  • Проверяйте и отлаживайте свою 3D-сцену в виде разметки с помощью встроенных инструментов браузера, таких как инспектор Chrome.
  • Управление игровыми активами в графе зависимостей с помощью Webpack, например <Texture src={ require('../assets/image.png') } />

Давайте создадим сцену, чтобы понять, как все это работает.

Реагировать и WebGL

Я создал образец репозитория GitHub для сопровождения этой статьи. Клонируйте репозиторий и следуйте инструкциям в README для запуска кода и следуйте инструкциям. Это звезды SitePointy 3D-робот!

SitePointy 3D скриншот робота

Предупреждение: R3R все еще находится в бета-версии. Его API изменчив и может измениться в будущем. На данный момент он обрабатывает только подмножество Three.js. Я нашел его достаточно полным, чтобы построить полноценную игру, но ваш пробег может отличаться.

Организация просмотра кода

Основное преимущество использования React для управления WebGL заключается в том, что код нашего представления отделен от нашей игровой логики. Это означает, что наши визуализируемые объекты представляют собой небольшие компоненты, которые легко рассуждать.

R3R предоставляет декларативный API, который оборачивает Three.js. Например, мы можем написать:

 <scene> <perspectiveCamera position={ new THREE.Vector3( 1, 1, 1 ) /> </scene> 

Теперь у нас есть пустая 3D-сцена с камерой. Добавить сетку на сцену так же просто, как включить компонент <mesh /> и присвоить ему <geometry /> и <material /> .

 <scene><mesh> <boxGeometry width={ 1 } height={ 1 } depth={ 1 } /> <meshBasicMaterial color={ 0x00ff00 } /> </mesh> 

Под капотом это создает THREE.Scene и автоматически добавляет меш с THREE.BoxGeometry . R3R обрабатывает изменение старой сцены с любыми изменениями. Если вы добавите новую сетку в сцену, оригинальная сетка не будет воссоздана. Как и в случае с Vanilla React и DOM, 3D-сцена обновляется только с учетом различий.

Поскольку мы работаем в React, мы можем разделить игровые объекты на файлы компонентов. Файл Robot.js в репозитории примеров демонстрирует, как представить главного персонажа с помощью чистого кода представления React. Это функциональный компонент без сохранения состояния, то есть он не содержит локального состояния:

 const Robot = ({ position, rotation }) => <group position={ position } rotation={ rotation } > <mesh rotation={ localRotation }> <geometryResource resourceId="robotGeometry" /> <materialResource resourceId="robotTexture" /> </mesh> </group>; 

И теперь мы включаем <Robot /> в нашу 3D-сцену!

 <scene><mesh></mesh> <Robot position={} rotation={} /> </scene> 

Вы можете увидеть больше примеров API в репозитории R3R GitHub или просмотреть полную настройку примера в сопроводительном проекте .

Организация игровой логики

Вторая половина уравнения — логика игры. Давайте дадим SitePointy, нашему роботу, простую анимацию.

SitePointy имеет время своей жизни

Как традиционно работают игровые циклы? Они принимают пользовательский ввод, анализируют старое «состояние мира» и возвращают новое состояние мира для рендеринга. Для удобства давайте сохраним наш объект «игровое состояние» в состоянии компонента. В более зрелом проекте вы можете перенести игровое состояние в магазин Redux или Flux.

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

 // … gameLoop( time ) { this.setState({ robotPosition: new THREE.Vector3( Math.sin( time * 0.01 ), 0, 0 ) }); } 

Вызов setState() запускает повторный рендеринг дочерних компонентов и обновления 3D-сцены. Мы передаем состояние от компонента контейнера до презентационного компонента <Game /> :

 render() { const { robotPosition } = this.state; return <Game robotPosition={ robotPosition } />; } 

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

Теперь мы можем написать чистый, простой игровой цикл, в котором есть только вызовы функций:

 import robotMovementReducer from './game-reducers/robotMovementReducer.js'; // … gameLoop() { const oldState = this.state; const newState = robotMovementReducer( oldState ); this.setState( newState ); } 

Чтобы добавить больше логики в игровой цикл, такой как физика обработки, создайте еще одну функцию-редуктор и передайте ей результат предыдущего редуктора:

 const newState = physicsReducer( robotMovementReducer( oldState ) ); 

По мере роста игрового движка организация логики игры в отдельные функции становится критической. Эта организация проста с моделью редуктора.

Управление активами

Это все еще развивающаяся область R3R. Для текстур вы указываете атрибут url в теге JSX. Используя Webpack, вы можете указать локальный путь к изображению:

 <texture url={ require( '../local/image/path.png' ) } /> 

С этой настройкой, если вы измените изображение на диске, ваша 3D сцена будет обновляться в реальном времени! Это неоценимо для быстрой итерации игрового дизайна и контента.

Для других ресурсов, таких как 3D-модели, вам все равно придется обрабатывать их с помощью встроенных загрузчиков из Three.js, таких как JSONLoader . Я экспериментировал с использованием пользовательского загрузчика Webpack для загрузки файлов 3D-моделей, но в конце концов это было слишком много работы, но безрезультатно. Проще воспринимать модель как двоичные данные и загружать их с помощью загрузчика файлов . Это все еще дает возможность перезагрузки данных модели в реальном времени. Вы можете увидеть это в действии в примере кода .

Отладка

R3R поддерживает расширение инструментов разработчика React для Chrome и Firefox . Вы можете осмотреть свою сцену, как если бы это был ванильный DOM! При наведении курсора на элементы в инспекторе отображается их ограничительная рамка на сцене. Вы также можете навести курсор на определения текстур, чтобы увидеть, какие объекты в сцене используют эти текстуры.

Отладка сцены с использованием рендеринга типа 3 и React devtools

Вы также можете присоединиться к нам в чате Gitter реагирования на три рендерера для помощи в отладке ваших приложений.

Вопросы производительности

При создании Charisma The Chameleon я столкнулся с несколькими проблемами производительности, которые характерны только для этого рабочего процесса.

  • Мое горячее время перезарядки с Webpack составило тридцать секунд! Это связано с тем, что большие ресурсы необходимо перезаписывать в пакет при каждой перезагрузке. Решением было реализовать DLLPlugin в Webpack , который сократил время перезагрузки до пяти секунд.
  • В идеале ваша сцена должна вызывать только один setState() на кадр рендера. После профилирования моей игры само React является основным узким местом. Вызов setState() более одного раза за кадр может вызвать двойную визуализацию и снизить производительность.
  • За определенным количеством объектов R3R будет работать хуже, чем ванильный код Three.js. Для меня это было около 1000 объектов. Вы можете сравнить R3R с Three.js в разделе «Тесты» в примерах .

Функция Chrome DevTools Timeline — удивительный инструмент для отладки производительности. Это легко визуально осмотреть ваш игровой цикл, и он более читабелен, чем функция «Профиль» DevTools.

Это оно!

Проверьте Харизму Хамелеон, чтобы увидеть, что возможно с помощью этой настройки. Хотя этот набор инструментов еще довольно молод, я считаю, что React с R3R является неотъемлемой частью чистой организации моего игрового кода WebGL. Вы также можете проверить небольшую, но растущую страницу примеров R3R, чтобы увидеть некоторые хорошо организованные примеры кода.

Эта статья была рецензирована Марком Брауном и Кевом Цеттлером . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!