├── .gitattributes ├── .gitignore ├── .watchmanconfig ├── App.android.js ├── App.js ├── LICENSE ├── README.md ├── __tests__ ├── index.android.js └── index.ios.js ├── app.json ├── app ├── examples │ ├── game-engine │ │ ├── index.js │ │ ├── renderers.js │ │ └── systems.js │ ├── index.js │ └── override-renderer │ │ ├── index.js │ │ ├── renderers.js │ │ └── systems.js ├── opengl │ ├── game-of-life │ │ ├── index.js │ │ ├── renderers.js │ │ └── systems.js │ ├── index.js │ ├── lighting │ │ ├── index.js │ │ ├── renderers.js │ │ └── systems.js │ ├── motion-blur │ │ ├── index.js │ │ ├── renderers.js │ │ └── systems.js │ └── stanford-bunny │ │ ├── index.js │ │ ├── renderers.js │ │ └── systems.js ├── physics │ ├── index.js │ └── rigid-bodies │ │ ├── index.js │ │ ├── renderers.js │ │ └── systems.js ├── sensors │ ├── accelerometer │ │ ├── index.js │ │ ├── renderers.js │ │ └── systems.js │ └── index.js ├── table-of-contents │ ├── backButton.js │ ├── closeButton.js │ ├── heading.js │ ├── images │ │ ├── back@1x.png │ │ ├── back@2x.png │ │ ├── back@3x.png │ │ ├── back@4x.png │ │ ├── close@1x.png │ │ ├── close@2x.png │ │ ├── close@3x.png │ │ ├── close@4x.png │ │ ├── logo-alt@1x.png │ │ ├── logo-alt@2x.png │ │ ├── logo-alt@3x.png │ │ ├── logo-alt@4x.png │ │ ├── logo@1x.png │ │ ├── logo@2x.png │ │ ├── logo@3x.png │ │ └── logo@4x.png │ ├── index.js │ ├── item.js │ ├── renderers.js │ ├── systems.js │ └── title.js └── touch-events │ ├── index.js │ ├── multi-touch │ ├── index.js │ ├── renderers.js │ └── systems.js │ └── single-touch │ ├── index.js │ └── worm.js ├── assets ├── cannon-ball-sea-1.png ├── cannon-ball-sea-2.png ├── cannon-ball-sea-3.png ├── cannon-ball-sea-4.png ├── cannon-ball-sea-5.png ├── icon-and-splash.sketch ├── icon.png ├── multi-touch.gif ├── rigid-bodies.gif ├── single-touch.gif └── splash.png ├── babel.config.js ├── package-lock.json └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p12 6 | *.key 7 | *.mobileprovision 8 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /App.android.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View, Modal } from "react-native"; 3 | import CloseButton from "./app/table-of-contents/closeButton"; 4 | import EStyleSheet from "react-native-extended-stylesheet"; 5 | 6 | import TableOfContents from "./app/table-of-contents"; 7 | import TouchChapter from "./app/touch-events"; 8 | import PhysicsChapter from "./app/physics"; 9 | //import SensorsChapter from "./app/sensors"; 10 | import ExamplesChapter from "./app/examples"; 11 | import OpenGLChapter from "./app/opengl"; 12 | 13 | EStyleSheet.build(); 14 | 15 | //-- There is a bunch of warnings about the use of deprecated lifecycle methods. A lot of them are caused 16 | //-- by dependencies. Comment out the line below to see the warnings. 17 | console.disableYellowBox = true; 18 | 19 | export default class App extends Component { 20 | constructor(props) { 21 | super(props); 22 | this.state = { 23 | sceneVisible: false, 24 | scene: null 25 | }; 26 | } 27 | 28 | mountScene = scene => { 29 | this.setState({ 30 | sceneVisible: true, 31 | scene: scene 32 | }); 33 | }; 34 | 35 | unMountScene = () => { 36 | this.setState({ 37 | sceneVisible: false, 38 | scene: null 39 | }); 40 | }; 41 | 42 | render() { 43 | return ( 44 | 45 | 58 | {}} 63 | > 64 | {this.state.scene} 65 | 66 | 67 | 68 | 69 | ); 70 | } 71 | } -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View, Modal } from "react-native"; 3 | import CloseButton from "./app/table-of-contents/closeButton"; 4 | import EStyleSheet from "react-native-extended-stylesheet"; 5 | import { GestureHandlerRootView } from 'react-native-gesture-handler' 6 | 7 | import TableOfContents from "./app/table-of-contents"; 8 | import TouchChapter from "./app/touch-events"; 9 | import PhysicsChapter from "./app/physics"; 10 | ////import SensorsChapter from "./app/sensors"; 11 | import ExamplesChapter from "./app/examples"; 12 | import OpenGLChapter from "./app/opengl"; 13 | 14 | EStyleSheet.build(); 15 | 16 | //-- There is a bunch of warnings about the use of deprecated lifecycle methods. A lot of them are caused 17 | //-- by dependencies. Comment out the line below to see the warnings. 18 | console.disableYellowBox = true; 19 | 20 | export default class App extends Component { 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | sceneVisible: false, 25 | scene: null 26 | }; 27 | } 28 | 29 | mountScene = scene => { 30 | this.setState({ 31 | sceneVisible: true, 32 | scene: scene 33 | }); 34 | }; 35 | 36 | unMountScene = () => { 37 | this.setState({ 38 | sceneVisible: false, 39 | scene: null 40 | }); 41 | }; 42 | 43 | render() { 44 | return ( 45 | 46 | 47 | 60 | {}} 65 | > 66 | {this.state.scene} 67 | 68 | 69 | 70 | 71 | 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Boris Berak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | React Native Game Engine Handbook 3 |

4 | 5 | # React Native Game Engine Handbook · [![mit license](https://img.shields.io/badge/license-MIT-50CB22.svg)](https://opensource.org/licenses/MIT) 6 | 7 | A React Native app showcasing some examples using the lightweight [react-native-game-engine](https://github.com/bberak/react-native-game-engine) library. 8 | 9 |

10 | Single Touch Preview 11 | Multi Touch Preview 12 | Rigid Bodies Preview 13 |

14 | 15 | ## Quick Start Expo (iOS and Android) 16 | 17 | ``` 18 | npm install -g expo-cli 19 | 20 | git clone https://github.com/bberak/react-native-game-engine-handbook.git 21 | 22 | cd react-native-game-engine-handbook 23 | 24 | npm install 25 | 26 | npm run start 27 | 28 | # Then follow the Expo prompts to run on a device or simulator 29 | ``` 30 | 31 | > For an even quicker start, [download the Expo Go app](https://expo.dev/client) and scan this [QR code with your mobile device](https://expo.dev/@bberak/react-native-game-engine-handbook). 32 | 33 | ## Pull Often! 34 | 35 | The Handbook app will be updated with new examples and content from time to time - so make sure you do a ```git pull``` every now and then to get the latest and greatest. 36 | 37 | ## Contribute 38 | 39 | If you've created an interesting example, scene or even want to contribute (or start) a chapter - please get in touch and we'll get your ideas merged into the master branch. 40 | 41 | ## License 42 | 43 | MIT License 44 | 45 | Copyright (c) 2017 Boris Berak 46 | 47 | Permission is hereby granted, free of charge, to any person obtaining a copy 48 | of this software and associated documentation files (the "Software"), to deal 49 | in the Software without restriction, including without limitation the rights 50 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 51 | copies of the Software, and to permit persons to whom the Software is 52 | furnished to do so, subject to the following conditions: 53 | 54 | The above copyright notice and this permission notice shall be included in all 55 | copies or substantial portions of the Software. 56 | 57 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 58 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 59 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 60 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 61 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 62 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 63 | SOFTWARE. 64 | -------------------------------------------------------------------------------- /__tests__/index.android.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.android.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /__tests__/index.ios.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.ios.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "React Native Game Engine - Handbook", 4 | "description": "A React Native app showcasing some examples using the lightweight https://github.com/bberak/react-native-game-engine library.", 5 | "slug": "react-native-game-engine-handbook", 6 | "privacy": "public", 7 | "platforms": [ 8 | "ios", 9 | "android" 10 | ], 11 | "version": "1.0.0", 12 | "orientation": "portrait", 13 | "primaryColor": "#E96443", 14 | "icon": "./assets/icon.png", 15 | "splash": { 16 | "image": "./assets/splash.png", 17 | "resizeMode": "contain", 18 | "backgroundColor": "#904E95" 19 | }, 20 | "updates": { 21 | "fallbackToCacheTimeout": 0 22 | }, 23 | "assetBundlePatterns": [ 24 | "**/*" 25 | ], 26 | "ios": { 27 | "supportsTablet": true 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/examples/game-engine/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { AppRegistry, StyleSheet, StatusBar } from "react-native"; 3 | import { GameEngine } from "react-native-game-engine"; 4 | import { Finger } from "./renderers"; 5 | import { MoveFinger } from "./systems" 6 | 7 | export default class GameEngineExample extends PureComponent { 8 | constructor() { 9 | super(); 10 | } 11 | 12 | render() { 13 | return ( 14 | }, 19 | 2: { position: [100, 200], renderer: }, 20 | 3: { position: [160, 200], renderer: }, 21 | 4: { position: [220, 200], renderer: }, 22 | 5: { position: [280, 200], renderer: } 23 | }}> 24 | 25 | 28 | ); 29 | } 30 | } 31 | 32 | const styles = StyleSheet.create({ 33 | container: { 34 | flex: 1, 35 | backgroundColor: "#FFF" 36 | } 37 | }); -------------------------------------------------------------------------------- /app/examples/game-engine/renderers.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { StyleSheet, View } from "react-native"; 3 | 4 | const RADIUS = 20; 5 | 6 | class Finger extends PureComponent { 7 | render() { 8 | const x = this.props.position[0] - RADIUS / 2; 9 | const y = this.props.position[1] - RADIUS / 2; 10 | return ( 11 | 12 | ); 13 | } 14 | } 15 | 16 | const styles = StyleSheet.create({ 17 | finger: { 18 | borderColor: "#CCC", 19 | borderWidth: 4, 20 | borderRadius: RADIUS * 2, 21 | width: RADIUS * 2, 22 | height: RADIUS * 2, 23 | backgroundColor: "pink", 24 | position: "absolute" 25 | } 26 | }); 27 | 28 | export { Finger }; -------------------------------------------------------------------------------- /app/examples/game-engine/systems.js: -------------------------------------------------------------------------------- 1 | const MoveFinger = (entities, { touches }) => { 2 | 3 | //-- I'm choosing to update the game state (entities) directly for the sake of brevity and simplicity. 4 | //-- There's nothing stopping you from treating the game state as immutable and returning a copy.. 5 | //-- Example: return { ...entities, t.id: { UPDATED COMPONENTS }}; 6 | 7 | touches.filter(t => t.type === "move").forEach(t => { 8 | let finger = entities[t.id]; 9 | if (finger && finger.position) { 10 | finger.position = [ 11 | finger.position[0] + t.delta.pageX, 12 | finger.position[1] + t.delta.pageY 13 | ]; 14 | } 15 | }); 16 | 17 | return entities; 18 | }; 19 | 20 | export { MoveFinger }; -------------------------------------------------------------------------------- /app/examples/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import GameEngineExample from "./game-engine"; 3 | import OverrideRendererExample from "./override-renderer"; 4 | import DonkeyKong from "react-native-donkey-kong"; 5 | 6 | export default function (mount) { 7 | return { 8 | heading: "Examples", 9 | items: [ 10 | { 11 | heading: "Game Engine", 12 | onPress: _ => mount() 13 | }, 14 | { 15 | heading: "Override Renderer", 16 | onPress: _ => mount() 17 | }, 18 | { 19 | heading: "DonkeyKong", 20 | onPress: _ => mount() 21 | } 22 | ] 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /app/examples/override-renderer/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { AppRegistry, StyleSheet, StatusBar } from "react-native"; 3 | import { GameEngine } from "react-native-game-engine"; 4 | import { WebglRenderer, Bunny } from "./renderers"; 5 | import { Rotate } from "./systems"; 6 | 7 | export default class OverrideRendererExample extends PureComponent { 8 | constructor() { 9 | super(); 10 | } 11 | 12 | render() { 13 | return ( 14 | 22 | 24 | ); 25 | } 26 | } 27 | 28 | const styles = StyleSheet.create({ 29 | container: { 30 | flex: 1, 31 | backgroundColor: "#FFF" 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /app/examples/override-renderer/renderers.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { StyleSheet, View, Text } from "react-native"; 3 | import { GLView } from "expo-gl"; 4 | import REGL from "regl"; 5 | import mat4 from "gl-mat4"; 6 | import bunny from "bunny"; 7 | import _ from "lodash"; 8 | 9 | class ReglView extends PureComponent { 10 | constructor() { 11 | super(); 12 | this.state = {}; 13 | } 14 | 15 | onContextCreate = gl => { 16 | const regl = REGL(gl); 17 | const rngl = gl.getExtension("RN"); 18 | 19 | this.setState({ 20 | frame: props => { 21 | 22 | regl.clear({ 23 | depth: 1, 24 | color: [0, 0, 0, 1] 25 | }); 26 | 27 | if (this.props.children) 28 | this.props.children.forEach(entity => entity.renderer(regl)(entity)); 29 | 30 | gl.endFrameEXP(); 31 | } 32 | }); 33 | }; 34 | 35 | render() { 36 | if (this.state.frame) this.state.frame(this.props); 37 | 38 | return ( 39 | 43 | ); 44 | } 45 | } 46 | 47 | const WebglRenderer = (state, screen) => { 48 | if (!state || !screen) 49 | return null; 50 | 51 | return ( 52 | 53 | {Object.keys(state) 54 | .filter(key => state[key].renderer) 55 | .map(key => state[key])} 56 | 57 | ); 58 | }; 59 | 60 | const Bunny = _.memoize(regl => { 61 | return regl({ 62 | vert: ` 63 | precision mediump float; 64 | attribute vec3 position; 65 | uniform mat4 model, view, projection; 66 | void main() { 67 | gl_Position = projection * view * model * vec4(position, 1); 68 | }`, 69 | 70 | frag: ` 71 | precision mediump float; 72 | void main() { 73 | gl_FragColor = vec4(1, 1, 1, 1); 74 | }`, 75 | 76 | // this converts the vertices of the mesh into the position attribute 77 | attributes: { 78 | position: bunny.positions 79 | }, 80 | 81 | // and this converts the faces of the mesh into elements 82 | elements: bunny.cells, 83 | 84 | uniforms: { 85 | model: mat4.identity([]), 86 | view: (_, { angle }) => { 87 | return mat4.lookAt( 88 | [], 89 | [30 * Math.cos(angle), 2.5, 30 * Math.sin(angle)], 90 | [0, 2.5, 0], 91 | [0, 1, 0] 92 | ); 93 | }, 94 | projection: ({ viewportWidth, viewportHeight }) => 95 | mat4.perspective( 96 | [], 97 | Math.PI / 4, 98 | viewportWidth / viewportHeight, 99 | 0.01, 100 | 1000 101 | ) 102 | } 103 | }); 104 | }); 105 | 106 | export { Bunny, WebglRenderer }; 107 | -------------------------------------------------------------------------------- /app/examples/override-renderer/systems.js: -------------------------------------------------------------------------------- 1 | const Rotate = (entities, { touches }) => { 2 | let move = touches.find(x => x.type === "move"); 3 | 4 | if (move) 5 | entities.bunny.angle += move.delta.pageX * 0.01; 6 | 7 | return entities; 8 | } 9 | 10 | export { Rotate }; -------------------------------------------------------------------------------- /app/opengl/game-of-life/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { AppRegistry, StyleSheet, StatusBar } from "react-native"; 3 | import { GameEngine } from "react-native-game-engine"; 4 | import { GameOfLife } from "./renderers"; 5 | 6 | export default class GameOfLifeExample extends PureComponent { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | render() { 12 | return ( 13 | } 17 | }}> 18 | 19 | 22 | ); 23 | } 24 | } 25 | 26 | const styles = StyleSheet.create({ 27 | container: { 28 | flex: 1, 29 | backgroundColor: "#FFF" 30 | } 31 | }); -------------------------------------------------------------------------------- /app/opengl/game-of-life/renderers.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, Component } from "react"; 2 | import { StyleSheet, View, Text } from "react-native"; 3 | import { GLView } from "expo-gl"; 4 | import REGL from "regl"; 5 | import mat4 from "gl-mat4"; 6 | import bunny from "bunny"; 7 | 8 | class ReglView extends Component { 9 | constructor() { 10 | super(); 11 | this.state = {}; 12 | } 13 | 14 | onContextCreate = gl => { 15 | const regl = REGL(gl); 16 | const rngl = gl.getExtension("RN"); 17 | const clear = this.props.clearCommand(regl, rngl); 18 | const draw = this.props.drawCommand(regl, rngl); 19 | 20 | this.setState({ 21 | frame: props => { 22 | clear(props); 23 | draw(props); 24 | gl.endFrameEXP(); 25 | } 26 | }); 27 | }; 28 | 29 | render() { 30 | if (this.state.frame) this.state.frame(this.props); 31 | 32 | return ( 33 | 37 | ); 38 | } 39 | } 40 | 41 | class GameOfLife extends Component { 42 | drawCommand = regl => { 43 | const RADIUS = 128; 44 | const INITIAL_CONDITIONS = (Array(RADIUS * RADIUS * 4)).fill(0).map( 45 | () => Math.random() > 0.9 ? 255 : 0); 46 | 47 | const state = (Array(2)).fill().map(() => 48 | regl.framebuffer({ 49 | color: regl.texture({ 50 | radius: RADIUS, 51 | data: INITIAL_CONDITIONS, 52 | wrap: 'repeat' 53 | }), 54 | depthStencil: false 55 | })); 56 | 57 | const updateLife = regl({ 58 | frag: ` 59 | precision mediump float; 60 | uniform sampler2D prevState; 61 | varying vec2 uv; 62 | void main() { 63 | float n = 0.0; 64 | for(int dx=-1; dx<=1; ++dx) 65 | for(int dy=-1; dy<=1; ++dy) { 66 | n += texture2D(prevState, uv+vec2(dx,dy)/float(${RADIUS})).r; 67 | } 68 | float s = texture2D(prevState, uv).r; 69 | if(n > 3.0+s || n < 3.0) { 70 | gl_FragColor = vec4(0,0,0,1); 71 | } else { 72 | gl_FragColor = vec4(1,1,1,1); 73 | } 74 | }`, 75 | 76 | framebuffer: ({tick}) => state[(tick + 1) % 2] 77 | }); 78 | 79 | const setupQuad = regl({ 80 | frag: ` 81 | precision mediump float; 82 | uniform sampler2D prevState; 83 | varying vec2 uv; 84 | void main() { 85 | float state = texture2D(prevState, uv).r; 86 | gl_FragColor = vec4(vec3(state), 1); 87 | }`, 88 | 89 | vert: ` 90 | precision mediump float; 91 | attribute vec2 position; 92 | varying vec2 uv; 93 | void main() { 94 | uv = 0.5 * (position + 1.0); 95 | gl_Position = vec4(position, 0, 1); 96 | }`, 97 | 98 | attributes: { 99 | position: [ -4, -4, 4, -4, 0, 4 ] 100 | }, 101 | 102 | uniforms: { 103 | prevState: ({tick}) => state[tick % 2] 104 | }, 105 | 106 | depth: { enable: false }, 107 | 108 | count: 3 109 | }); 110 | 111 | return props => { 112 | setupQuad(() => { 113 | regl.draw() 114 | updateLife() 115 | }); 116 | } 117 | }; 118 | 119 | clearCommand = regl => { 120 | return props => { 121 | regl.poll(); 122 | regl.clear({ 123 | color: [0, 0, 0, 1], 124 | depth: 1, 125 | }); 126 | }; 127 | }; 128 | 129 | render() { 130 | return ( 131 | 136 | ); 137 | } 138 | } 139 | 140 | export { GameOfLife }; 141 | -------------------------------------------------------------------------------- /app/opengl/game-of-life/systems.js: -------------------------------------------------------------------------------- 1 | const Rotate = (entities, { time }) => { 2 | entities.bunny.angle = time.current * 0.001 3 | 4 | return entities; 5 | } 6 | 7 | export { Rotate }; -------------------------------------------------------------------------------- /app/opengl/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import MotionBlurExample from "./motion-blur"; 3 | import StanfordBunnyExmaple from "./stanford-bunny"; 4 | import GameOfLifeExample from "./game-of-life"; 5 | import OverrideRendererExample from "../examples/override-renderer"; 6 | import LightingExample from "./lighting" 7 | 8 | export default function (mount) { 9 | return { 10 | heading: "OpenGL", 11 | items: [ 12 | { 13 | heading: "Motion Blur", 14 | onPress: _ => mount() 15 | }, 16 | { 17 | heading: "Stanford Bunny", 18 | onPress: _ => mount() 19 | }, 20 | { 21 | heading: "Lighting", 22 | onPress: _ => mount() 23 | }, 24 | { 25 | heading: "Game of Life", 26 | onPress: _ => mount() 27 | }, 28 | { 29 | heading: "OpenGL Renderer", 30 | onPress: _ => mount() 31 | } 32 | ] 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /app/opengl/lighting/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { AppRegistry, StyleSheet, StatusBar } from "react-native"; 3 | import { GameEngine } from "react-native-game-engine"; 4 | import { Bunny } from "./renderers"; 5 | import { Rotate } from "./systems" 6 | 7 | export default class LightingExample extends PureComponent { 8 | constructor() { 9 | super(); 10 | } 11 | 12 | render() { 13 | return ( 14 | } 19 | }}> 20 | 21 | 24 | ); 25 | } 26 | } 27 | 28 | const styles = StyleSheet.create({ 29 | container: { 30 | flex: 1, 31 | backgroundColor: "#FFF" 32 | } 33 | }); -------------------------------------------------------------------------------- /app/opengl/lighting/renderers.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { StyleSheet, View, Text } from "react-native"; 3 | import { GLView } from "expo-gl"; 4 | import REGL from "regl"; 5 | import mat4 from "gl-mat4"; 6 | import bunny from "bunny"; 7 | import normals from "angle-normals"; 8 | 9 | const t = 200; 10 | 11 | class ReglView extends PureComponent { 12 | constructor() { 13 | super(); 14 | this.state = {}; 15 | } 16 | 17 | onContextCreate = gl => { 18 | const regl = REGL(gl); 19 | const rngl = gl.getExtension("RN"); 20 | const clear = this.props.clearCommand(regl, rngl); 21 | const draw = this.props.drawCommand(regl, rngl); 22 | 23 | this.setState({ 24 | frame: props => { 25 | clear(props); 26 | draw(props); 27 | gl.endFrameEXP(); 28 | } 29 | }); 30 | }; 31 | 32 | render() { 33 | if (this.state.frame) this.state.frame(this.props); 34 | 35 | return ( 36 | 40 | ); 41 | } 42 | } 43 | 44 | class Bunny extends PureComponent { 45 | drawCommand = regl => { 46 | return regl({ 47 | vert: ` 48 | precision mediump float; 49 | attribute vec3 position, normal; 50 | uniform mat4 model, view, projection; 51 | varying vec3 fragNormal, fragPosition; 52 | void main() { 53 | fragNormal = normal; 54 | fragPosition = position; 55 | gl_Position = projection * view * model * vec4(position, 1); 56 | }`, 57 | 58 | frag: ` 59 | precision mediump float; 60 | struct Light { 61 | vec3 color; 62 | vec3 position; 63 | }; 64 | uniform Light lights[4]; 65 | varying vec3 fragNormal, fragPosition; 66 | void main() { 67 | vec3 normal = normalize(fragNormal); 68 | vec3 light = vec3(0, 0, 0); 69 | for (int i = 0; i < 4; ++i) { 70 | vec3 lightDir = normalize(lights[i].position - fragPosition); 71 | float diffuse = max(0.0, dot(lightDir, normal)); 72 | light += diffuse * lights[i].color; 73 | } 74 | gl_FragColor = vec4(light, 1); 75 | }`, 76 | 77 | attributes: { 78 | position: bunny.positions, 79 | normal: normals(bunny.cells, bunny.positions) 80 | }, 81 | 82 | elements: bunny.cells, 83 | 84 | uniforms: { 85 | model: (_, { yaw, pitch }) => { 86 | return mat4.translate( 87 | [], 88 | mat4.rotateY([], mat4.rotateX([], mat4.identity([]), pitch), yaw), 89 | [0, -2.5, 0] 90 | ); 91 | }, 92 | view: mat4.lookAt([], [0, 0, 30], [0, 0, 0], [0, 1, 0]), 93 | projection: ({ viewportWidth, viewportHeight }) => 94 | mat4.perspective( 95 | [], 96 | Math.PI / 4, 97 | viewportWidth / viewportHeight, 98 | 0.01, 99 | 1000 100 | ), 101 | "lights[0].color": [1, 0, 0], 102 | "lights[1].color": [0, 1, 0], 103 | "lights[2].color": [0, 0, 1], 104 | "lights[3].color": [1, 1, 0], 105 | "lights[0].position": [ 106 | 10 * Math.cos(0.09 * t), 107 | 10 * Math.sin(0.09 * (2 * t)), 108 | 10 * Math.cos(0.09 * (3 * t)) 109 | ], 110 | "lights[1].position": [ 111 | 10 * Math.cos(0.05 * (5 * t + 1)), 112 | 10 * Math.sin(0.05 * (4 * t)), 113 | 10 * Math.cos(0.05 * (0.1 * t)) 114 | ], 115 | "lights[2].position": [ 116 | 10 * Math.cos(0.05 * (9 * t)), 117 | 10 * Math.sin(0.05 * (0.25 * t)), 118 | 10 * Math.cos(0.05 * (4 * t)) 119 | ], 120 | "lights[3].position": [ 121 | 10 * Math.cos(0.1 * (0.3 * t)), 122 | 10 * Math.sin(0.1 * (2.1 * t)), 123 | 10 * Math.cos(0.1 * (1.3 * t)) 124 | ] 125 | } 126 | }); 127 | }; 128 | 129 | clearCommand = regl => { 130 | return props => { 131 | regl.clear({ 132 | depth: 1, 133 | color: [0, 0, 0, 1] 134 | }); 135 | }; 136 | }; 137 | 138 | render() { 139 | return ( 140 | 146 | ); 147 | } 148 | } 149 | 150 | export { Bunny }; 151 | -------------------------------------------------------------------------------- /app/opengl/lighting/systems.js: -------------------------------------------------------------------------------- 1 | const Rotate = (entities, { touches }) => { 2 | let move = touches.find(x => x.type === "move"); 3 | 4 | if (move){ 5 | entities.bunny.yaw += move.delta.pageX * 0.01; 6 | entities.bunny.pitch += move.delta.pageY * 0.01; 7 | } 8 | 9 | return entities; 10 | } 11 | 12 | export { Rotate }; -------------------------------------------------------------------------------- /app/opengl/motion-blur/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { AppRegistry, StyleSheet, StatusBar } from "react-native"; 3 | import { GameEngine } from "react-native-game-engine"; 4 | import { Square } from "./renderers"; 5 | import { Rotate, Scale } from "./systems" 6 | 7 | export default class MotionBlueExample extends PureComponent { 8 | constructor() { 9 | super(); 10 | } 11 | 12 | render() { 13 | return ( 14 | } 19 | }}> 20 | 21 | 24 | ); 25 | } 26 | } 27 | 28 | const styles = StyleSheet.create({ 29 | container: { 30 | flex: 1, 31 | backgroundColor: "#FFF" 32 | } 33 | }); -------------------------------------------------------------------------------- /app/opengl/motion-blur/renderers.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { StyleSheet, View } from "react-native"; 3 | import { Surface } from "gl-react-expo"; 4 | import { Node, Shaders, GLSL, Uniform, NearestCopy } from "gl-react"; 5 | 6 | const shaders = Shaders.create({ 7 | MotionBlur: { 8 | frag: GLSL` 9 | precision highp float; 10 | varying vec2 uv; 11 | uniform sampler2D children, backbuffer; 12 | uniform float persistence; 13 | void main () { 14 | gl_FragColor = vec4(mix( 15 | texture2D(children, uv), 16 | texture2D(backbuffer, uv), 17 | persistence 18 | ).rgb, 1.0); 19 | }` 20 | }, 21 | HelloGL: { 22 | frag: GLSL` 23 | precision highp float; 24 | varying vec2 uv; 25 | uniform float blue; 26 | void main() { 27 | gl_FragColor = vec4(uv.x, uv.y, blue, 1.0); 28 | }` 29 | }, 30 | Rotate: { 31 | frag: GLSL` 32 | precision highp float; 33 | varying vec2 uv; 34 | uniform float angle, scale; 35 | uniform sampler2D children; 36 | void main() { 37 | mat2 rotation = mat2(cos(angle), -sin(angle), sin(angle), cos(angle)); 38 | vec2 p = (uv - vec2(0.5)) * rotation / scale + vec2(0.5); 39 | gl_FragColor = 40 | p.x < 0.0 || p.x > 1.0 || p.y < 0.0 || p.y > 1.0 41 | ? vec4(0.0) 42 | : texture2D(children, p); 43 | }` 44 | } 45 | }); 46 | 47 | const MotionBlur = ({ children, persistence }: *) => ( 48 | 49 | 54 | 55 | ); 56 | 57 | class HelloGL extends PureComponent { 58 | props: { 59 | blue: number 60 | }; 61 | render() { 62 | const { blue } = this.props; 63 | return ; 64 | } 65 | } 66 | 67 | class Rotate extends PureComponent { 68 | props: { 69 | scale: number, 70 | angle: number, 71 | children: any 72 | }; 73 | render() { 74 | const { angle, scale, children } = this.props; 75 | return ( 76 | 77 | ); 78 | } 79 | } 80 | 81 | class Square extends PureComponent { 82 | render() { 83 | let size = Math.min(this.props.screen.width, this.props.screen.height) 84 | return ( 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | ); 95 | } 96 | } 97 | 98 | const css = StyleSheet.create({ 99 | container: { 100 | flex: 1, 101 | alignItems: "center", 102 | justifyContent: "center", 103 | backgroundColor: "#000" 104 | } 105 | }) 106 | 107 | export { Square }; 108 | -------------------------------------------------------------------------------- /app/opengl/motion-blur/systems.js: -------------------------------------------------------------------------------- 1 | let rotateBy = 0.01; 2 | 3 | const Rotate = (entities, { touches }) => { 4 | let move = touches.find(x => x.type === "move"); 5 | 6 | if (move) 7 | rotateBy += move.delta.pageX * 0.005; 8 | 9 | entities.square.angle += rotateBy 10 | 11 | return entities; 12 | } 13 | 14 | const Scale = (entities, { touches }) => { 15 | let move = touches.find(x => x.type === "move"); 16 | 17 | if (move) 18 | entities.square.scale += move.delta.pageY * 0.005; 19 | 20 | return entities; 21 | } 22 | 23 | export { Rotate, Scale }; -------------------------------------------------------------------------------- /app/opengl/stanford-bunny/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { StyleSheet, StatusBar } from "react-native"; 3 | import { GameEngine } from "react-native-game-engine"; 4 | import { Bunny } from "./renderers"; 5 | import { Rotate } from "./systems" 6 | 7 | export default class StanfordBunnyExample extends PureComponent { 8 | constructor() { 9 | super(); 10 | } 11 | 12 | render() { 13 | return ( 14 | } 19 | }}> 20 | 21 | 24 | ); 25 | } 26 | } 27 | 28 | const styles = StyleSheet.create({ 29 | container: { 30 | flex: 1, 31 | backgroundColor: "#FFF" 32 | } 33 | }); -------------------------------------------------------------------------------- /app/opengl/stanford-bunny/renderers.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { StyleSheet, View, Text } from "react-native"; 3 | import { GLView } from "expo-gl"; 4 | import REGL from "regl"; 5 | import mat4 from "gl-mat4"; 6 | import bunny from "bunny"; 7 | 8 | class ReglView extends PureComponent { 9 | constructor() { 10 | super(); 11 | this.state = {}; 12 | } 13 | 14 | onContextCreate = gl => { 15 | const regl = REGL(gl); 16 | const rngl = gl.getExtension("RN"); 17 | const clear = this.props.clearCommand(regl, rngl); 18 | const draw = this.props.drawCommand(regl, rngl); 19 | 20 | this.setState({ 21 | frame: props => { 22 | clear(props); 23 | draw(props); 24 | gl.endFrameEXP(); 25 | } 26 | }); 27 | }; 28 | 29 | render() { 30 | if (this.state.frame) this.state.frame(this.props); 31 | 32 | return ( 33 | 37 | ); 38 | } 39 | } 40 | 41 | class Bunny extends PureComponent { 42 | drawCommand = regl => { 43 | return regl({ 44 | vert: ` 45 | precision mediump float; 46 | attribute vec3 position; 47 | uniform mat4 model, view, projection; 48 | void main() { 49 | gl_Position = projection * view * model * vec4(position, 1); 50 | }`, 51 | 52 | frag: ` 53 | precision mediump float; 54 | void main() { 55 | gl_FragColor = vec4(1, 1, 1, 1); 56 | }`, 57 | 58 | // this converts the vertices of the mesh into the position attribute 59 | attributes: { 60 | position: bunny.positions 61 | }, 62 | 63 | // and this converts the faces of the mesh into elements 64 | elements: bunny.cells, 65 | 66 | uniforms: { 67 | model: (_, { yaw, pitch }) => { 68 | return mat4.translate( 69 | [], 70 | mat4.rotateY([], mat4.rotateX([], mat4.identity([]), pitch), yaw), 71 | [0, -2.5, 0] 72 | ); 73 | }, 74 | view: mat4.lookAt([], [0, 0, 30], [0, 0, 0], [0, 1, 0]), 75 | projection: ({ viewportWidth, viewportHeight }) => 76 | mat4.perspective( 77 | [], 78 | Math.PI / 4, 79 | viewportWidth / viewportHeight, 80 | 0.01, 81 | 1000 82 | ) 83 | } 84 | }); 85 | }; 86 | 87 | clearCommand = regl => { 88 | return props => { 89 | regl.clear({ 90 | depth: 1, 91 | color: [0, 0, 0, 1] 92 | }); 93 | }; 94 | }; 95 | 96 | render() { 97 | return ( 98 | 104 | ); 105 | } 106 | } 107 | 108 | export { Bunny }; 109 | -------------------------------------------------------------------------------- /app/opengl/stanford-bunny/systems.js: -------------------------------------------------------------------------------- 1 | const Rotate = (entities, { touches }) => { 2 | let move = touches.find(x => x.type === "move"); 3 | 4 | if (move){ 5 | entities.bunny.yaw += move.delta.pageX * 0.01; 6 | entities.bunny.pitch += move.delta.pageY * 0.01; 7 | } 8 | 9 | return entities; 10 | } 11 | 12 | export { Rotate }; -------------------------------------------------------------------------------- /app/physics/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import RigidBodies from "./rigid-bodies"; 3 | 4 | export default function (mount) { 5 | return { 6 | heading: "Physics", 7 | items: [ 8 | { 9 | heading: "Rigid Bodies", 10 | onPress: _ => mount() 11 | } 12 | ] 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /app/physics/rigid-bodies/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { StatusBar, Dimensions } from "react-native"; 3 | import { GameEngine } from "react-native-game-engine"; 4 | import { Physics, CreateBox, MoveBox, CleanBoxes } from "./systems"; 5 | import { Box } from "./renderers"; 6 | import Matter from "matter-js"; 7 | 8 | Matter.Common.isElement = () => false; //-- Overriding this function because the original references HTMLElement 9 | 10 | const RigidBodies = (props) => { 11 | const { width, height } = Dimensions.get("window"); 12 | const boxSize = Math.trunc(Math.max(width, height) * 0.075); 13 | 14 | const engine = Matter.Engine.create({ enableSleeping: false }); 15 | const world = engine.world; 16 | const body = Matter.Bodies.rectangle(width / 2, -1000, boxSize, boxSize, { frictionAir: 0.021 }); 17 | const floor = Matter.Bodies.rectangle(width / 2, height - boxSize / 2, width, boxSize, { isStatic: true }); 18 | const constraint = Matter.Constraint.create({ 19 | label: "Drag Constraint", 20 | pointA: { x: 0, y: 0 }, 21 | pointB: { x: 0, y: 0 }, 22 | length: 0.01, 23 | stiffness: 0.1, 24 | angularStiffness: 1, 25 | }); 26 | 27 | Matter.World.add(world, [body, floor]); 28 | Matter.World.addConstraint(world, constraint); 29 | 30 | return ( 31 | 39 | 41 | ); 42 | }; 43 | 44 | export default RigidBodies; 45 | -------------------------------------------------------------------------------- /app/physics/rigid-bodies/renderers.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Animated from "react-native-reanimated"; 3 | 4 | const Box = (props) => { 5 | const width = props.size[0]; 6 | const height = props.size[1]; 7 | const x = props.body.position.x - width / 2; 8 | const y = props.body.position.y - height / 2; 9 | const angle = props.body.angle; 10 | 11 | return ( 12 | 23 | ); 24 | }; 25 | 26 | export { Box }; 27 | -------------------------------------------------------------------------------- /app/physics/rigid-bodies/systems.js: -------------------------------------------------------------------------------- 1 | import { Box } from "./renderers"; 2 | import Matter from "matter-js"; 3 | 4 | let boxIds = 0; 5 | 6 | const distance = ([x1, y1], [x2, y2]) => 7 | Math.sqrt(Math.abs(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))); 8 | 9 | const Physics = (state, { touches, time }) => { 10 | let engine = state["physics"].engine; 11 | 12 | Matter.Engine.update(engine, time.delta); 13 | 14 | return state; 15 | }; 16 | 17 | const CreateBox = (state, { touches, screen }) => { 18 | let world = state["physics"].world; 19 | let boxSize = Math.trunc(Math.max(screen.width, screen.height) * 0.075); 20 | 21 | touches.filter(t => t.type === "press").forEach(t => { 22 | let body = Matter.Bodies.rectangle( 23 | t.event.pageX, 24 | t.event.pageY, 25 | boxSize, 26 | boxSize, 27 | { frictionAir: 0.021 } 28 | ); 29 | Matter.World.add(world, [body]); 30 | 31 | state[++boxIds] = { 32 | body: body, 33 | size: [boxSize, boxSize], 34 | color: boxIds % 2 == 0 ? "pink" : "#B8E986", 35 | renderer: Box 36 | }; 37 | }); 38 | 39 | return state; 40 | }; 41 | 42 | const MoveBox = (state, { touches }) => { 43 | let constraint = state["physics"].constraint; 44 | 45 | //-- Handle start touch 46 | let start = touches.find(x => x.type === "start"); 47 | 48 | if (start) { 49 | let startPos = [start.event.pageX, start.event.pageY]; 50 | 51 | let boxId = Object.keys(state).find(key => { 52 | let body = state[key].body; 53 | 54 | return ( 55 | body && 56 | distance([body.position.x, body.position.y], startPos) < 25 57 | ); 58 | }); 59 | 60 | if (boxId) { 61 | constraint.pointA = { x: startPos[0], y: startPos[1] }; 62 | constraint.bodyB = state[boxId].body; 63 | constraint.pointB = { x: 0, y: 0 }; 64 | constraint.angleB = state[boxId].body.angle; 65 | } 66 | } 67 | 68 | //-- Handle move touch 69 | let move = touches.find(x => x.type === "move"); 70 | 71 | if (move) { 72 | constraint.pointA = { x: move.event.pageX, y: move.event.pageY }; 73 | } 74 | 75 | //-- Handle end touch 76 | let end = touches.find(x => x.type === "end"); 77 | 78 | if (end) { 79 | constraint.pointA = null; 80 | constraint.bodyB = null; 81 | constraint.pointB = null; 82 | } 83 | 84 | return state; 85 | }; 86 | 87 | const CleanBoxes = (state, { touches, screen }) => { 88 | let world = state["physics"].world; 89 | 90 | Object.keys(state) 91 | .filter(key => state[key].body && state[key].body.position.y > screen.height * 2) 92 | .forEach(key => { 93 | Matter.Composite.remove(world, state[key].body); 94 | delete state[key]; 95 | }); 96 | 97 | return state; 98 | }; 99 | 100 | export { Physics, CreateBox, MoveBox, CleanBoxes }; 101 | -------------------------------------------------------------------------------- /app/sensors/accelerometer/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { StatusBar, Dimensions } from "react-native"; 3 | import { GameEngine } from "react-native-game-engine"; 4 | import { Physics, CreateBox, MoveBox, CleanBoxes, Shake } from "./systems"; 5 | import { Box } from "./renderers"; 6 | import Matter from "matter-js"; 7 | import { Accelerometer } from 'react-native-sensors'; 8 | 9 | Matter.Common.isElement = () => false; //-- Overriding this function because the original references HTMLElement 10 | 11 | export default class AccelerometerExample extends Component { 12 | constructor() { 13 | super(); 14 | this.accelerometer = new Accelerometer({ updateInterval: 16 }); 15 | } 16 | 17 | componentDidMount() { 18 | this.accelerometer.pairwise().subscribe(([r1, r2]) => { 19 | this.refs.engine.publishEvent({ type: "accelerometer", x: r2.x - r1.x, y: r2.y - r1.y }); 20 | }) 21 | } 22 | 23 | componentWillUnmount() { 24 | this.accelerometer.stop(); 25 | } 26 | 27 | render() { 28 | const { width, height } = Dimensions.get("window"); 29 | const boxSize = Math.trunc(Math.max(width, height) * 0.075); 30 | 31 | const engine = Matter.Engine.create({ enableSleeping: false }); 32 | const world = engine.world; 33 | const body = Matter.Bodies.rectangle(width / 2, -1000, boxSize, boxSize, { frictionAir: 0.021 }); 34 | const floor = Matter.Bodies.rectangle(width / 2, height - boxSize / 2, width, boxSize, { isStatic: true }); 35 | const constraint = Matter.Constraint.create({ 36 | label: "Drag Constraint", 37 | pointA: { x: 0, y: 0 }, 38 | pointB: { x: 0, y: 0 }, 39 | length: 0.01, 40 | stiffness: 0.1, 41 | angularStiffness: 1 42 | }); 43 | 44 | Matter.World.add(world, [body, floor]); 45 | Matter.World.addConstraint(world, constraint); 46 | 47 | return ( 48 | 59 | 60 | 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/sensors/accelerometer/renderers.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PureComponent } from "react"; 2 | import { StyleSheet, View, ART, Dimensions } from "react-native"; 3 | import Svg, { Path, Rect } from "react-native-svg"; 4 | import { Vector } from "matter-js"; 5 | 6 | class Box extends Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | 11 | render() { 12 | const width = this.props.size[0]; 13 | const height = this.props.size[1]; 14 | const x = this.props.body.position.x - width / 2; 15 | const y = this.props.body.position.y - height / 2; 16 | const angle = this.props.body.angle; 17 | 18 | return ( 19 | 32 | ); 33 | } 34 | } 35 | 36 | export { 37 | Box 38 | }; 39 | -------------------------------------------------------------------------------- /app/sensors/accelerometer/systems.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import { Box } from "./renderers"; 3 | import Matter from "matter-js"; 4 | 5 | let boxIds = 0; 6 | 7 | const distance = ([x1, y1], [x2, y2]) => 8 | Math.sqrt(Math.abs(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))); 9 | 10 | const Physics = (state, { touches, time }) => { 11 | let engine = state["engine"].engine; 12 | 13 | Matter.Engine.update(engine, time.delta); 14 | 15 | return state; 16 | }; 17 | 18 | const CreateBox = (state, { touches, screen }) => { 19 | let world = state["world"].world; 20 | let boxSize = Math.trunc(Math.max(screen.width, screen.height) * 0.075); 21 | 22 | touches.filter(t => t.type === "press").forEach(t => { 23 | let body = Matter.Bodies.rectangle( 24 | t.event.pageX, 25 | t.event.pageY, 26 | boxSize, 27 | boxSize, 28 | { frictionAir: 0.021 } 29 | ); 30 | Matter.World.add(world, [body]); 31 | 32 | state[++boxIds] = { 33 | body: body, 34 | size: [boxSize, boxSize], 35 | color: boxIds % 2 == 0 ? "pink" : "#B8E986", 36 | renderer: Box 37 | }; 38 | }); 39 | 40 | return state; 41 | }; 42 | 43 | const MoveBox = (state, { touches }) => { 44 | let constraint = state["constraint"].constraint; 45 | 46 | //-- Handle start touch 47 | let start = touches.find(x => x.type === "start" && x.id === 1); 48 | 49 | if (start) { 50 | let startPos = [start.event.pageX, start.event.pageY]; 51 | 52 | let boxId = Object.keys(state).find(key => { 53 | let body = state[key].body; 54 | 55 | return ( 56 | body && 57 | distance([body.position.x, body.position.y], startPos) < 25 58 | ); 59 | }); 60 | 61 | if (boxId) { 62 | constraint.pointA = { x: startPos[0], y: startPos[1] }; 63 | constraint.bodyB = state[boxId].body; 64 | constraint.pointB = { x: 0, y: 0 }; 65 | constraint.angleB = state[boxId].body.angle; 66 | } 67 | } 68 | 69 | //-- Handle move touch 70 | let move = touches.find(x => x.type === "move" && x.id === 1); 71 | 72 | if (move) { 73 | constraint.pointA = { x: move.event.pageX, y: move.event.pageY }; 74 | } 75 | 76 | //-- Handle end touch 77 | let end = touches.find(x => x.type === "end" && x.id === 1); 78 | 79 | if (end) { 80 | constraint.pointA = null; 81 | constraint.bodyB = null; 82 | constraint.pointB = null; 83 | } 84 | 85 | return state; 86 | }; 87 | 88 | const CleanBoxes = (state, { touches, screen }) => { 89 | let world = state["world"].world; 90 | 91 | Object.keys(state) 92 | .filter(key => state[key].body && state[key].body.position.y > screen.height * 2) 93 | .forEach(key => { 94 | Matter.Composite.remove(world, state[key].body); 95 | delete state[key]; 96 | }); 97 | 98 | return state; 99 | }; 100 | 101 | const Shake = (state, { events }) => { 102 | 103 | let e = events.find(x => x.type === "accelerometer"); 104 | 105 | if (!e) return state; 106 | 107 | Object.keys(state) 108 | .filter(key => state[key].body) 109 | .forEach(key => { 110 | let body = state[key].body; 111 | Matter.Body.applyForce(body, body.position, Matter.Vector.div(e, 50)) 112 | }); 113 | 114 | return state; 115 | }; 116 | 117 | export { Physics, CreateBox, MoveBox, CleanBoxes, Shake }; 118 | -------------------------------------------------------------------------------- /app/sensors/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Accelerometer from "./accelerometer"; 3 | 4 | export default function (mount) { 5 | return { 6 | heading: "Sensors", 7 | items: [ 8 | { 9 | heading: "Accelerometer", 10 | onPress: _ => mount() 11 | } 12 | ] 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /app/table-of-contents/backButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View, StyleSheet, TouchableOpacity, Platform } from "react-native"; 3 | import * as Animatable from "react-native-animatable"; 4 | import EStyleSheet from "react-native-extended-stylesheet"; 5 | import Back from "./images/back.png"; 6 | 7 | export default class BackButton extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = {}; 11 | } 12 | 13 | fadeInLeft = duration => { 14 | return this.refs.back.fadeInLeft(duration); 15 | }; 16 | 17 | fadeOutLeft = duration => { 18 | return this.refs.back.fadeOutLeft(duration); 19 | }; 20 | 21 | fadeInRight = duration => { 22 | return this.refs.back.fadeInRight(duration); 23 | }; 24 | 25 | fadeOutRight = duration => { 26 | return this.refs.back.fadeOutRight(duration); 27 | }; 28 | 29 | onPressIn = () => { 30 | this.refs.back.transitionTo({ 31 | marginLeft: -40, 32 | marginRight: 40 33 | }); 34 | }; 35 | 36 | onPressOut = () => { 37 | this.refs.back.transitionTo({ 38 | marginLeft: -20, 39 | marginRight: 20 40 | }); 41 | }; 42 | 43 | render() { 44 | return ( 45 | 53 | 59 | 60 | ); 61 | } 62 | } 63 | 64 | const css = EStyleSheet.create({ 65 | container: { 66 | marginLeft: -20, 67 | marginRight: 20 68 | } 69 | }); 70 | -------------------------------------------------------------------------------- /app/table-of-contents/closeButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View, StyleSheet, TouchableOpacity } from "react-native"; 3 | import * as Animatable from "react-native-animatable"; 4 | import EStyleSheet from "react-native-extended-stylesheet"; 5 | import Close from "./images/close.png"; 6 | 7 | export default class CloseButton extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = {}; 11 | } 12 | 13 | onPress = async () => { 14 | await this.refs.close.bounceOut(300); 15 | 16 | if (this.props.onPress) 17 | this.props.onPress(); 18 | }; 19 | 20 | render() { 21 | return ( 22 | 28 | 34 | 35 | ); 36 | } 37 | } 38 | 39 | const css = EStyleSheet.create({ 40 | $marginTop: "1.5%", 41 | button: { 42 | margin: "$marginTop", 43 | position: "absolute" 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /app/table-of-contents/heading.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View, StyleSheet, Text, TouchableOpacity } from "react-native"; 3 | import * as Animatable from "react-native-animatable"; 4 | import EStyleSheet from "react-native-extended-stylesheet"; 5 | 6 | export default class Heading extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = {}; 10 | } 11 | 12 | fadeInLeft = duration => { 13 | return this.refs.heading.fadeInLeft(duration); 14 | }; 15 | 16 | fadeOutLeft = duration => { 17 | return this.refs.heading.fadeOutLeft(duration); 18 | }; 19 | 20 | fadeInRight = duration => { 21 | return this.refs.heading.fadeInRight(duration); 22 | }; 23 | 24 | fadeOutRight = duration => { 25 | return this.refs.heading.fadeOutRight(duration); 26 | }; 27 | 28 | render() { 29 | return ( 30 | 35 | 36 | {this.props.value 37 | .substring( 38 | this.props.value.indexOf(".") + 1, 39 | this.props.value.length 40 | ) 41 | .trim() 42 | .toUpperCase()} 43 | 44 | 45 | ); 46 | } 47 | } 48 | 49 | const css = EStyleSheet.create({ 50 | $borderHeight: "0.5%", 51 | $fontHeight: "3%", 52 | $lineHeight: "5%", 53 | $letterSpacingWidth: "1.3%", 54 | container: { 55 | borderBottomWidth: "$borderHeight", 56 | borderColor: "#FFF" 57 | }, 58 | text: { 59 | backgroundColor: "transparent", 60 | letterSpacing: "$letterSpacingWidth", 61 | color: "#FFF", 62 | fontSize: "$fontHeight", 63 | lineHeight: "$lineHeight", 64 | fontWeight: "bold" 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /app/table-of-contents/images/back@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/back@1x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/back@2x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/back@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/back@3x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/back@4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/back@4x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/close@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/close@1x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/close@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/close@2x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/close@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/close@3x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/close@4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/close@4x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/logo-alt@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/logo-alt@1x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/logo-alt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/logo-alt@2x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/logo-alt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/logo-alt@3x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/logo-alt@4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/logo-alt@4x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/logo@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/logo@1x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/logo@2x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/logo@3x.png -------------------------------------------------------------------------------- /app/table-of-contents/images/logo@4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/app/table-of-contents/images/logo@4x.png -------------------------------------------------------------------------------- /app/table-of-contents/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { StatusBar, View, StyleSheet, ScrollView, Image } from "react-native"; 3 | import { GameEngine } from "react-native-game-engine"; 4 | import { ParticleSystem } from "./renderers"; 5 | import { 6 | SpawnParticles, 7 | Gravity, 8 | Wind, 9 | Sprinkles, 10 | Motion, 11 | DegenerateParticles 12 | } from "./systems"; 13 | import Title from "./title"; 14 | import Heading from "./heading"; 15 | import BackButton from "./backButton"; 16 | import Item from "./item"; 17 | import { LinearGradient } from "expo-linear-gradient"; 18 | import Logo from "./images/logo.png" 19 | 20 | export default class TableOfContents extends Component { 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | heading: props.contents.heading, 25 | items: props.contents.items, 26 | animation: "fadeInRight" 27 | }; 28 | } 29 | 30 | onItemPress = async data => { 31 | if (data.items) { 32 | let refs = [this.state.heading, "back"].concat( 33 | this.state.items.map(x => x.heading) 34 | ); 35 | let tasks = refs 36 | .map(r => this.refs[r]) 37 | .filter(r => r) 38 | .map(r => r.fadeOutLeft(400)); 39 | 40 | await Promise.all(tasks); 41 | 42 | this.setState({ 43 | heading: data.heading, 44 | items: data.items, 45 | parent: Object.assign({}, this.state), 46 | animation: "fadeInRight" 47 | }); 48 | } else if (data.onPress) { 49 | data.onPress(); 50 | } 51 | }; 52 | 53 | onBackPress = async ev => { 54 | if (this.state.parent) { 55 | this.refs.engine.publishEvent({ 56 | type: "back-pressed", 57 | x: ev.nativeEvent.pageX, 58 | y: ev.nativeEvent.pageY 59 | }); 60 | 61 | let parent = this.state.parent; 62 | let backButton = this.refs["back"]; 63 | let refs = [this.state.heading].concat( 64 | this.state.items.map(x => x.heading) 65 | ); 66 | let tasks = refs 67 | .map(r => this.refs[r]) 68 | .filter(r => r) 69 | .map(r => r.fadeOutRight(400)) 70 | .concat([backButton.fadeOutLeft(400)]); 71 | 72 | await Promise.all(tasks); 73 | 74 | this.setState({ 75 | heading: parent.heading, 76 | items: parent.items, 77 | parent: null, 78 | animation: "fadeInLeft" 79 | }); 80 | } 81 | }; 82 | 83 | render() { 84 | let backButton = this.state.parent 85 | ? 91 | : null; 92 | 93 | return ( 94 | 98 | 99 | 117 | 156 | 157 | 158 | ); 159 | } 160 | } 161 | 162 | const css = StyleSheet.create({ 163 | linearGradient: { 164 | flex: 1 165 | }, 166 | logo: { 167 | marginTop: "20%", 168 | marginBottom: "10%" 169 | }, 170 | container: { 171 | alignSelf: "center", 172 | alignItems: "center", 173 | width: "100%" 174 | }, 175 | headingContainer: { 176 | alignItems: "center", 177 | marginTop: "4.5%", 178 | marginBottom: "2.25%", 179 | alignSelf: "center", 180 | flexDirection: "row" 181 | } 182 | }); -------------------------------------------------------------------------------- /app/table-of-contents/item.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { View, StyleSheet, Text, TouchableOpacity } from "react-native"; 3 | import * as Animatable from "react-native-animatable"; 4 | import EStyleSheet from "react-native-extended-stylesheet"; 5 | 6 | export default class Item extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = {}; 10 | } 11 | 12 | fadeInLeft = duration => { 13 | return this.refs.item.fadeInLeft(duration); 14 | }; 15 | 16 | fadeOutLeft = duration => { 17 | return this.refs.item.fadeOutLeft(duration); 18 | }; 19 | 20 | fadeInRight = duration => { 21 | return this.refs.item.fadeInRight(duration); 22 | }; 23 | 24 | fadeOutRight = duration => { 25 | return this.refs.item.fadeOutRight(duration); 26 | }; 27 | 28 | render() { 29 | return ( 30 | 31 | 37 | {this.props.value} 38 | 39 | 40 | ); 41 | } 42 | } 43 | 44 | const css = EStyleSheet.create({ 45 | $fontHeight: "3%", 46 | $lineHeight: "7.5%", 47 | itemText: { 48 | backgroundColor: "transparent", 49 | fontSize: "$fontHeight", 50 | lineHeight: "$lineHeight", 51 | color: "#FFF" 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /app/table-of-contents/renderers.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PureComponent } from "react"; 2 | import { StyleSheet, View, /*ART,*/ Dimensions } from "react-native"; 3 | //import Svg, { Path, Rect } from "react-native-svg"; 4 | 5 | //const { Surface, Group, Shape } = ART; 6 | const { width: WIDTH, height: HEIGHT } = Dimensions.get("window"); 7 | 8 | class Particle extends PureComponent { 9 | constructor(props) { 10 | super(props); 11 | } 12 | 13 | render() { 14 | const size = HEIGHT * 0.002 * this.props.size 15 | const x = this.props.position[0] - size / 2; 16 | const y = this.props.position[1] - size / 2; 17 | return ( 18 | 31 | ); 32 | } 33 | } 34 | 35 | //-- Performs best on physical device (iPad Mini 2) 36 | class ParticleSystem extends Component { 37 | constructor(props) { 38 | super(props); 39 | } 40 | 41 | render() { 42 | return ( 43 | 44 | {this.props.particles.map((p, i) => ( 45 | 51 | ))} 52 | 53 | ); 54 | } 55 | } 56 | 57 | // class ParticleSystemReactNativeART extends Component { 58 | // constructor(props) { 59 | // super(props); 60 | // } 61 | 62 | // render() { 63 | // const data = this.props.particles.map( 64 | // p => `M${p.position[0]},${p.position[1]}a5,5 0 1,0 10,0a5,5 0 1,0 -10,0` 65 | // ); 66 | 67 | // return ( 68 | // 69 | // 70 | // 71 | // 72 | // 73 | // ); 74 | // } 75 | // } 76 | 77 | // //-- Performs best on simulator 78 | // class ParticleSystemReactNativeSvg extends Component { 79 | // constructor(props) { 80 | // super(props); 81 | // } 82 | 83 | // render() { 84 | // const data = this.props.particles.map( 85 | // p => `M${p.position[0]},${p.position[1]}a5,5 0 1,0 10,0a5,5 0 1,0 -10,0` 86 | // ); 87 | 88 | // return ( 89 | // 90 | // 91 | // 92 | // ); 93 | // } 94 | // } 95 | 96 | // class ParticleSystemReactNativeSvgWithRects extends Component { 97 | // constructor(props) { 98 | // super(props); 99 | // } 100 | 101 | // render() { 102 | // return ( 103 | // 104 | // {this.props.particles.map((p, i) => { 105 | // return ( 106 | // 114 | // ); 115 | // })} 116 | // 117 | // ); 118 | // } 119 | // } 120 | 121 | export { 122 | Particle, 123 | ParticleSystem, 124 | //ParticleSystemReactNativeART, 125 | //ParticleSystemReactNativeSvg, 126 | //ParticleSystemReactNativeSvgWithRects 127 | }; 128 | -------------------------------------------------------------------------------- /app/table-of-contents/systems.js: -------------------------------------------------------------------------------- 1 | 2 | const COLORS = ["#FFF"]; 3 | const GRAVITY = [0, 0.05]; 4 | 5 | const random = (min = 0, max = 1) => { 6 | return Math.random() * (max - min) + min; 7 | }; 8 | 9 | const SpawnParticles = (state, { screen }) => { 10 | let flowRate = Math.random(); 11 | if (flowRate > 0.2) return state; 12 | 13 | Object.keys(state).filter(key => state[key].particles).forEach(key => { 14 | let sys = state[key]; 15 | sys.particles.push({ 16 | position: [random(0, screen.width), -50], 17 | velocity: GRAVITY, 18 | mass: random(), 19 | lifespan: 148, 20 | size: random(0, 10), 21 | color: COLORS[Math.trunc(random(0, COLORS.length))] 22 | }); 23 | }); 24 | 25 | return state; 26 | }; 27 | 28 | const Gravity = (state) => { 29 | Object.keys(state).filter(key => state[key].particles).forEach(key => { 30 | let sys = state[key]; 31 | sys.particles.forEach(p => { 32 | let mass = p.mass; 33 | let acc = [GRAVITY[0] / mass, GRAVITY[1] / mass]; 34 | let vel = p.velocity; 35 | 36 | p.velocity = [vel[0] + acc[0], vel[1] + acc[1]] 37 | }); 38 | }); 39 | 40 | return state; 41 | }; 42 | 43 | const Wind = (state) => { 44 | return state; 45 | }; 46 | 47 | const Sprinkles = (state, { events }) => { 48 | let sysId = Object.keys(state).find(key => state[key].particles); 49 | let sys = state[sysId]; 50 | if (sys) { 51 | events.filter(e => e.type === "back-pressed").forEach(e => { 52 | for (let i = 0; i < 8; i++) { 53 | sys.particles.push({ 54 | position: [e.x, e.y], 55 | velocity: [random(-2, 2), random(-4, 1)], 56 | mass: random(), 57 | lifespan: 148, 58 | size: random(0, 10), 59 | color: COLORS[Math.trunc(random(0, COLORS.length))] 60 | }); 61 | } 62 | }) 63 | } 64 | 65 | return state; 66 | } 67 | 68 | const Motion = (state) => { 69 | Object.keys(state).filter(key => state[key].particles).forEach(key => { 70 | let sys = state[key]; 71 | sys.particles.forEach(p => { 72 | let vel = p.velocity; 73 | let pos = p.position; 74 | 75 | p.position = [pos[0] + vel[0], pos[1] + vel[1]] 76 | }) 77 | }); 78 | 79 | return state; 80 | }; 81 | 82 | const DegenerateParticles = (state) => { 83 | Object.keys(state).filter(key => state[key].particles).forEach(key => { 84 | let sys = state[key]; 85 | sys.particles = sys.particles.filter(p => p.lifespan > 0); 86 | sys.particles.forEach(p => p.lifespan--); 87 | }) 88 | 89 | return state; 90 | }; 91 | 92 | export { SpawnParticles, Gravity, Wind, Sprinkles, Motion, DegenerateParticles } 93 | -------------------------------------------------------------------------------- /app/table-of-contents/title.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Text, StyleSheet, View } from "react-native"; 3 | import EStyleSheet from "react-native-extended-stylesheet"; 4 | 5 | export default class Title extends Component { 6 | constructor() { 7 | super(); 8 | } 9 | 10 | render() { 11 | return ( 12 | 13 | 14 | THE NATURE OF 15 | 16 | — CODE — 17 | 18 | ); 19 | } 20 | } 21 | 22 | const css = EStyleSheet.create({ 23 | $borderHeight: "0.5%", 24 | $fontHeight: "3.8%", 25 | $lineHeight: "6.7%", 26 | $letterSpacingWidth: "1.3%", 27 | $paddingHeight: "10.5%", 28 | container: { 29 | paddingTop: "$paddingHeight", 30 | alignItems: "center" 31 | }, 32 | titleText: { 33 | fontSize: "$fontHeight", 34 | backgroundColor: "transparent", 35 | letterSpacing: "$letterSpacingWidth", 36 | color: "#FFF" 37 | }, 38 | bold: { 39 | fontWeight: "900" 40 | }, 41 | small: { 42 | fontSize: "$fontHeight * 0.6", 43 | lineHeight: "$lineHeight" 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /app/touch-events/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import SingleTouch from "./single-touch" 3 | import MultiTouch from "./multi-touch" 4 | 5 | export default function (mount) { 6 | return { 7 | heading: "Touch Events", 8 | items: [ 9 | { 10 | heading: "Single Touch", 11 | onPress: _ => mount() 12 | }, 13 | { 14 | heading: "Multi Touch", 15 | onPress: _ => mount() 16 | } 17 | ] 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /app/touch-events/multi-touch/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { StatusBar } from "react-native"; 3 | import { GameEngine } from "react-native-game-engine"; 4 | import { 5 | SpawnWorm, 6 | AssignFingerToWorm, 7 | MoveWorm, 8 | ReleaseFingerFromWorm, 9 | RemoveWorm 10 | } from "./systems"; 11 | 12 | export default class MultiTouch extends Component { 13 | constructor() { 14 | super(); 15 | } 16 | 17 | render() { 18 | return ( 19 | 29 | 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/touch-events/multi-touch/renderers.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { StyleSheet, View, Dimensions } from "react-native"; 3 | import { StaggeredMotion, spring } from "react-motion"; 4 | import * as Animatable from "react-native-animatable"; 5 | 6 | const { width: WIDTH, height: HEIGHT } = Dimensions.get("window"); 7 | 8 | const BODY_DIAMETER = Math.trunc(Math.max(WIDTH, HEIGHT) * 0.085); 9 | const BORDER_WIDTH = Math.trunc(BODY_DIAMETER * 0.1); 10 | const COLORS = ["#86E9BE", "#8DE986", "#B8E986", "#E9E986"]; 11 | const BORDER_COLORS = ["#C0F3DD", "#C4F6C0", "#E5FCCD", "#FCFDC1"]; 12 | 13 | class Worm extends PureComponent { 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | ready: false 18 | }; 19 | } 20 | 21 | onReady = () => { 22 | this.setState({ 23 | ready: true 24 | }); 25 | }; 26 | 27 | render() { 28 | const x = this.props.position[0] - BODY_DIAMETER / 2; 29 | const y = this.props.position[1] - BODY_DIAMETER / 2; 30 | return ( 31 | 32 | 33 | 41 | prevInterpolatedStyles.map((_, i) => { 42 | return i === 0 43 | ? { 44 | left: spring(x), 45 | top: spring(y) 46 | } 47 | : { 48 | left: spring( 49 | prevInterpolatedStyles[i - 1].left 50 | ), 51 | top: spring( 52 | prevInterpolatedStyles[i - 1].top 53 | ) 54 | }; 55 | })} 56 | > 57 | {interpolatingStyles => ( 58 | 61 | {interpolatingStyles.map((style, i) => ( 62 | 78 | ))} 79 | 80 | )} 81 | 82 | 83 | 90 | 91 | 92 | ); 93 | } 94 | } 95 | 96 | const css = StyleSheet.create({ 97 | body: { 98 | borderColor: "#FFF", 99 | borderWidth: BORDER_WIDTH, 100 | width: BODY_DIAMETER, 101 | height: BODY_DIAMETER, 102 | backgroundColor: "red", 103 | position: "absolute", 104 | borderRadius: BODY_DIAMETER * 2 105 | }, 106 | anchor: { 107 | position: "absolute" 108 | }, 109 | head: { 110 | backgroundColor: "#FF5877", 111 | borderColor: "#FFC1C1", 112 | borderWidth: BORDER_WIDTH, 113 | width: BODY_DIAMETER, 114 | height: BODY_DIAMETER, 115 | position: "absolute", 116 | borderRadius: BODY_DIAMETER * 2 117 | } 118 | }); 119 | 120 | export { Worm }; 121 | -------------------------------------------------------------------------------- /app/touch-events/multi-touch/systems.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import { Worm } from "./renderers"; 3 | 4 | let wormIds = 0; 5 | 6 | const distance = ([x1, y1], [x2, y2]) => 7 | Math.sqrt(Math.abs(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))); 8 | 9 | const SpawnWorm = (state, { touches }) => { 10 | touches.filter(t => t.type === "press").forEach(t => { 11 | if (_.size(state) < 5) { 12 | state[++wormIds] = { 13 | position: [t.event.pageX, t.event.pageY], 14 | renderer: Worm 15 | }; 16 | } 17 | }); 18 | 19 | return state; 20 | }; 21 | 22 | const AssignFingerToWorm = (state, { touches }) => { 23 | let allWorms = Object.keys(state).map(key => ({ 24 | id: key, 25 | components: state[key] 26 | })); 27 | 28 | touches.filter(x => x.type === "start").forEach(t => { 29 | let touchOrigin = [t.event.pageX, t.event.pageY]; 30 | let closestWorm = _.minBy( 31 | allWorms 32 | .filter(w => !w.components.touchId) 33 | .map(w => 34 | Object.assign(w, { 35 | distance: distance(touchOrigin, w.components.position) 36 | }) 37 | ), 38 | "distance" 39 | ); 40 | if (closestWorm) closestWorm.components.touchId = t.id; 41 | }); 42 | 43 | return state; 44 | }; 45 | 46 | const MoveWorm = (state, { touches }) => { 47 | touches.filter(t => t.type === "move").forEach(t => { 48 | let wormId = Object.keys(state).find( 49 | key => state[key].touchId === t.id 50 | ); 51 | let worm = state[wormId]; 52 | if (worm) { 53 | worm.position = [ 54 | worm.position[0] + t.delta.pageX, 55 | worm.position[1] + t.delta.pageY 56 | ]; 57 | } 58 | }); 59 | 60 | return state; 61 | }; 62 | 63 | const ReleaseFingerFromWorm = (state, { touches }) => { 64 | touches.filter(t => t.type === "end").forEach(t => { 65 | Object.keys(state) 66 | .filter(key => state[key].touchId === t.id) 67 | .forEach(key => delete state[key]["touchId"]); 68 | }); 69 | 70 | return state; 71 | }; 72 | 73 | const RemoveWorm = (state, { touches }) => { 74 | touches.filter(t => t.type === "long-press").forEach(t => { 75 | let touchOrigin = [t.event.pageX, t.event.pageY]; 76 | let closestWorm = _.sortBy( 77 | Object.keys(state) 78 | .map(key => ({ 79 | id: key, 80 | distance: distance(state[key].position, touchOrigin) 81 | })) 82 | .filter(x => x.distance < 60), 83 | ["distance"] 84 | )[0]; 85 | 86 | if (closestWorm) delete state[closestWorm.id]; 87 | }); 88 | 89 | return state; 90 | }; 91 | 92 | export { 93 | SpawnWorm, 94 | AssignFingerToWorm, 95 | MoveWorm, 96 | ReleaseFingerFromWorm, 97 | RemoveWorm 98 | }; 99 | -------------------------------------------------------------------------------- /app/touch-events/single-touch/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { StyleSheet, Dimensions, StatusBar } from "react-native"; 3 | import { GameLoop } from "react-native-game-engine"; 4 | import Worm from "./worm"; 5 | 6 | const { width: WIDTH, height: HEIGHT } = Dimensions.get("window"); 7 | 8 | export default class SingleTouch extends Component { 9 | constructor() { 10 | super(); 11 | this.state = { 12 | x: WIDTH / 2, 13 | y: HEIGHT / 2, 14 | }; 15 | } 16 | 17 | onUpdate = ({ touches }) => { 18 | let move = touches.find(x => x.type === "move"); 19 | if (move) { 20 | this.setState({ 21 | x: this.state.x + move.delta.pageX, 22 | y: this.state.y + move.delta.pageY 23 | }); 24 | } 25 | }; 26 | 27 | render() { 28 | return ( 29 | 30 | 31 | 36 | ); 37 | } 38 | } 39 | 40 | const styles = StyleSheet.create({ 41 | container: { 42 | flex: 1, 43 | backgroundColor: "#FFF" 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /app/touch-events/single-touch/worm.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import { StyleSheet, View, Dimensions } from "react-native"; 3 | import { StaggeredMotion, spring } from "react-motion"; 4 | 5 | const { width: WIDTH, height: HEIGHT } = Dimensions.get("window"); 6 | 7 | const BODY_DIAMETER = Math.trunc(Math.max(WIDTH, HEIGHT) * 0.085); 8 | const BORDER_WIDTH = Math.trunc(BODY_DIAMETER * 0.1); 9 | const COLORS = ["#86E9BE", "#8DE986", "#B8E986", "#E9E986"]; 10 | const BORDER_COLORS = ["#C0F3DD", "#C4F6C0", "#E5FCCD", "#FCFDC1"]; 11 | 12 | export default class Worm extends PureComponent { 13 | render() { 14 | const x = this.props.x - BODY_DIAMETER / 2; 15 | const y = this.props.y - BODY_DIAMETER / 2; 16 | return ( 17 | 18 | 19 | 27 | prevInterpolatedStyles.map((_, i) => { 28 | return i === 0 29 | ? { 30 | left: spring(x), 31 | top: spring(y) 32 | } 33 | : { 34 | left: spring( 35 | prevInterpolatedStyles[i - 1].left 36 | ), 37 | top: spring( 38 | prevInterpolatedStyles[i - 1].top 39 | ) 40 | }; 41 | })} 42 | > 43 | {interpolatingStyles => ( 44 | 45 | {interpolatingStyles.map((style, i) => ( 46 | 59 | ))} 60 | 61 | )} 62 | 63 | 64 | 65 | 66 | 67 | ); 68 | } 69 | } 70 | 71 | const css = StyleSheet.create({ 72 | body: { 73 | borderColor: "#FFF", 74 | borderWidth: BORDER_WIDTH, 75 | width: BODY_DIAMETER, 76 | height: BODY_DIAMETER, 77 | position: "absolute", 78 | borderRadius: BODY_DIAMETER * 2 79 | }, 80 | anchor: { 81 | position: "absolute" 82 | }, 83 | head: { 84 | backgroundColor: "#FF5877", 85 | borderColor: "#FFC1C1", 86 | borderWidth: BORDER_WIDTH, 87 | width: BODY_DIAMETER, 88 | height: BODY_DIAMETER, 89 | position: "absolute", 90 | borderRadius: BODY_DIAMETER * 2 91 | } 92 | }); 93 | -------------------------------------------------------------------------------- /assets/cannon-ball-sea-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/assets/cannon-ball-sea-1.png -------------------------------------------------------------------------------- /assets/cannon-ball-sea-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/assets/cannon-ball-sea-2.png -------------------------------------------------------------------------------- /assets/cannon-ball-sea-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/assets/cannon-ball-sea-3.png -------------------------------------------------------------------------------- /assets/cannon-ball-sea-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/assets/cannon-ball-sea-4.png -------------------------------------------------------------------------------- /assets/cannon-ball-sea-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/assets/cannon-ball-sea-5.png -------------------------------------------------------------------------------- /assets/icon-and-splash.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/assets/icon-and-splash.sketch -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/assets/icon.png -------------------------------------------------------------------------------- /assets/multi-touch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/assets/multi-touch.gif -------------------------------------------------------------------------------- /assets/rigid-bodies.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/assets/rigid-bodies.gif -------------------------------------------------------------------------------- /assets/single-touch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/assets/single-touch.gif -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bberak/react-native-game-engine-handbook/915a48eb216d2beda3c6633a0e2797c6be9a7d4f/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: ['react-native-reanimated/plugin'] 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "private": true, 4 | "scripts": { 5 | "start": "expo start", 6 | "android": "expo start --android", 7 | "ios": "expo start --ios", 8 | "eject": "expo eject" 9 | }, 10 | "dependencies": { 11 | "@react-native-community/masked-view": "0.1.10", 12 | "angle-normals": "^1.0.0", 13 | "art": "^0.10.1", 14 | "buffer": "^5.6.0", 15 | "bunny": "^1.0.1", 16 | "expo": "^50.0.7", 17 | "expo-camera": "~14.0.5", 18 | "expo-gl": "~13.6.0", 19 | "expo-linear-gradient": "~12.7.2", 20 | "gl-mat4": "^1.1.4", 21 | "gl-react": "^3.13.0", 22 | "gl-react-expo": "^3.16.3", 23 | "lodash": "^4.17.19", 24 | "matter-js": "^0.13.0", 25 | "react": "18.2.0", 26 | "react-motion": "^0.5.0", 27 | "react-native": "0.73.4", 28 | "react-native-animatable": "^1.2.2", 29 | "react-native-donkey-kong": "^0.5.1", 30 | "react-native-extended-stylesheet": "git+https://github.com/bberak/react-native-extended-stylesheet.git", 31 | "react-native-game-engine": "^1.1.0", 32 | "react-native-gesture-handler": "~2.14.0", 33 | "react-native-reanimated": "~3.6.2", 34 | "react-native-safe-area-context": "4.8.2", 35 | "react-native-screens": "~3.29.0", 36 | "react-native-svg": "14.1.0", 37 | "regl": "^1.3.0", 38 | "save": "^2.4.0" 39 | }, 40 | "devDependencies": { 41 | "babel-preset-expo": "^10.0.0" 42 | } 43 | } 44 | --------------------------------------------------------------------------------