├── .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 |
3 |
4 |
5 | # React Native Game Engine Handbook · [](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 |
11 |
12 |
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 |
26 |
27 |
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 |
23 |
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 |
20 |
21 |
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 |
22 |
23 |
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 |
22 |
23 |
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 |
22 |
23 |
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 |
40 |
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 |
61 |
62 |
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 |
118 |
119 |
120 |
121 |
122 |
128 |
129 | {backButton}
130 |
131 |
137 |
138 |
139 |
140 | {this.state.items.map((x, i) => {
141 | return (
142 | - this.onItemPress(x)}
149 | />
150 | );
151 | })}
152 |
153 |
154 |
155 |
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 | //
92 | // );
93 | // }
94 | // }
95 |
96 | // class ParticleSystemReactNativeSvgWithRects extends Component {
97 | // constructor(props) {
98 | // super(props);
99 | // }
100 |
101 | // render() {
102 | // return (
103 | //
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 |
30 |
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 |
32 |
33 |
34 |
35 |
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 |
--------------------------------------------------------------------------------