Как разработчик программного обеспечения, я трачу время на создание веб-сайтов и веб-приложений, но в течение долгого времени меня также интересовала виртуальная реальность. Я назвал свой Oculus Go «Бетти» и у меня кружится голова от разговоров о поездках на гондоле по Венеции, прыжках на захватывающих дух американских горках и путешествиях по венам в человеческом теле. Поскольку я в основном использую React, я был взволнован, узнав, что могу разрабатывать опыт виртуальной реальности с помощью библиотеки, которую я уже знаю и люблю.
Чтобы попробовать свои силы в React VR, я недавно создал приложение виртуальной реальности под названием Найди свой Дзен, которое позволяет пользователю выбрать иммерсивную среду для медитации, каждая из которых имеет свою собственную мантру, вдохновленную прекрасным шоу. Хорошее место. В мае 2018 года, вскоре после того, как я создал свое приложение, Facebook выпустил обновленную и переименованную версию React VR под названием React 360 с множеством изменений и значительных улучшений.
Перенеся свое приложение на React 360, я обратил внимание на некоторые важные различия между React VR и React 360. Я написал следующую статью для разработчиков, которые обладают практическими знаниями React. Если вы не знакомы с библиотекой, я рекомендую сначала начать здесь.
Если вы хотите познакомиться с React VR (а также с Recompose, служебные функции которого помогают управлять состоянием моего приложения), вы можете найти их здесь и здесь.
Просмотр готового демонстрационного кода
$ git clone https://github.com/lilybarrett/find-your-zen.git
$ cd find-your-zen
$ npm i
$ npm start
Файловая структура
Базовая файловая структура React VR была следующей:
index.vr.js
= точка входа для моего приложенияvr
папка = хранит код, запускающий мое приложение, включая файлыindex.html
иclient.js
static_assets
= хранит изображения, аудиофайлы и другие внешние ресурсы
А вот новая файловая структура React 360:
index.js
= точка входа для моего приложенияclient.js
= настраивает «среду выполнения», которая превращает мои компоненты React в трехмерные элементы в нашем ландшафте виртуальной реальности.index.html
= как в типичном приложении React, предоставляет мне место для монтирования моего кода Reactstatic_assets
= хранит изображения, аудиофайлы и другие внешние ресурсы
Я настроил остальную часть моей структуры папок следующим образом:
- components // shared components
- base-button
- content
- consts
- providers // Recompose providers live here
- scenes
- home-environment
- components
- menu
- title
- zen-button
- zens
- zen-environment
- components
- home-button
- mantra
- static-assets
- images
- sounds
Общие компоненты находятся в папке верхнего уровня components
. Хранится в scenes,
my HomeEnvironment
- первой загружаемой среде, где мой пользователь получает доступ к меню сред медитации для исследования - и ZenEnvironment
сцены имеют свои собственные наборы соответствующих компонентов. Мое управление состоянием осуществляется с помощью Recompose providers
и функционально объединяется в каждый компонент, которому требуется доступ к состоянию.
Установка приложения
В React VR мой client.js
был довольно простым и не давал мне слишком много вариантов конфигурации:
// React VR application -- vr/client.js// Auto-generated content. // This file contains the boilerplate to set up your React app. // If you want to modify your application, start in "index.vr.js"
import
{ VRInstance } from "react-vr-web"; function
init(bundle, parent, options) { const vr = new
VRInstance(bundle, "MeditationApp", parent, { cursorVisibility: "auto", // Add custom options here ...options, });
vr.render = function() { // Any custom behavior you want to perform on each frame goes here }; // Begin the animation loop vr.start(); return
vr; } window.ReactVR = {init};
В React 360 я могу монтировать контент моего приложения на поверхность или в другое место. Поверхности, как говорится в документации, позволяют добавлять 2D-интерфейсы в 3D-пространство, позволяя работать в пикселях, а не в физических измерениях. В моем случае я оборачиваю визуальный контент своего приложения в AppContent
компонент, который монтирую на цилиндрическую поверхность React 360 по умолчанию. Эта поверхность проецирует содержимое на внутреннюю часть цилиндра, расположенного по центру перед пользователем, с радиусом 4 метра.
Я могу создавать свои собственные пользовательские поверхности в React 360, увеличивая или уменьшая радиус или делая поверхность плоской, а не цилиндрической.
Я также монтирую все приложение в расположение по умолчанию React 360, что позволяет моему приложению использовать все преимущества среды выполнения React 360.
Новая среда выполнения - одно из значительных преимуществ React 360 перед React VR. Почему? Отделение аспектов визуализации или «времени выполнения» приложения от кода приложения улучшает задержку: время между действием пользователя и временем обновления пикселей в представлении в ответ на это действие. Если передача данных идет слишком медленно, это приводит к прерывистому, дезориентирующему виду для пользователя - подобно буферизации видео на Youtube или статическим помехам на экране телевизора.
Как далее объясняется в документации React 360, веб-браузеры являются однопоточными, что означает, что в рамках незаметных обновлений приложения этот процесс может блокировать или препятствовать передаче данных. Это особенно проблематично для пользователей, просматривающих ваш 360-градусный вид на гарнитуре VR, где значительная задержка рендеринга может нарушить ощущение погружения, - говорится в документации. Запуская код вашего приложения в отдельном контексте, мы позволяем циклу рендеринга постоянно обновлять с высокой частотой кадров .
В моем index.js
я регистрирую свой MeditationApp
(см. Второй блок кода ниже) для монтирования в расположение по умолчанию, предоставляя всему моему приложению доступ к среде выполнения, в то время как я регистрирую контент, который хочу отобразить (опять же, хранится в AppContent
) на цилиндрическую поверхность по умолчанию.
// components/content.js
import
React from "react"; import
{ View } from "react-360"; import
{ HomeEnvironment, ZenEnvironment } from "../../scenes"; import
{ withAppContext } from "../../providers";
const AppContent = withAppContext(() => ( <View> <HomeEnvironment /> <ZenEnvironment /> </View> ));
export
default
AppContent;
// index.js
import
React from "react"; import
{ AppRegistry, View, } from "react-360"; import
{ AppContent } from "./components"; import
{ withAppContext } from "./providers";
const MeditationApp = withAppContext(() => ( <View style={{ transform: [{ translate: [0, 0, -2] }] }}> <AppContent /> </View> ));
AppRegistry.registerComponent("AppContent", () => AppContent); AppRegistry.registerComponent("MeditationApp", () => MeditationApp);
Мой client.js
занимается установкой моего компонента на места и поверхности:
// client.js import
{ ReactInstance, Surface } from "react-360-web";
function
init(bundle, parent, options = {}) { const r360 = new
ReactInstance(bundle, parent, { fullScreen: true, // Add custom options here ...options, });
r360.renderToSurface( r360.createRoot("AppContent", { /* initial props */
}), r360.getDefaultSurface() );
r360.renderToLocation( r360.createRoot("MeditationApp", { /* initial props */
}), r360.getDefaultLocation(), );
r360.compositor.setBackground( r360.getAssetURL("images/homebase.png") ); }
window.React360 = {init};
Воспроизведение аудио
В моей папке consts
я создал файл zens.js
, в котором можно быстро сохранить мои данные, включая правильный аудиофайл и изображение, для каждой среды:
const zens = [ { id: 1, mantra: "Find your inner motherforking peace", image: "images/hawaii_beach.jpg", audio: "sounds/waves.mp3", text: "I'm feeling beachy keen", }, { id: 2, mantra: "Breathe in peace, breathe out bullshirt", image: "images/horseshoe_bend.jpg", audio: "sounds/birds.mp3", text: "Ain't no mountain high enough", }, { id: 3, mantra: "Benches will be benches", image: "images/sunrise_paris_2.jpg", audio: "sounds/chimes.mp3", text: "I want a baguette", }, { id: 4, image: "images/homebase.png", text: "Home" } ]
export default zens;
Для воспроизведения звука в сценах React VR я использовал компонент Sound
, который принимал URL-адрес звукового файла в папке static_assets
в качестве source
опоры. Чтобы предотвратить воспроизведение звука в средах, которым он не принадлежит, например, в домашней среде, я реализовал логику через Recompose для «скрытия» и «отображения» компонента Sound
в зависимости от того, находимся ли мы в среде без аудиофайлов. связанные с ним.
// React VR -- components/audio.js
import
React from "react"; import
{ Sound } from "react-vr"; import
zens from "../consts/zens.js"; import
{ compose } from "recompose"; import
{ asset } from "react-vr"; import
{ hideIf, usingAppContext } from "../providers/index.js";
const hideIfNoAudioUrl = hideIf(({ selectedZen }) => { const zenAudio = zens[selectedZen - 1].audio; return
zenAudio === null
|| zenAudio === undefined || zenAudio.length === 0; });
export
default
compose( usingAppContext, hideIfNoAudioUrl, )(({ selectedZen }) => { const zenAudio = zens[selectedZen - 1].audio; return
( <Sound source={asset(zenAudio)} /> ) });
React 360 значительно улучшает это. Для воспроизведения звука я использую модуль AudioModule
Native. Его playEnvironmental
метод позволяет мне указать путь (к аудио в папке с ресурсами) и громкость, на которой будет воспроизводиться этот звук в циклическом темпе. Как только аудиофайл перестанет воспроизводиться, он начнется снова.
Попутно я понял, что мне нужно сообщить своему приложению, когда нужно остановить воспроизведение определенного аудиофайла при переключении сцен. (В противном случае, погружаясь в Find Your Zen, вы можете закончить прослушивание звука из вашей предыдущей среды, то есть церковных колоколов на городской площади в Париже, после того, как вы вернетесь в домашнюю среду). Я достигаю этого с помощью метода AudioModule
stopEnvironmental
.
Продолжайте читать, чтобы увидеть это в действии…
Использование изображений
В React VR я использовал компонент Pano
для отображения фотографии на 360 градусов. Чтобы отобразить конкретное изображение, Pano
, как и Audio
, использовал URL-адрес ресурсов как source
prop. В зависимости от того, какую среду выбрал пользователь, состояние приложения обновляется для отображения изображения для этой среды.
// React VR -- components/wrapped-pano.js
import
React from "react"; import
{ Pano } from "react-vr"; import
{ usingAppContext } from "../providers/index.js"; import
{ Audio } from "../components/index.js"; import
zens from "../consts/zens.js"; import
{ asset } from "react-vr";
export
default
usingAppContext(({ selectedZen }) => { return
( <Pano source={asset(zens[selectedZen - 1].image)} > <Audio /> </Pano> ) });
Возможно, вы заметили или не заметили, что в моем приложении React 360 client.js
я пишу следующую строку после рендеринга компонентов моего приложения:
r360.compositor.setBackground(r360.getAssetURL("images/homebase.png"));
Эта строка кода, которая сразу устанавливает фоновое изображение при первом подключении приложения, использует утилиту asset
из React 360 для автоматического поиска нужного изображения в папке mystatic_assets
.
Это все хорошо, но я все же хочу изменить изображение в зависимости от того, какую среду выбирает пользователь. К счастью, я могу обрабатывать динамические изображения из события React с помощью модуля Environment
React 360. Вот пример использования:
Environment.setBackgroundImage(asset(someImage));
Чтобы собрать все это воедино, вот как я динамически настраиваю фоновое изображение и звук в зависимости от среды, которую выбирает пользователь, с помощью функций Recompose withState
и withHandlers
:
// providers/withStateAndHandlers.js
import
React from "react"; import
{ withState, withHandlers, compose } from "recompose"; import
{ Environment, asset, NativeModules } from "react-360"; const { AudioModule } = NativeModules; import
{ zens } from "../consts";
const withStateAndHandlers = compose( withState("selectedZen", "zenClicked", 4), withHandlers({ zenClicked: (props) => (id, evt) => { Environment.setBackgroundImage(asset(zens[id - 1].image)); if
(zens[id - 1].audio !== null
&& zens[id - 1].audio !== undefined) { AudioModule.playEnvironmental({ source: asset(zens[id - 1].audio), volume: 0.3, }); } else
{ AudioModule.stopEnvironmental(); } props.zenClicked(selectedZen => id); } }), )
export
default
withStateAndHandlers;
Стилизация приложения
React 360, как и React VR, использует Flexbox, чтобы легко адаптировать макет приложения к любому дисплею, будь то веб-браузер ноутбука, экран телефона или гарнитура VR. Однако для частей приложения, установленных в определенном месте - например, MeditationApp
в моем случае - React 360 переключается с макета Flexbox на трехмерную систему координат, основанную на счетчике. Вот почему вы видите этот код в моем index.js
:
// index.js
// other code goes here const MeditationApp = withAppContext(() => ( <View style={{ transform: [{ translate: [0, 0, -2] }] }}> <AppContent /> </View> ));
// other code goes here
В transform
передаются значения x
, y
и z
в указанном порядке. x
представляет ориентацию объекта справа от пользователя; y
представляет ориентацию вверх или вниз, а z
представляет собой воспринимаемое расстояние от пользователя.
В приведенном выше примере View
должен быть в центре и на 2 метра впереди пользователя.
Все трансформации расположены относительно своих родителей.
Практики, которые мне понравились
Таблицы стилей
StyleSheet
из react-native
позволил мне использовать JavaScript для стилизации моих компонентов React. Смотрите мой код ниже:
// scenes/home-environment/components/zen-button/style.js import
{ StyleSheet } from "react-360";
export
default
StyleSheet.create({ text: { backgroundColor: "#29ECCE", textAlign: "center", color: "white", marginTop: 30 } })
Здесь я создаю и экспортирую объект StyleSheet
, который позволяет мне кратко, СУХИМ образом ссылаться на стили в самом моем компоненте.
// scenes/home-environment/components/zen-button/index.js import
React from "react"; import
{ BaseButton } from "../../../../components"; import
style from "./style";
const ZenButton = ({ text, buttonClick, selectedZen }) => { return
( <BaseButton text={text} selectedZen={selectedZen} buttonClick={buttonClick} textStyle={style.text} /> ) }
export
default
ZenButton;
Управление государством
Поскольку, в конце концов, это все еще просто React, вы можете подойти к обработке состояния так же, как и в типичном приложении React: Redux, Recompose, Mobx и т. Д. Я решил использовать Перекомпонуйте, потому что мне нравится, как это позволяет мне создавать функциональные компоненты. Как упоминалось ранее, я написал несколько сообщений о Recompose в контексте React VR, которые вы можете найти здесь и здесь. Мне не нужно было ничего менять в моем подходе к управлению состоянием при переносе моего приложения с React VR на React 360.
Отладка React 360
Когда вы Inspect Element
в приложении, вы увидите, что React 360 объединяет все свои файлы в один гигантский объект, который не так-то просто разобрать. К счастью, поскольку React 360 поддерживает исходные карты, мы все еще можем получить доступ к исходным файлам, использовать debugger
и т. Д.