Игра в браузере на React и Three.js!

Я занимаюсь фронтендом уже очень давно, порядка 10 лет. И как любой уважающий себя фронтендер, я люблю тащить javascript туда, где обычно его не используют: на сервер, в мобильные приложения, в геймдев. С тех пор как я увидел первые WebGL демосцены в 2013-м, я мечтал сделать что-то похожее, скажем, на это.

Так что я провел немало времени экспериментируя и читая документацию, и вот что у меня получилось.

Дисклеймер: я не претендую на абсолютные знания того, как делают игры, я всего лишь описываю свой наивный подход к поиску ответа на вопрос, который задает заголовок. Поэтому это будет не совсем игра, а скорее концепт, интерактивная демо-сцена.

Дизайн

В рамках эксперимента я создам игру с изометрическим видом, в которой герой будет убивать зомби. Каждый раз, когда герой кастует файрбол, на карте в случайном месте появляется новый враг. Цель игры - как можно дольше остаться в живых. Управление будет с помощью геймпада.

Шаг 1. Настройка проекта

Как и в любом другом JS-проекте, в первую очередь придется выполнить настройку. Я ненавижу долгие настройки, полные магических операций, поэтому буду пользоваться Parcel, так как он придерживается zero-config концепции. В качестве рендер-движка я буду использовать Three.js и react-three-fiber, который склеит React и Three.js и освободит от императивности последней.

В первую очередь, я создам папку под проект и выполню следующее:

npm init -y
npm install parcel-bundler react react-dom three react-three-fiber
npm install -D @babel/core @babel/preset-react parcel-plugin-static-files-copy

Так же понадобится библиотека с крайне полезными утилитами для Three - drei.

npm install @react-three/drei

А так же следующая строчка в разделе scripts package.json:

"start": "parcel ./index.html"

А еще index.html со следующим содержимым.

Теперь после выполнения команды npm start можно перейти по адресу http://localhost:1234 и приступить к делу.

Шаг 2. Базовые вещи

Позвольте мне пропустить подробности того, как сделан react-three-fiber и как он работает, в сети довольно много информации по этой теме. Например, в официальной документации.

Прежде всего, в игре должна быть земля, поверхность, чтобы все не выглядело так, словно персонажи висят в воздухе. В приведенном ниже примере я создал базовую сцену с компонентами:

  • Canvas. Самый главный компонент, который делает за вас много работы: запускает игровой цикл, следит за всем, является контекстом. Большинство настроек сцены будут в нем.

  • axesHelper. Компонент, который добавляет в сцену оси (хотя, конечно, не очень полезный, потому что ось Y направлена вверх, хотя я ожидал, что вверх будет направлена ось Z).

  • mesh. Поверхность. В такой компонент нужно прокинуть два потомка: описание геометрии поверхности и ее материала.

  • pointLight. Источник света. Без него вы не увидите ничего.

Тут должен был быть эмбед codesandbox, но что-то пошло не так: https://codesandbox.io/embed/sleepy-wu-4vt60

Шаг 3. Движение камеры

Сейчас вся сцена статична и не очень впечатляюща, поэтому я воспользуюсь вспомогательной сущностью three.js - OrbitControls, которая даст возможность манипулировать камерой с помощью мыши. Если зажать левую клавишу, то камера будет вращаться вокруг фиксированной точки и всегда будет обращена к ней. Колесико будет регулировать степень приближения. Кроме того, компонент принимает свойства, которые определяют ограничения, то есть, например, не позволят камере быть выше или ниже определенных координат.

Пример использования.

Шаг 4. Файрбол

Ок, пришло время перейти к более интересным и сложным вещам. К созданию файрбола. К сожалению, я не умею писать шейдеры, поэтому взял и адаптировал уже готовый код из подходящей демки.

Идея проста как дважды два. Всего три компонента: сфера, цилиндр для огня и еще один, большего размера, для дыма. Все компоненты имеют прозрачный shaderMaterial, который обновляется в каждом новом кадре. В итоге все выглядит, как будто он состоит из пылающей лавы.

Вот тут лежит полный код файрбола, а пока я расскажу о некоторых аспектах.

Файл /src/fireball/fireball.jsx экспортирует единственный компонент, который, в свою очередь, состоит из трех других, завернутых в группу. Почему? Потому что это позволяет применять трансформации на всех потомках, сохраняя их относительное местоположение и соотношения.

В каждом подкомпоненте есть следующие строки:

useFrame вызывается каждый раз, когда срабатывает requestAnimationFrame - в зависимости от вашего оборудования 60 или, например, 144 раза в секунду.

Наконец, если поместить компонент в сцену и прокинуть в него свойства position и scale, то выглядеть это будет следующим образом:

Тут должен был быть эмбед codesandbox, но что-то пошло не так: https://codesandbox.io/embed/exciting-marco-iyy6h

Шаг 5. Персонажи

Где взять модели персонажей? Отличный вопрос. К примеру, в mannequin.js или mixamo. Можно даже купить на sketchfab (и использовать mixamo для создания скелета в нем). В целях эксперимента я буду использовать mixamo, там есть подходящие модели и анимации.

При скачивании модели стоит выбрать формат FBX.

Файл .fbx может весить до 15 МБ (27 тысяч полигонов!), и не хочется, чтобы они были частью бандла. Так что будем грузить их через ajax тогда, когда они понадобятся.

parcel-plugin-static-files-copy сделает все необходимое: возьмет статику из папки /assets/models в /dist. Для этого нужно добавить следующее в package.json:

"staticFiles": {  "staticPath": "assets"}

В целях отображения модели персонажа я создал два новых файла: getModel.js и NPC.jsx. Первый экспортирует хук, который ответственен за асинхронную загрузку файлов моделей и включение теней, второй использует этот хук и контролирует анимации. Все довольно просто. Кстати, анимации уже включены в состав FBX файла, но не будем же мы каждый раз из заново парсить. Проще сохранить их где-то в виде json-а и использовать на разных моделях (в этом универсальное преимущество mixamo: все модели имеют скелет с одинаково названными костями, так что можно взять описание анимации одного персонажа и сыграть на другом).

Animation clip в three.js - это просто json файл с описанием того, как будет двигаться каждая кость в скелете. Так что их можно просто взять и добавить в специальный объект AnimationMixer. AnimationMixer, ну, смешивает анимации ¯\_(ツ)_/¯. Но здесь я буду использовать его для переключения между различными состояниями.

Спасибо, что дочитали до конца! В следующей части я опишу, как использовать геймпад, заставить модели двигаться и как сюда вписывается state management.

Полезные ресурсы

  1. https://codepen.io/pizza3/pen/Rwoqemx?editors=0010 — Демка файрбола

  2. https://ykob.github.io/sketch-threejs/sketch/fire_ball_2.html — Еще одна

  3. https://docs.pmnd.rs/react-three-fiber/getting-started/introduction— Документация по react-three-fiber

  4. https://www.mixamo.com/ — Каталог 3D-моделей и штука для авторига

  5. https://boytchev.github.io/mannequin.js/ — Любопытная библиотека для генерации моделей персонажей

Комментарии (8)


  1. thegriglat
    24.11.2021 12:48
    +1

    Когда я делал свою "недоигру" (в качестве самообучения), просто пилил ее в Unity и собирал в WebGL (thegriglat.itch.io/landslide, не реклама, как пример)

    Также хотелось немного сравнения с другими технологиями для game in web в статье, раз уж это часть 1

    Да и почитав разное про web игры понял что хоть для разработчика это заманчиво (скомпилировал раз и работает (почти) везде), на деле это нерентабельно от слова совсем, поэтому только учебные прокты и есть


    1. john_samilin Автор
      24.11.2021 12:59

      Я слышал, что когда-то можно было компилировать Unity в WebGL, но минусов, говорят, было немало.

      Мой поинт в том, что а) для простых игр декларативность jsx не будет проблемой; б) plug-n-play подход (в теории) тоже не будет; в) это проще, чем кажется на первый взгляд.

      Я не буду делать сравнений библиотек, отчасти потому что кроме очевидных three.js и babylon есть еще десяток (blend4web, Seen.js, Phoria.js, LightGL.js и так далее) и сравнивать их так же бессмысленно, как сравнивать стейт менеджеры.

      Сейчас это не рентабельно, но это не значит, что оно таким и останется. К примеру, именно запрос webgl-сообщества привел к появлению формата GLTF, так что я бы не стал ставить на этой теме крест.


      1. N0zzy
        24.11.2021 23:28
        +1

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


        1. terantul
          25.11.2021 09:11
          +1

          Ко мне уже обращались пару человек на предмет создания 3D сайта.

          Один из проектов - интернет магазин. Подсчитали примерную стоимость перевода в 3д модели всех товаров - владелец решил что продавать квартиру не стоит :-)

          А вот для мелких сайтов запилить версию в WebGL, вполне здравая идея - всё новое привлекает внимание. Так что сижу изучаю технологию, хоть и не мой профиль.


          1. thegriglat
            25.11.2021 11:42

            как пример крутейшего 3D сайта https://www.aquarium.ru


            1. john_samilin Автор
              25.11.2021 11:53

  1. gameplayer55055
    25.11.2021 11:08
    +3

    Использовать фреймворки в играх - зло. Я про реакт. Реакт двигать менюшки, а вся игра это почти только канвас. Но все равно грузить мегабайты лишние.

    А вообще можно и wasm подключить, есть даже шейдеры glsl


    1. john_samilin Автор
      25.11.2021 11:38

      Про шейдеры я обязательно расскажу (кратко), а если нужно, то и про wasm могу