Slide 1

Slide 1 text

www.swingdev.io React VR workshop Building Cannon Shooter game in React VR with Mirek Ciastek, SwingDev’s Front-end Developer

Slide 2

Slide 2 text

www.swingdev.io Agenda 1. Introduction and recap 2. Project setup and building basic scene 3. Loading models and adding interaction 4. Adding particles and sounds 5. Adding score and weapon types 6. Summary

Slide 3

Slide 3 text

www.swingdev.io Cannon Shooter

Slide 4

Slide 4 text

www.swingdev.io Introduction and recap

Slide 5

Slide 5 text

www.swingdev.io React VR React VR lets you build VR apps using only JavaScript. It uses the same design as React, letting you compose a rich VR world and UI from declarative components.

Slide 6

Slide 6 text

www.swingdev.io It’s more complicated than that Main Thread React VR Runtime React Native WebGL/WebVR OVRUI Three.js WebWorker React VR Application Code React Native React Web Browser Asynchronous Bridge

Slide 7

Slide 7 text

www.swingdev.io • JSX and React API • Props and State • Input handlers - onEnter, onExit, onMove, onInput, onClick • Fonts and Text Core features

Slide 8

Slide 8 text

www.swingdev.io 3D space • OpenGL coordinates • units in meters • rotation in degrees • Y axis points up, Z axis points to user

Slide 9

Slide 9 text

www.swingdev.io React VR is based on React React is a declarative, efficient, and flexible JavaScript library for building user interfaces. • JSX syntax - like HTML in JavaScript • component oriented - everything is a component • props are for passing data to component • state allows to update component • React is only for views - the rest you need to add by your own

Slide 10

Slide 10 text

www.swingdev.io class ShoppingList extends React.Component { render() { return (

Shopping List for {this.props.name}

  • Instagram
  • WhatsApp
  • Oculus
); } } React example

Slide 11

Slide 11 text

www.swingdev.io hello React VR example

Slide 12

Slide 12 text

www.swingdev.io Project setup and building basic scene

Slide 13

Slide 13 text

www.swingdev.io Requirements • Node.js • NPM or Yarn • React VR CLI • Git (version control) or SourceTree

Slide 14

Slide 14 text

www.swingdev.io https://github.com/SwingDev/reactvr-workshop or short version https://bit.ly/2qzdp5k

Slide 15

Slide 15 text

www.swingdev.io Setup project git clone [email protected]:SwingDev/reactvr-workshop.git Clone repository cd reactvr-workshop && git checkout stage_1 Go to project directory and checkout to start branch yarn install && yarn start Run the app Go to http://localhost:8081/vr?hotreload

Slide 16

Slide 16 text

www.swingdev.io Project structure !"" /actions !"" /components !"" /libs !"" /modules !"" /reducers !"" /static_assets !"" /store !"" /utils !"" /views !"" /vr #"" index.vr.js #"" /components !"" App.js !"" Cannon.js !"" Ground.js !"" HUD.js !"" Sky.js !"" Summary.js #"" Wall.js #"" /views !"" /CustomModel #"" /ParticlePool

Slide 17

Slide 17 text

www.swingdev.io Let’s make Sky const vertexShader = ` varying vec3 vWorldPosition; void main() { vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); vWorldPosition = worldPosition.xyz; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); } `; const fragmentShader = ` uniform vec3 topColor; uniform vec3 bottomColor; uniform float offset; uniform float exponent; varying vec3 vWorldPosition; void main() { float h = normalize( vWorldPosition + offset ).y; gl_FragColor = vec4( mix( bottomColor, topColor, max( pow( max( h , 0.0), exponent ), 0.0 ) ), 1.0 ); } `; Sky.js

Slide 18

Slide 18 text

www.swingdev.io Let’s make Sky const Sky = () => ( ); const uniforms = { topColor: { value: new THREE.Color(0x0077ff), }, bottomColor: { value: new THREE.Color(0xffffff), }, offset: { value: Y_OFFSET, }, exponent: { value: 0.6, }, }; Sky.js

Slide 19

Slide 19 text

www.swingdev.io Ground const Ground = () => ( ); Ground.js

Slide 20

Slide 20 text

www.swingdev.io Add Sky and Ground to App.js import Sky from './Sky'; import Ground from './Ground'; class App extends React.Component { render() { return ( ); } } App.js

Slide 21

Slide 21 text

www.swingdev.io Add lights to scene import { View, AmbientLight, DirectionalLight, } from 'react-vr'; class App extends React.Component { render() { return ( ); } } App.js

Slide 22

Slide 22 text

www.swingdev.io Loading models and adding interaction

Slide 23

Slide 23 text

www.swingdev.io glTF glTF (GL Transmission Format) is a file format for 3D scenes and models using the JSON standard. It is described by its creators as the "JPEG of 3D."

Slide 24

Slide 24 text

www.swingdev.io How glTF look like

Slide 25

Slide 25 text

www.swingdev.io Create Cannon component render() { return ( ); } import { CustomModel } from '../views/CustomModel/component'; Cannon.js

Slide 26

Slide 26 text

www.swingdev.io Make Wall of boxes renderBox = (box) => { const { id, x, y, file, } = box; return ( ); } render() { const { boxes } = this.state; return ( {boxes.map(this.renderBox)} ); } Wall.js

Slide 27

Slide 27 text

www.swingdev.io Add Cannon and Wall to App.js class App extends React.Component { render() { return ( ); } }

Slide 28

Slide 28 text

www.swingdev.io Adding click listener to boxes this.handleHit(box)} > {/* Custom model component */} handleHit = (box) => { const { id, x, y } = box; const { boxes } = this.state; const { updatedBoxes, boxesToRemove, } = getUpdatedBoxes(boxes, id, weapon); this.setState({ boxes: updatedBoxes, }, () => this.afterHitUpdate(boxesToRemove)); }; afterHitUpdate = (boxesToRemove) => { const { boxes } = this.state; this.props.addHit( getPoints(boxesToRemove), boxesToRemove.length, ); }; Wall.js

Slide 29

Slide 29 text

www.swingdev.io Link Cannon’s head to camera componentDidMount() { this.rotateListener = RCTDeviceEventEmitter.addListener( 'onReceivedHeadMatrix', this.handleReceiveMatrix, ); } componentWillUnmount() { this.rotateListener(); } handleReceiveMatrix = () => { const [x, y] = VrHeadModel.rotation(); this.setState({ headRotateX: getClamped(x), headRotateY: getClamped(y), }); }; Cannon.js

Slide 30

Slide 30 text

www.swingdev.io Update Cannon’s render function render() { const { headRotateX, headRotateY } = this.state; return ( ); }

Slide 31

Slide 31 text

www.swingdev.io Adding particles and sounds

Slide 32

Slide 32 text

www.swingdev.io Show particles on box’s hit Wall.js state = { boxes: getBoxesProps(), explosionPosition: new THREE.Vector3(0, 0, -5), showExplosion: false, }; handleHit = (box) => { const { id, x, y } = box; const { boxes, explosionPosition } = this.state; NativeModules.ShotBridge.emitShot(); const { updatedBoxes, boxesToRemove, } = getUpdatedBoxes(boxes, id); this.setState({ boxes: updatedBoxes, explosionPosition: explosionPosition.clone().set(x, y, -5), showExplosion: true, }, () => this.afterHitUpdate(boxesToRemove)); }; afterHitUpdate = (boxesToRemove) => { this.props.addHit( getPoints(boxesToRemove), boxesToRemove.length, ); this.setState({ showExplosion: false, }); };

Slide 33

Slide 33 text

www.swingdev.io Add particles to Wall’s boxes Wall.js import { ParticlePool } from '../views/ParticlePool/component'; render() { const { boxes, explosionPosition, showExplosion } = this.state; return ( {boxes.map(this.renderBox)} ); }

Slide 34

Slide 34 text

www.swingdev.io Update Cannon’s state on shot Cannon.js state = { headRotateX: 0, headRotateY: 0, showSmoke: false, }; componentDidMount() { this.rotateListener = RCTDeviceEventEmitter.addListener( 'onReceivedHeadMatrix', this.handleReceiveMatrix, ); this.shotListener = RCTDeviceEventEmitter.addListener( 'shot', this.handleShot, ); } componentWillUnmount() { this.rotateListener(); this.shotListener(); } handleShot = () => { this.setState({ showSmoke: true, }, () => { this.setState({ showSmoke: false, }); }); };

Slide 35

Slide 35 text

www.swingdev.io Add particles to Cannon

Slide 36

Slide 36 text

www.swingdev.io Add sound to Wall’s component Wall.js state = { boxes: getBoxesProps(), soundPlayState: 'pause', explosionPosition: new THREE.Vector3(0, 0, -5), showExplosion: false, }; render() { // start of render() return ( boxes.map(this.renderBox) {/* rest of the component */} ); }

Slide 37

Slide 37 text

www.swingdev.io Play sound on hit Wall.js handleSoundEnd = () => { this.setState({ soundPlayState: 'stop', }); }; handleHit = (box, weapon = weapons.CANNONBALL) => { const { id, x, y } = box; const { boxes, explosionPosition } = this.state; NativeModules.ShotBridge.emitShot(); const { updatedBoxes, boxesToRemove, } = getUpdatedBoxes(boxes, id, weapon); this.setState({ boxes: updatedBoxes, soundPlayState: 'play', explosionPosition: explosionPosition.clone().set(x, y, -5), showExplosion: true, }, () => this.afterHitUpdate(boxesToRemove)); };

Slide 38

Slide 38 text

www.swingdev.io Add sound to Cannon’s component Cannon.js state = { headRotateX: 0, headRotateY: 0, soundPlayState: 'pause', showSmoke: false, }; handleSoundEnd = () => { this.setState({ soundPlayState: 'stop', }); }; handleShot = () => { this.setState({ soundPlayState: 'play', showSmoke: true, }, () => { this.setState({ showSmoke: false, }); }); }; render() { const { headRotateX, headRotateY, showSmoke, soundPlayState, } = this.state; return ( {/* start of the component */} ); }

Slide 39

Slide 39 text

www.swingdev.io Adding score and weapon types

Slide 40

Slide 40 text

www.swingdev.io Make component for game summary Summary.js class Summary extends React.Component { render() { const { score, hits, time } = this.props.player; return ( Your points: {score} Your hits: {hits} Your time: {getFormattedTime(time)} this.props.updateFinishedStatus(false)}> Restart game ); } }

Slide 41

Slide 41 text

www.swingdev.io Create game’s HUD HUD.js class HUD extends React.Component { render() { const { score } = this.props.player; return ( {score} ); } }

Slide 42

Slide 42 text

www.swingdev.io Show Summary when game ends Wall.js render() { const { hasFinished } = this.props.player; const { boxes, explosionPosition, showExplosion, soundPlayState, } = this.state; return ( {(!hasFinished) ? ( boxes.map(this.renderBox) ) : ( )} {/* rest of the component */} ); } finishGame() { this.props.updateFinishedStatus(true); this.setState({ boxes: getBoxesProps(), soundPlayState: 'pause', }); } afterHitUpdate = (boxesToRemove) => { const { boxes } = this.state; this.props.addHit( getPoints(boxesToRemove), boxesToRemove.length, ); this.setState({ showExplosion: false, }); if (boxes.length === 0) { this.finishGame(); } }; import Summary from './Summary';

Slide 43

Slide 43 text

www.swingdev.io Attach HUD to Cannon’s head Cannon.js render() { // start of render() return ( {/* rest of the Cannon’s head */} {/* rest of the component */} ); } import HUD from './HUD';

Slide 44

Slide 44 text

www.swingdev.io Handle weapon types Wall.js renderBox = (box) => { const { id, x, y, file, } = box; return ( this.handleHit(box)} onLongClick={() => this.handleHit(box, weapons.ROCKET)} longClickDelayMS={SPECIAL_WEAPON_DELAY} > ); } handleHit = (box, weapon = weapons.CANNONBALL) => { const { id, x, y } = box; const { boxes, explosionPosition } = this.state; NativeModules.ShotBridge.emitShot(); const { updatedBoxes, boxesToRemove, } = getUpdatedBoxes(boxes, id, weapon); this.setState({ boxes: updatedBoxes, soundPlayState: 'play', explosionPosition: explosionPosition.clone().set(x, y, -5), showExplosion: true, }, () => this.afterHitUpdate(boxesToRemove)); };

Slide 45

Slide 45 text

www.swingdev.io

Slide 46

Slide 46 text

www.swingdev.io Questions?

Slide 47

Slide 47 text

www.swingdev.io Further reading • Getting started with React VR • Oculus Developer Center • Prototyping with React VR • Getting Started with WebVR • Bring Virtual Reality to Your Browser with React VR • Learn React VR (Chapter 8 | Building a VR Video App) • glTF Overview

Slide 48

Slide 48 text

www.swingdev.io https://twitter.com/mciastek https://medium.com/@mciastek https://github.com/mciastek #webvrmeetup Where to find me:

Slide 49

Slide 49 text

www.swingdev.io Thank you