initial commit
This commit is contained in:
parent
01434a0e75
commit
55fdfcb7d7
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
|
||||
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
node_modules/
|
||||
.expo/
|
||||
dist/
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
yarn.lock
|
||||
package-lock.json
|
|
@ -0,0 +1,15 @@
|
|||
import { StatusBar } from "expo-status-bar";
|
||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
|
||||
import AppView from "./AppView";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<>
|
||||
<StatusBar backgroundColor="orange" hidden={true} />
|
||||
<SafeAreaProvider>
|
||||
<AppView />
|
||||
</SafeAreaProvider>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
||||
import { GameEngine } from "react-native-game-engine";
|
||||
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
|
||||
import { entities, useEntities } from "@entities";
|
||||
import { GameLoop } from "@systems";
|
||||
import { IGameEngine } from "@types";
|
||||
|
||||
export default function AppView() {
|
||||
const [isRunning, setIsRunning] = React.useState(true);
|
||||
const [score, setScore] = useState(0);
|
||||
const [gameEngine, setGameEngine] = useState<IGameEngine | null>(null);
|
||||
const { entities } = useEntities();
|
||||
const { top } = useSafeAreaInsets();
|
||||
const topInset = top.valueOf();
|
||||
(global as any).topInset = topInset;
|
||||
|
||||
const gameEntities = entities();
|
||||
const { engine, world } = gameEntities.physics;
|
||||
|
||||
// console.log(topInset)
|
||||
|
||||
// const gameEntities = entities();
|
||||
useEffect(() => {
|
||||
(global as any).gameEngine = gameEngine;
|
||||
}, [gameEngine]);
|
||||
|
||||
return (
|
||||
<SafeAreaView edges={['top']} style={[styles.container, {
|
||||
|
||||
}]}>
|
||||
{/* @ts-ignore */}
|
||||
<GameEngine
|
||||
ref={(ref) => setGameEngine(ref as IGameEngine)}
|
||||
systems={[GameLoop]}
|
||||
entities={gameEntities}
|
||||
running={isRunning}
|
||||
onEvent={({ type, ...rest }: any) => {
|
||||
switch (type) {
|
||||
case "addToScore": {
|
||||
setScore(score => score + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}}
|
||||
style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0 }}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
gameEngine?.swap(entities());
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
textAlign: "center",
|
||||
fontSize: 40,
|
||||
fontWeight: "bold",
|
||||
marginTop: topInset / 2,
|
||||
}}
|
||||
>
|
||||
{score}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</GameEngine>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"expo": {
|
||||
"name": "react-native-game-engine-expo-typescript-template",
|
||||
"slug": "react-native-game-engine-expo-typescript-template",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#FFFFFF"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import * as React from "react";
|
||||
import Svg, { G, Path } from "react-native-svg";
|
||||
|
||||
export const BalloonSVG = (props) => {
|
||||
const color = props.color || "#000";
|
||||
return (
|
||||
<Svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={50}
|
||||
height={50}
|
||||
viewBox="0 0 950.000000 1280.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
{...props}
|
||||
>
|
||||
<G
|
||||
transform="translate(0.000000,1280.000000) scale(0.100000,-0.100000)"
|
||||
fill={color}
|
||||
stroke="none"
|
||||
>
|
||||
<Path d="M4430 12789 c-921 -59 -1769 -370 -2480 -908 -1067 -807 -1758 -2084 -1914 -3536 -69 -641 -34 -1378 100 -2105 192 -1039 624 -2084 1222 -2955 536 -781 1225 -1439 1919 -1834 140 -79 424 -214 558 -264 215 -80 439 -136 648 -162 60 -7 111 -16 115 -19 8 -9 -15 -237 -34 -326 -35 -167 -134 -411 -198 -487 -33 -40 -33 -73 2 -105 59 -57 173 -82 372 -83 213 0 331 25 393 83 36 34 34 62 -7 116 -114 150 -225 522 -226 758 0 54 -15 47 150 68 889 116 1919 814 2751 1865 480 607 892 1332 1182 2085 466 1210 624 2521 441 3659 -214 1337 -881 2491 -1874 3242 -876 663 -1971 982 -3120 908z" />
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './BalloonSVG';
|
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
|
@ -0,0 +1,22 @@
|
|||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
plugins: [
|
||||
[
|
||||
'module-resolver',
|
||||
{
|
||||
root: ['./src'],
|
||||
extensions: ['.ios.ts', '.android.ts', '.ts', '.ios.tsx', '.android.tsx', '.jsx', '.js', '.json'],
|
||||
alias: {
|
||||
'@entities': './game/entities',
|
||||
"@game": "./game",
|
||||
'@systems': './game/systems',
|
||||
'@svg': './assets/SVG',
|
||||
'@types': './game',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
};
|
|
@ -0,0 +1,60 @@
|
|||
import Matter from 'matter-js'
|
||||
import React from 'react'
|
||||
import { ColorValue, View } from 'react-native'
|
||||
|
||||
import { BalloonSVG } from '@svg'
|
||||
import { Position2D, Size2D } from '@types'
|
||||
|
||||
const Balloon = ({ body, color }: any) => {
|
||||
const widthBody = body.bounds.max.x - body.bounds.min.x;
|
||||
const heightBody = body.bounds.max.y - body.bounds.min.y;
|
||||
|
||||
const xBody = body.position.x - widthBody / 2;
|
||||
const yBody = body.position.y - heightBody / 2;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: xBody,
|
||||
top: yBody,
|
||||
width: widthBody,
|
||||
height: heightBody,
|
||||
}}
|
||||
>
|
||||
<BalloonSVG color={color} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default (
|
||||
world: Matter.Composite,
|
||||
color: ColorValue,
|
||||
pos: Position2D,
|
||||
size: Size2D
|
||||
) => {
|
||||
const body = Matter.Bodies.circle(pos.x, pos.y, 50, {
|
||||
label: "Balloon",
|
||||
isStatic: false,
|
||||
// restitution: 0.4,
|
||||
// friction: 1,
|
||||
frictionAir: 0.2,
|
||||
// mass: 0.1,
|
||||
// inverseMass: 0.1,
|
||||
// bounds: {
|
||||
// min: { x: size.width, y: size.height },
|
||||
// max: { x: size.width, y: size.height },
|
||||
// },
|
||||
// inertia: Infinity,
|
||||
// inverseInertia: Infinity,
|
||||
} as Matter.IChamferableBodyDefinition);
|
||||
Matter.Composite.add(world, body);
|
||||
|
||||
return {
|
||||
body,
|
||||
color,
|
||||
pos,
|
||||
renderer: Balloon,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import React, { PureComponent } from "react";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
|
||||
const RADIUS = 20;
|
||||
|
||||
export const Finger = ({ position }: { position: any[] }) => {
|
||||
const x = position[0] - RADIUS / 2;
|
||||
const y = position[1] - RADIUS / 2;
|
||||
return (
|
||||
<View style={[styles.finger, { left: x, top: y }]} />
|
||||
)
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
finger: {
|
||||
borderColor: "#CCC",
|
||||
borderWidth: 4,
|
||||
borderRadius: RADIUS * 2,
|
||||
width: RADIUS * 2,
|
||||
height: RADIUS * 2,
|
||||
backgroundColor: "pink",
|
||||
position: "absolute"
|
||||
}
|
||||
});
|
||||
|
||||
export default Finger;
|
|
@ -0,0 +1,45 @@
|
|||
import Matter from 'matter-js'
|
||||
import React from 'react'
|
||||
import { ColorValue, View } from 'react-native'
|
||||
|
||||
import { Position2D, Size2D } from '@types'
|
||||
|
||||
const Wall = ({ body, color }: any) => {
|
||||
const widthBody = body.bounds.max.x - body.bounds.min.x
|
||||
const heightBody = body.bounds.max.y - body.bounds.min.y
|
||||
|
||||
const xBody = body.position.x - widthBody / 2
|
||||
const yBody = body.position.y - heightBody / 2
|
||||
|
||||
return (
|
||||
<View style={{
|
||||
backgroundColor: color,
|
||||
position: 'absolute',
|
||||
left: xBody,
|
||||
top: yBody,
|
||||
width: widthBody,
|
||||
height: heightBody
|
||||
}} />
|
||||
)
|
||||
}
|
||||
|
||||
export default (world: Matter.Composite, color: ColorValue, pos: Position2D, size: Size2D) => {
|
||||
const body = Matter.Bodies.rectangle(
|
||||
pos.x,
|
||||
pos.y,
|
||||
size.width,
|
||||
size.height,
|
||||
{
|
||||
label: 'Wall',
|
||||
isStatic: true
|
||||
} as Matter.IChamferableBodyDefinition
|
||||
)
|
||||
Matter.Composite.add(world, body)
|
||||
|
||||
return {
|
||||
body,
|
||||
color,
|
||||
pos,
|
||||
renderer: Wall
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
import { Dimensions } from "react-native";
|
||||
import Matter from "matter-js";
|
||||
|
||||
import { windowHeight, windowWidth } from "@game";
|
||||
import { Balloon, Finger, Wall } from ".";
|
||||
|
||||
export const entities = (restart: boolean = false) => {
|
||||
let engine = Matter.Engine.create(undefined, {
|
||||
enableSleeping: false,
|
||||
gravity: { x: 0, y: 0.0005 },
|
||||
} as Matter.IEngineDefinition);
|
||||
|
||||
let world = engine.world;
|
||||
const topInset = (global as any).topInset; // for notch handling
|
||||
|
||||
const newBalloon = () => {
|
||||
return Balloon(
|
||||
world,
|
||||
"red",
|
||||
{
|
||||
x: Math.random() * (windowWidth - 100) + 50,
|
||||
y: 150,
|
||||
},
|
||||
{ width: 50, height: 50 }
|
||||
);
|
||||
};
|
||||
|
||||
let entities = {
|
||||
physics: { engine, world },
|
||||
fingers: {
|
||||
1: { position: [40, 200], renderer: Finger },
|
||||
2: { position: [100, 200], renderer: Finger },
|
||||
3: { position: [160, 200], renderer: Finger },
|
||||
4: { position: [220, 200], renderer: Finger },
|
||||
5: { position: [280, 200], renderer: Finger },
|
||||
},
|
||||
Balloon: newBalloon(),
|
||||
LeftWall: Wall(
|
||||
world,
|
||||
"orange",
|
||||
{ x: 0 - 25, y: windowHeight / 2 },
|
||||
{ height: windowHeight, width: 50 }
|
||||
),
|
||||
RightWall: Wall(
|
||||
world,
|
||||
"orange",
|
||||
{ x: windowWidth + 25, y: windowHeight / 2 },
|
||||
{ height: windowHeight, width: 50 }
|
||||
),
|
||||
Ceiling: Wall(
|
||||
world,
|
||||
"orange",
|
||||
{ x: 0, y: 0 },
|
||||
{ height: 110 + topInset, width: windowWidth * 2 }
|
||||
),
|
||||
Floor: Wall(
|
||||
world,
|
||||
"orange",
|
||||
{ x: windowWidth / 2, y: windowHeight + 60 },
|
||||
{ height: 60, width: windowWidth }
|
||||
),
|
||||
};
|
||||
|
||||
Matter.Events.on(
|
||||
engine,
|
||||
"collisionStart",
|
||||
({ pairs }: Matter.IEventCollision<any>) => {
|
||||
for (var i = 0, j = pairs.length; i != j; ++i) {
|
||||
const bodyA = pairs[i].bodyA;
|
||||
const bodyB = pairs[i].bodyB;
|
||||
console.log(
|
||||
"collisionStart between " + bodyA.label + " - " + bodyB.label
|
||||
);
|
||||
const balloonBody = entities.Balloon.body;
|
||||
Matter.Body.scale(balloonBody, 0.0, 0.0, {
|
||||
x: balloonBody.bounds.min.x - 1000,
|
||||
y: balloonBody.bounds.max.y,
|
||||
});
|
||||
Matter.World.remove(world, balloonBody, true);
|
||||
|
||||
const gameEngine = (global as any).gameEngine;
|
||||
gameEngine.dispatch({
|
||||
type: "addToScore",
|
||||
});
|
||||
|
||||
entities.Balloon = newBalloon();
|
||||
|
||||
// @ts-ignore
|
||||
Matter.World.add(world, entities.Balloon);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return entities;
|
||||
};
|
||||
|
||||
export const useEntities = () => {
|
||||
return {
|
||||
entities
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { Dimensions } from "react-native";
|
||||
import Matter from "matter-js";
|
||||
|
||||
export { windowHeight, windowWidth } from "@game";
|
||||
|
||||
import Finger from "./Finger";
|
||||
import Wall from "./Wall";
|
||||
import Balloon from './Balloon';
|
||||
|
||||
export {
|
||||
Finger,
|
||||
Wall,
|
||||
Balloon
|
||||
};
|
||||
|
||||
export * from './entities';
|
|
@ -0,0 +1,4 @@
|
|||
import { Dimensions } from "react-native";
|
||||
|
||||
export const windowHeight = Dimensions.get("window").height;
|
||||
export const windowWidth = Dimensions.get("window").width;
|
|
@ -0,0 +1,23 @@
|
|||
import Matter, { Vector } from "matter-js";
|
||||
import { GameEngineUpdateEventOptionType, TouchEvent } from "react-native-game-engine";
|
||||
|
||||
import { windowHeight, windowWidth } from "@game";
|
||||
|
||||
export const GameLoop = (
|
||||
entities: any,
|
||||
{ touches, time, dispatch }: GameEngineUpdateEventOptionType
|
||||
) => {
|
||||
let engine = entities.physics.engine;
|
||||
let world = entities.physics.world;
|
||||
|
||||
touches
|
||||
.filter((t: TouchEvent) => t.type === "press")
|
||||
.forEach((t: TouchEvent) => {
|
||||
let balloonPos = entities.Balloon.body.position;
|
||||
console.log('Touch:', t, balloonPos);
|
||||
// Matter.Body.setVelocity(something, { x: something.velocity.x + 20, y: something.velocity.y - 20 });
|
||||
});
|
||||
|
||||
Matter.Engine.update(engine, time.delta);
|
||||
return entities;
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
export * from './GameLoop';
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { GameEngine } from "react-native-game-engine";
|
||||
|
||||
export interface Position2D {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Size2D {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface IGameEngine extends GameEngine {
|
||||
stop: () => void;
|
||||
start: () => void;
|
||||
swap: (newEntities: Promise<any> | any) => void;
|
||||
dispatch: (event: string) => void;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "react-native-game-engine-expo-typescript-template",
|
||||
"version": "1.0.0",
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"expo": "~45.0.0",
|
||||
"expo-status-bar": "~1.3.0",
|
||||
"matter-js": "^0.18.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-native": "0.68.2",
|
||||
"react-native-game-engine": "^1.2.0",
|
||||
"react-native-safe-area-context": "4.2.4",
|
||||
"react-native-svg": "12.3.0",
|
||||
"react-native-web": "0.17.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.12",
|
||||
"@babel/plugin-proposal-class-properties": "^7.17.12",
|
||||
"@babel/preset-env": "^7.17.12",
|
||||
"@expo/webpack-config": "^0.16.24",
|
||||
"@types/matter-js": "^0.17.7",
|
||||
"@types/react": "~17.0.21",
|
||||
"@types/react-native": "~0.66.13",
|
||||
"typescript": "~4.3.5"
|
||||
},
|
||||
"private": true,
|
||||
"peerDependencies": {
|
||||
"rxjs": "6.6.7"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"importHelpers": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"module": "esnext",
|
||||
"paths": {
|
||||
"@entities": ["game/entities/index.ts"],
|
||||
"@game": ["game/index.ts"],
|
||||
"@svg": ["assets/SVG/index.ts"],
|
||||
"@systems": ["game/systems/index.ts"],
|
||||
"@types": ["game/types.ts"],
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
const createExpoWebpackConfigAsync = require('@expo/webpack-config');
|
||||
|
||||
// Expo CLI will await this method so you can optionally return a promise.
|
||||
module.exports = async function (env, argv) {
|
||||
const config = await createExpoWebpackConfigAsync(env, argv);
|
||||
|
||||
config.performance = {
|
||||
hints: false,
|
||||
maxEntrypointSize: 512000,
|
||||
maxAssetSize: 512000,
|
||||
};
|
||||
|
||||
config.optimization = {
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
maxSize: 512000,
|
||||
maxAsyncRequests: 50,
|
||||
maxInitialRequests: 30,
|
||||
minChunks: 1,
|
||||
cacheGroups: {
|
||||
defaultVendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
priority: -10,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
default: {
|
||||
minChunks: 2,
|
||||
priority: -20,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Don't compress the development build
|
||||
if (config.mode === 'development') {
|
||||
config.devServer.compress = false;
|
||||
}
|
||||
|
||||
if (config.mode === 'production') {
|
||||
config.optimization.minimize = true;
|
||||
}
|
||||
|
||||
// Finally return the new config for the CLI to use.
|
||||
return config;
|
||||
};
|
Loading…
Reference in New Issue