React VR 快速入門徹底教程

React VR 快速入門

什麼是React

React是一個開放源代碼的JavaScript庫,爲HTML呈現的數據提供了視圖渲染。React視圖一般使用指定的像HTML標籤同樣的組件來進行UI渲染。它目前是最流行的JavaScript庫之一,它擁有強大的基礎和龐大的社區。javascript

建立一個React App

$ npm install -g create-react-app
$ create-react-app my-app

$ cd my-app
$ npm start

效果圖

效果圖

什麼是React Native?

React Native是僅使用Javascript的移動應用構建框架。它使用與React相同的設計,包含豐富的UI庫和組件聲明。php

它使用與常規iOS和Android應用程序相同的基本UI構建塊。html

使用React-Native最讚的地方是還能夠經過原生的Objective-C, Java, 或者Swift來構建組件。java

  • React VS React Native:node

vs

  • React Native bridge:react

bridge

建立一個React Native App

  • 環境安裝android

查看官網http://facebook.github.io/react-native/docs/getting-started.htmlios

  • 建立項目git

$ react-native init my-rn-app
  • 運行項目github

To run your app on iOS:
   cd /Users/liyuechun/Pictures/my_rn_app
   react-native run-ios
   - or -
   Open ios/my_rn_app.xcodeproj in Xcode
   Hit the Run button
To run your app on Android:
   cd /Users/liyuechun/Pictures/my_rn_app
   Have an Android emulator running (quickest way to get started), or a device connected
   react-native run-android
  • 效果圖

iOS Android

聯繫我

掃碼

開始使用React VR

React VR旨在容許Web開發人員使用React的聲明方法(特別是React Native)來創做虛擬現實(VR)應用程序。

React VR使用Three.js 來支持較低級別的WebVRWebGL API. WebVR是用於訪問Web上VR設備的API。 WebGL(Web圖形庫)是一種無需使用插件便可用於在任何兼容的Web瀏覽器中渲染3D圖形的API。

React VR相似於React Native,由於它使用ViewImageText做爲核心組件,而且支持Flexbox佈局。 此外,React VR還將PanoMeshPointLight等VR組件添加相關庫中。

在本篇文章中,我將帶領你們建立一個簡單的VR應用程序來學習如何建立一個全景圖片,3D對象模型,按鈕和flexbox佈局的使用場景。咱們的模擬應用程序基於React VR的兩個網格佈局官方示例

該應用程序將渲染一個可以放大和縮小的地球月球的3D模型,效果圖以下所示:

finished app

這些模型中,它們的尺度和旋轉不是地球-月球系統的真實複製品。 這種關係只是爲了展現React VR的工做原理。 與此同時,咱們將解釋一些關鍵的3D建模概念。 一旦掌握了ReactVR,就能夠隨意創造更多的做品。

你可以從 GitHub找到最後的項目源代碼。

要求

到目前爲止,虛擬現實是一項至關新的技術,開發或測試咱們的VR應用程序的方法不多。

WebVRIs WebVR Ready? 能夠幫助您瞭解哪些瀏覽器和設備支持最新的VR規範。

可是你也沒必要過於擔憂, 你如今不須要任何特殊的設備,例如: Oculus Rift, HTC Vive, 或者 Samsung Gear VR 來測試一個WebVR APP。

下面是你如今 所須要 準備的:

  • 一臺Windows/Mac電腦。

  • Google瀏覽器。

  • 最新版本的Node.js

若是您也有Android設備和Gear VR耳機,您能夠安裝Carmel Developer Preview瀏覽器來探索您的React VR 應用程序。

建立項目

首先,咱們須要使用NPM來安裝React VR CLI工具:

$ npm install -g react-vr-cli

使用React VR CLI來建立一個名字叫作EarthMoonVR的新項目:

$ react-vr init EarthMoonVR

在建立過程當中您須要等一下子,這將建立一個EarthMoonVR目錄,目錄裏面就是相關項目文件,若是但願建立速度快一些,您能夠安裝 Yarn 來提升速度。

一旦項目建立完畢,能夠經過cd切換到EarthMoonVR文件路徑下面:

$ cd EarthMoonVR

在終端經過npm start來啓動程序以便查看效果:

$ npm start

在瀏覽器中輸入http://localhost:8081/vr。稍微等一下子,便可查看到VR效果:
sample app

React VR

下面是初始化的React VR新項目的項目結構:

+-__tests__
+-node_modules
+-static_assets
+-vr
\-.babelrc
\-.flowconfig
\-.gitignore
\-.watchmanconfig
\-index.vr.js
\-package.json
\-rn-cli-config.js
\-yarn.lock

我將index.vr.js文件呈現高亮狀態,它包含了您應用程序的源碼,static_assets目錄包含像圖片和3D模型的外部資源文件。

你能夠從這裏瞭解更多項目結構。

index.vr.js 文件的內容以下:

import React from 'react';
import {
  AppRegistry,
  asset,
  StyleSheet,
  Pano,
  Text,
  View,
} from 'react-vr';

class EarthMoonVR extends React.Component {
  render() {
    return (
      <View>
        <Pano source={asset('chess-world.jpg')}/>
        <Text
          style={{
            backgroundColor:'blue',
            padding: 0.02,
            textAlign:'center',
            textAlignVertical:'center',
            fontSize: 0.8,
            layoutOrigin: [0.5, 0.5],
            transform: [{translate: [0, 0, -3]}],
          }}>
          hello
        </Text>
      </View>
    );
  }
};

AppRegistry.registerComponent('EarthMoonVR', () => EarthMoonVR);

咱們能夠看到React VR使用了ES2015JSX

這個代碼經過React Native packager進行預編譯,它提供了(ES2015, JSX)編譯和其餘資源加載。

render函數中,return了一個頂級標籤,裏面包含:

  • View 組件, 它是能夠包含其餘全部組件的容器組件。

  • Pano 組件, 用於將(chess-world.jpg)渲染一張360度的圖片。

  • Text 組件, 用於渲染字體的3D空間。

注意,Text組件經過一個內聯的樣式對象來設置樣式。在React VR中的每個組件都有一個style屬性來控制控制它的外觀和佈局。

除了添加PanoVrButton等特殊組件以外,React VR還使用了與React和React Native相同的概念,例如組件,屬性,狀態,生命週期,事件和彈性佈局。

最後,項目根組件應該經過AppRegistry.registerComponent來進行註冊,以便App可以進行打包和正常運行。

如今咱們知道代碼是作什麼用的,接下來咱們將全景圖片拖拽到項目中。

全景圖像

一般,咱們的VR應用程序中的空間由全景(pano)圖像組成,它建立了一個1000米的球體(在React VR距離中,尺寸單位爲米),並將用戶置於其中心。

一張全景圖像容許你從上面,下面,後面以及你的前面去觀察它,這就是他們也被稱爲360的圖像或球面全景的緣由。

360全景圖有兩種主要格式:平面全景圖立方體。 React VR支持二者。

平面全景圖

平面全景圖由寬高比爲2:1的單個圖像組成,意味着寬度必須是高度的兩倍。

這些照片是經過360度照相機建立的。一個很好的平面圖像來源是Flickr,你打開這個網站嘗試搜索equirectangular關鍵字,例如:我經過equirectangular關鍵字嘗試搜索就找到這張圖片:

equirectangular example

看起來很奇怪,不是嗎?

不管如何,下載最高可用分辨率的照片,將其拖拽到項目中static_assets的路徑下面,而且修改render函數中的代碼,以下所示:

render() {
    return (
      <View>
        <Pano source={asset('sample_pano.jpg')}/>
      </View>
    );
  }

Pano組件的source屬性接收一個當前圖片位置的uri屬性值。這裏咱們使用了asset函數,將sample_pano.jpg做爲參數,這個函數將會返回static_assets 路徑下的圖片的正確的uri。換句話說,上面的方法等價於下面的方法:

render() {
  return (
    <View>
      <Pano source={ {uri:'../static_assets/sample_pano.jpg'} }/>
    </View>
  );
}

假設本地服務器一直在運行,當咱們在瀏覽器中刷新頁面時,咱們將看到以下效果:

sample pano

順便說一下,若是咱們想避免在每次更改時都須要從新刷新頁面,咱們能夠經過在URL(http://localhost:8081/vr/?hot... 中添加 ?hotreload 來啓用 熱刷新

立方體全景圖

立方體全景圖是360度全景圖的其餘格式。這種格式使用六個圖像做爲一個多維數組集的六個面,它將填充咱們周圍的球體。 它也被稱爲天空盒。

基本思想是渲染一個立方體,並將觀衆置於中心,隨後移動。

例如,下面的這張大圖中,每個方位的小圖表明立方體的一面:

cube image

爲了可以在React VR中使用立方體全景圖,Pano組件的source屬性的uri值必須指定爲一個數組,數組中的每一張圖片分別表明[+x, -x, +y, -y, +z, -z],以下所示:

render() {
  return (
    <View>
      <Pano source={
        {
          uri: [
            '../static_assets/sample_right.jpg',
            '../static_assets/sample_left.jpg',
            '../static_assets/sample_top.jpg',
            '../static_assets/sample_bottom.jpg',
            '../static_assets/sample_back.jpg',
            '../static_assets/sample_front.jpg'
          ]
        }
      } />
    </View>
  );
}

在2D佈局中,X軸越向右x值越大,Y軸越向下值越大,(0,0)座標爲最左上角,右下角表明元素的寬和高(width, height)。

然而,在3D空間中,React VR使用了同OpenGL使用的右手座標系統,正X指向右邊,正Y指向上邊,正Z指向用戶的方向。由於用戶的默認視圖從原點開始,這意味着它們將從負Z方向開始:

3D coordinate system

你能夠從React VR coordinate system here瞭解更多React VR座標系統.

這樣,咱們的立方體(或天空盒)將以下所示:

cubemap sample

Skybox在Unity中使用了不少,因此有不少地方能夠找到他們並進行下載。 例如,我從這個頁面下載了撒哈拉沙漠。我將圖片拖拽到項目中,並修改代碼以下所示:

render() {
  return (
    <View>
      <Pano source={
        {
          uri: [
            '../static_assets/sahara_rt.jpg',
            '../static_assets/sahara_lf.jpg',
            '../static_assets/sahara_up.jpg',
            '../static_assets/sahara_dn.jpg',
            '../static_assets/sahara_bk.jpg',
            '../static_assets/sahara_ft.jpg'
          ]
        }
      } />
    </View>
  );
}

效果以下所示:

sahara cubemap 1

你能注意到頂部和底部的圖像不協調嗎?咱們能夠經過將頂部圖像順時針旋轉90度,底部逆時針旋轉90度來校訂它們:

sahara cubemap 2

如今讓咱們爲咱們的應用建立一個空間天空盒。

最好的程序是Spacescape, 它一個免費的工具,可在Windows和Mac上建立空間天空盒(包括星星和星雲)。

建立一個sampleSpace.xml文件,將下面的代碼拷貝到sampleSpace.xml文件中:

<?xml version="1.0" encoding="utf-8" ?>
<spacescapelayers>
    <layer>
        <destBlendFactor>one</destBlendFactor>
        <farColor>0 0 0 1</farColor>
        <hdrMultiplier>1</hdrMultiplier>
        <hdrPower>1</hdrPower>
        <maskEnabled>false</maskEnabled>
        <maskGain>0.5</maskGain>
        <maskLacunarity>2</maskLacunarity>
        <maskNoiseType>fbm</maskNoiseType>
        <maskOctaves>1</maskOctaves>
        <maskOffset>1</maskOffset>
        <maskPower>1</maskPower>
        <maskScale>1</maskScale>
        <maskSeed>1</maskSeed>
        <maskThreshold>0</maskThreshold>
        <name>Fuzzy Blue Stars</name>
        <nearColor>1 1 1 1</nearColor>
        <numPoints>3000</numPoints>
        <pointSize>3</pointSize>
        <seed>4</seed>
        <sourceBlendFactor>one</sourceBlendFactor>
        <type>points</type>
    </layer>
    <layer>
        <destBlendFactor>one</destBlendFactor>
        <ditherAmount>0.03</ditherAmount>
        <gain>1.5</gain>
        <hdrMultiplier>1</hdrMultiplier>
        <hdrPower>1</hdrPower>
        <innerColor>1 1 1 1</innerColor>
        <lacunarity>2</lacunarity>
        <name>Fuzzy White Star Overlay</name>
        <noiseType>ridged</noiseType>
        <octaves>8</octaves>
        <offset>0.94</offset>
        <outerColor>0 0 0 1</outerColor>
        <powerAmount>1</powerAmount>
        <previewTextureSize>1024</previewTextureSize>
        <scale>10</scale>
        <seed>4</seed>
        <shelfAmount>0.81</shelfAmount>
        <sourceBlendFactor>one</sourceBlendFactor>
        <type>noise</type>
    </layer>
</spacescapelayers>

而且經過Spacescape軟件打開sampleSpace.xml文件,效果圖以下所示:

spacescape

咱們能夠導出天空盒的六張圖像:

09-export-skybox

若是咱們修改代碼以下所示:

<Pano source={
  {
    uri: [
      '../static_assets/space_right.png',
      '../static_assets/space_left.png',
      '../static_assets/space_up.png',
      '../static_assets/space_down.png',
      '../static_assets/space_back.png',
      '../static_assets/space_front.png'
    ]
  }
}/>

將獲得以下結果:

10-space-skybox

如今讓咱們來討論討論3D模型。

聯繫我

掃碼

3D 模型

React VR 有一個 Model 組件,它支持Wavefront .obj file format 來表明3D建模。

mesh是定義3D對象形狀的頂點、邊和麪的集合。

.obj文件是一個純文本文件,其中包含幾何頂點,紋理座標,頂點法線和多邊形面元素的座標。

一般,.obj文件引用一個外部.mtl file文件,其中存儲了描述多邊形視覺方面的材質(或紋理)。

您可使用Blender, 3DS Max,和Maya等程序建立3D模型並將其導出爲這些格式。

還有不少網站能夠免費下載或免費下載3D模型。 如下是其中三個很不錯的:

對於咱們的應用程序,咱們將使用這個3D地球模型和這個來自TF3DM的3D月球模型

當咱們將地球模型的文件提取到咱們應用程序的static_assets目錄時,咱們能夠看到有一堆圖像(紋理)以及.obj和.mtl文件。 若是咱們在文本編輯器中打開後者,咱們將看到以下所示定義:

# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 25.01.2016 02:22:51

newmtl 01___Default
    Ns 10.0000
    Ni 1.5000
    d 1.0000
    Tr 0.0000
    Tf 1.0000 1.0000 1.0000 
    illum 2
    Ka 0.0000 0.0000 0.0000
    Kd 0.0000 0.0000 0.0000
    Ks 0.0000 0.0000 0.0000
    Ke 0.0000 0.0000 0.0000
    map_Ka 4096_earth.jpg
    map_Kd 4096_earth.jpg
    map_Ke 4096_night_lights.jpg
    map_bump 4096_bump.jpg
    bump 4096_bump.jpg

newmtl 02___Default
    Ns 10.0000
    Ni 1.5000
    d 1.0000
    Tr 0.0000
    Tf 1.0000 1.0000 1.0000 
    illum 2
    Ka 0.5882 0.5882 0.5882
    Kd 0.5882 0.5882 0.5882
    Ks 0.0000 0.0000 0.0000
    Ke 0.0000 0.0000 0.0000
    map_Ka 4096_earth.jpg
    map_Kd 4096_earth.jpg
    map_d 4096_earth.jpg

如今咱們將添加Model組件,以下面的代碼所示:

<Model source={{obj:asset('earth.obj'), mtl:asset('earth.mtl')}} lit={true} />

lit屬性指定網格中使用的材料應使用Phong shading處理燈。

同時,也不要忘了從react-vr中導入Model組件:

import {
  ...
  Model,
} from 'react-vr';

可是,若是咱們只將該組件添加到咱們的應用程序中,則不會顯示任何內容。 咱們首先須要添加一個光源。

React VR 有四種光源類型:

  • AmbientLight表示全方位,固定強度和固定顏色的光源,能夠均勻地影響場景中的全部對象。

  • DirectionalLight表示從指定方向平均照亮全部物體的光源。

  • PointLight 表明光的起源來源於一個點,並向各個方向傳播。

  • SpotLight 表明光的起源來源於一個點,並以錐形向外擴散。

您能夠嘗試全部類型的燈光,看看哪個能夠爲您帶來最佳效果。 在這種狀況下,咱們將使用強度值爲2.6AmbientLight

import React from 'react';
import {
  AppRegistry,
  asset,
  StyleSheet,
  Pano,
  Text,
  View,
  Model,
  AmbientLight,
} from 'react-vr';

class EarthMoonVR extends React.Component {
  render() {
    return (
      <View>
        ...

        <AmbientLight intensity={ 2.6 }  />
        
        <Model source={{obj:asset('earth.obj'), mtl:asset('earth.mtl')}} lit={true} />
      </View>
    );
  }
};

AppRegistry.registerComponent('EarthMoonVR', () => EarthMoonVR);

接下來,咱們須要給咱們的模型一些用於放置、大小、和旋轉的樣式屬性。 經過嘗試不一樣的值,我想出瞭如下配置:

class EarthMoonVR extends React.Component {
  render() {
    return (
      <View>
        ...

        <Model
          style={{
            transform: [
              {translate: [-25, 0, -70]},
              {scale: 0.05 },
              {rotateY: -130},
              {rotateX: 20},
              {rotateZ: -10}
            ],
          }}
          source={{obj:asset('earth.obj'), mtl:asset('earth.mtl')}}
          lit={true}
        />
      </View>
    );
  }
};

AppRegistry.registerComponent('EarthMoonVR', () => EarthMoonVR);

Transforms被表示爲一個樣式對象中的對象數組,請記住它們最後被應用的單位是米。

translate將您的模型的位置轉換爲x,y,z空間,scale給您的模型一個大小,並根據提供的度數繞軸旋轉。

這是效果圖:

11-earth-1

這個地球模型能夠應用多個紋理。 默認狀況下它帶 clouds 紋理,可是咱們能夠經過用4096_earth.jpg替換最後三行中的4096_clouds.jpg來更改.mtl文件中的內容:

# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 25.01.2016 02:22:51

newmtl 01___Default
    ...

newmtl 02___Default
    ...
    map_Ka 4096_earth.jpg
    map_Kd 4096_earth.jpg
    map_d 4096_earth.jpg

效果圖以下:

12-earth-2

順便說一下,若是您的模型不帶有.mtl文件,React VR容許您經過下面的代碼指定紋理:

<Model
  source={{obj:asset('model.obj'), texture:asset('model.jpg')}}
  lit={true}
/>

咱們對月球模型作一樣的操做,將紋理的路徑修復到.mtl文件中,並嘗試使用不一樣的比例和放置值。 您不須要添加另外一個光源,AmbientLight將適用於兩種模型。

這是我想出的月球模型的代碼:

render() {
    return (
      <View>

        ...

        <Model
          style={{
            transform: [
              {translate: [10, 10, -100]},
              {scale: 0.05},
            ],
          }}
          source={{obj:asset('moon.obj'), mtl:asset('moon.mtl')}}
          lit={true}
        />
      </View>
    );
  }

效果圖:

13-earth-moon

若是你想在WebVR上下文中瞭解更多關於360度全景圖的信息,你能夠查看the developer documentation at Oculus這篇文章。

如今,咱們一塊兒來爲模型添加動畫。

模型動畫化

React VR 有一個 動畫庫來以簡單的方式組合一些類型的動畫。

在這個時候,只有幾個組件能夠本身動畫(View 使用 Animated.View, Text 使用 Animated.Text, Image 使用 Animated.Image). 這個文檔 提醒你能夠經過createAnimatedComponent來建立更多的動畫,可是目前爲止還找不到更多的相關信息。

另位一種選擇是使用requestAnimationFrame , 它是基於JavaScript動畫API的重要組成部分。

那麼咱們能夠作的就是要有一個狀態屬性來表示兩個模型的Y軸上的旋轉值(在月球模型上,爲了使它變慢讓旋轉是地球旋轉的三分之一):

class EarthMoonVR extends React.Component {
  constructor() {
    super();
    this.state = {
      rotation: 130,
    };
  }

  render() {
    return (
      <View>
        ...

        <Model
          style={{
            transform: [
              {translate: [-25, 0, -70]},
              {scale: 0.05 },
              {rotateY: this.state.rotation},
              {rotateX: 20},
              {rotateZ: -10}
            ],
          }}
          source={{obj:asset('earth.obj'), mtl:asset('earth.mtl')}}
          lit={true}
        />
        
        <Model
          style={{
            transform: [
              {translate: [10, 10, -100]},
              {scale: 0.05},
              {rotateY: this.state.rotation / 3},
            ],
          }}
          source={{obj:asset('moon.obj'), mtl:asset('moon.mtl')}}
          lit={true}
        />
      </View>
    );
  }
};

如今咱們來編寫一個rotate函數,它將經過requestAnimationFrame函數調用每一幀,在必定時間基礎上更新旋轉:

class EarthMoonVR extends React.Component {
  constructor() {
    super();
    this.state = {
      rotation: 130,
    };
    this.lastUpdate = Date.now();

    this.rotate = this.rotate.bind(this);
  }

  rotate() {
    const now = Date.now();
    const delta = now - this.lastUpdate;
    this.lastUpdate = now;

    this.setState({
        rotation: this.state.rotation + delta / 150
    });
    this.frameHandle = requestAnimationFrame(this.rotate);
  }

  ...
}

幻數 150只是控制旋轉速度(這個數字越大,旋轉速度越慢)。 咱們保存由requestAnimationFrame返回的處理程序,以便當組件卸載並啓動componentDidMount上的旋轉動畫時,咱們能夠取消動畫:

class EarthMoonVR extends React.Component {
  constructor() {
    super();
    this.state = {
      rotation: 130,
    };
    this.lastUpdate = Date.now();

    this.rotate = this.rotate.bind(this);
  }

  componentDidMount() {
    this.rotate();
  }

  componentWillUnmount() {
    if (this.frameHandle) {
      cancelAnimationFrame(this.frameHandle);
      this.frameHandle = null;
    }
  }

  rotate() {
    const now = Date.now();
    const delta = now - this.lastUpdate;
    this.lastUpdate = now;

    this.setState({
        rotation: this.state.rotation + delta / 150
    });
    this.frameHandle = requestAnimationFrame(this.rotate);
  }

  ...
}

這是效果圖(你可能沒有注意到,可是月亮旋轉得很慢):

earth moon rotating

如今讓咱們來添加一些button來增長一些交互。

添加button並設置樣式

爲咱們的button建立一個新的組件。在實際開發中,咱們也能使用View 或者VrButton,這倆都能設置像onEnter同樣的有效的事件來達到咱們的目的。

然而,咱們將使用VrButton,由於它有和其餘組件不同的狀態機,而且很方便的添加onClickonLongClick事件。

同時,咱們爲了讓button外觀更好看一些,咱們將使用StyleSheet 來建立一個樣式對象,並經過一個樣式ID來對button進行引用。

下面是button.js的內容:

import React from 'react';
import {
  StyleSheet,
  Text,
  VrButton,
} from 'react-vr';

export default class Button extends React.Component {
  constructor() {
    super();
    this.styles = StyleSheet.create({
      button: {
        margin: 0.05,
        height: 0.4,
        backgroundColor: 'red',
      },
      text: {
        fontSize: 0.3,
        textAlign: 'center',
      },
    });
  }

  render() {
    return (
      <VrButton style={this.styles.button}
        onClick={() => this.props.callback()}>
        <Text style={this.styles.text}>
          {this.props.text}
        </Text>
      </VrButton>
    );
  }
}

一個VrButton沒有外觀效果,所以咱們必須給它添加樣式。它也能夠包裝一個ImageText組件。當點擊這個button時,咱們能夠給它傳遞一個事件函數來接收點擊事件。

如今在咱們的根組件中,咱們倒入Button 組件而且在render函數中,以下所示添加兩個Button。

...
import Button from './button.js';

class EarthMoonVR extends React.Component {
  ...

  render() {
    return (
      <View>
        ...

        <AmbientLight intensity={ 2.6 }  />

        <View>
          <Button text='+'  />
          <Button text='-'  />
        </View>

        ...
      </View>
    );
  }
};

這兩個Button在被觸發時將會改變模型的Z座標值並進行相應的縮放。所以,咱們添加一個zoom狀態機變量值,讓它的初始值爲-70(地球的Z軸值),當咱們點擊+-時會增長減小 zoom的值。

class EarthMoonVR extends React.Component {
  constructor() {
    super();
    this.state = {
      rotation: 130,
      zoom: -70,
    };
    ...
  }

  render() {
    return (
      <View>

       ...

        <View>
          <Button text='+'
            callback={() => this.setState((prevState) => ({ zoom: prevState.zoom + 10 }) ) } />
          <Button text='-' 
            callback={() => this.setState((prevState) => ({ zoom: prevState.zoom - 10 }) ) } />
        </View>

        <Model
          style={{
            transform: [
              {translate: [-25, 0, this.state.zoom]},
              {scale: 0.05 },
              {rotateY: this.state.rotation},
              {rotateX: 20},
              {rotateZ: -10}
            ],
          }}
          source={{obj:asset('earth.obj'), mtl:asset('earth.mtl')}}
          lit={true}
        />
        
        <Model
          style={{
            transform: [
              {translate: [10, 10, this.state.zoom - 30]},
              {scale: 0.05},
              {rotateY: this.state.rotation / 3},
            ],
          }}
          source={{obj:asset('moon.obj'), mtl:asset('moon.mtl')}}
          lit={true}
        />
      </View>
    );
  }
};

如今咱們經過StyleSheet.create來給包含兩個Button的View添加flexbox佈局樣式。

class EarthMoonVR extends React.Component {
  constructor() {
    super();
    ...
    this.styles = StyleSheet.create({
      menu: {
        flex: 1,
        flexDirection: 'column',
        width: 1,
        alignItems: 'stretch',
        transform: [{translate: [2, 2, -5]}],
      },
    });
    ...
  }

  render() {
    return (
      <View>
        ...

        <View style={ this.styles.menu }>
          <Button text='+'
            callback={() => this.setState((prevState) => ({ zoom: prevState.zoom + 10 }) ) } />
          <Button text='-' 
            callback={() => this.setState((prevState) => ({ zoom: prevState.zoom - 10 }) ) } />
        </View>

        ...

      </View>
    );
  }
};

在flexbox佈局中,子組件會經過flexDirection:'column'屬性值垂直佈局,經過flexDirection:'row'屬性值水平佈局。在這個案例中,flex設置爲1表明兩個button大小同樣,flexDirection設置爲column表明兩個button從上往下排列。alignItems的值爲stretch表明兩個Button和父視圖寬度同樣。

看看 this page on the React Native documentationthis one on the React VR documentation 這兩篇文章來了解更多關於flexbox佈局的相關知識。

最後,咱們能夠從render函數中將天空盒的圖片移除,以便render中的代碼看起來不至於那麼擁擠:

import React from 'react';
import {
  AppRegistry,
  asset,
  StyleSheet,
  Pano,
  Text,
  View,
  Model,
  AmbientLight,
} from 'react-vr';
import Button from './button.js';

class EarthMoonVR extends React.Component {
  constructor() {
    super();
    this.state = {
      rotation: 130,
      zoom: -70,
    };
    this.lastUpdate = Date.now();
    this.spaceSkymap = [
      '../static_assets/space_right.png',
      '../static_assets/space_left.png',
      '../static_assets/space_up.png',
      '../static_assets/space_down.png',
      '../static_assets/space_back.png',
      '../static_assets/space_front.png'
    ];
    this.styles = StyleSheet.create({
      menu: {
        flex: 1,
        flexDirection: 'column',
        width: 1,
        alignItems: 'stretch',
        transform: [{translate: [2, 2, -5]}],
      },
    });

    this.rotate = this.rotate.bind(this);
  }

  componentDidMount() {
    this.rotate();
  }

  componentWillUnmount() {
    if (this.frameHandle) {
      cancelAnimationFrame(this.frameHandle);
      this.frameHandle = null;
    }
  }

  rotate() {
    const now = Date.now();
    const delta = now - this.lastUpdate;
    this.lastUpdate = now;

    this.setState({
        rotation: this.state.rotation + delta / 150
    });
    this.frameHandle = requestAnimationFrame(this.rotate);
  }

  render() {
    return (
      <View>
        <Pano source={ {uri: this.spaceSkymap} }/>

        <AmbientLight intensity={ 2.6 }  />

        <View style={ this.styles.menu }>
          <Button text='+'
            callback={() => this.setState((prevState) => ({ zoom: prevState.zoom + 10 }) ) } />
          <Button text='-' 
            callback={() => this.setState((prevState) => ({ zoom: prevState.zoom - 10 }) ) } />
        </View>

        <Model
          style={{
            transform: [
              {translate: [-25, 0, this.state.zoom]},
              {scale: 0.05 },
              {rotateY: this.state.rotation},
              {rotateX: 20},
              {rotateZ: -10}
            ],
          }}
          source={{obj:asset('earth.obj'), mtl:asset('earth.mtl')}}
          lit={true}
        />
        
        <Model
          style={{
            transform: [
              {translate: [10, 10, this.state.zoom - 30]},
              {scale: 0.05},
              {rotateY: this.state.rotation / 3},
            ],
          }}
          source={{obj:asset('moon.obj'), mtl:asset('moon.mtl')}}
          lit={true}
        />
      </View>
    );
  }
};

AppRegistry.registerComponent('EarthMoonVR', () => EarthMoonVR);

若是咱們測試這個應用程序,咱們將看到兩個button均觸發了相關事件。

zoom buttons

總結

React Native 是基於React的一個移動端的JavaScrpit庫,而React VR又是基於React Native的適用於虛擬現實的JavaScrpit庫。React VR容許咱們快速方便的建立VR體驗。

有不少相關的社區,如A-FrameReact VR 中文網。若是你想在App中製做360度的VR全景效果,而且若是你瞭解React/React Native,那麼React VR是很是不錯的選擇。

記住,你可以從GitHub下載本篇文章中的源碼。

謝謝閱讀!

社羣品牌:從零到壹全棧部落
定位:尋找共好,共同窗習,持續輸出全棧技術社羣
業界榮譽:IT界的邏輯思惟
文化:輸出是最好的學習方式
官方公衆號:全棧部落
社羣發起人:春哥(從零到壹創始人,交流微信:liyc1215)
技術交流社區:全棧部落BBS
全棧部落完整系列教程:全棧部落完整電子書學習筆記

關注全棧部落官方公衆號,每晚十點接收系列原創技術推送
相關文章
相關標籤/搜索