Compare commits
10 Commits
a8410d83d0
...
01fe41975b
Author | SHA1 | Date |
---|---|---|
Josh Guyette | 01fe41975b | |
Josh Guyette | 6bad325f21 | |
Josh Guyette | d4e1d4fe93 | |
Josh Guyette | b8697545b7 | |
Josh Guyette | 1ca88b176c | |
Josh Guyette | de0f8a6311 | |
Josh Guyette | 04870da051 | |
Josh Guyette | 1bac48ff8d | |
Josh Guyette | a732f89d6c | |
Josh Guyette | 4e9aa89861 |
|
@ -0,0 +1,30 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react/recommended",
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
files: [".eslintrc.{js,cjs}"],
|
||||
parserOptions: {
|
||||
sourceType: "script",
|
||||
},
|
||||
},
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["@typescript-eslint", "react"],
|
||||
rules: {},
|
||||
ignorePatterns: ["*.d.ts"],
|
||||
};
|
|
@ -9,6 +9,12 @@ npm-debug.*
|
|||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
*.log
|
||||
*.app
|
||||
*.tar.gz
|
||||
*.ipa
|
||||
*.apk
|
||||
*.aab
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
|
25
App.tsx
25
App.tsx
|
@ -1,15 +1,36 @@
|
|||
import React from "react";
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
import { Logs } from 'expo'
|
||||
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
|
||||
import AppView from "./AppView";
|
||||
import GameEngine from "./game/systems/GameEngine";
|
||||
|
||||
export default function App() {
|
||||
Logs.enableExpoCliLogging()
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatusBar backgroundColor="orange" hidden={true} />
|
||||
<SafeAreaProvider>
|
||||
<AppView />
|
||||
<SafeView />
|
||||
</SafeAreaProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SafeView() {
|
||||
const { top, bottom, left, right } = useSafeAreaInsets();
|
||||
const topInset = top.valueOf();
|
||||
global.topInset = topInset;
|
||||
global.bottomInset = bottom;
|
||||
global.leftInset = left;
|
||||
global.rightInset = right;
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<GameEngine />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
94
AppView.tsx
94
AppView.tsx
|
@ -1,94 +0,0 @@
|
|||
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;
|
||||
}
|
||||
case "subtractFromScore": {
|
||||
setScore(score => score - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}}
|
||||
style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0 }}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
// gameEngine?.swap(entities());
|
||||
if (isRunning) {
|
||||
gameEngine?.stop();
|
||||
setIsRunning(false);
|
||||
} else {
|
||||
gameEngine?.start();
|
||||
setIsRunning(true);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: 55,
|
||||
width: "100%",
|
||||
// borderColor: "red",
|
||||
// borderWidth: 1,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
textAlign: "center",
|
||||
fontSize: 40,
|
||||
fontWeight: "bold",
|
||||
marginTop: topInset / 2,
|
||||
}}
|
||||
>
|
||||
{isRunning ? score : "Press to Resume"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</GameEngine>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
|
@ -0,0 +1,136 @@
|
|||
# React Native Game Engine Expo TypeScript Template
|
||||
|
||||
This template is designed to jumpstart your game development with React Native using the react-native-game-engine library and TypeScript. It comes pre-configured with Expo Dev Client, allowing for a faster and more integrated development workflow.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js
|
||||
- Expo CLI
|
||||
- eas-cli (for building and submitting your app using EAS Build and EAS Submit)
|
||||
|
||||
### Setup
|
||||
|
||||
Clone the repository to get started:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/nightness/react-native-game-engine-expo-typescript-template
|
||||
cd react-native-game-engine-expo-typescript-template
|
||||
```
|
||||
|
||||
Install the dependencies:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
## Using Expo Dev Client
|
||||
|
||||
This project uses `expo-dev-client` to enable a more integrated development workflow, allowing you to build and run your app on simulators/emulators with hot module reloading and access to developer tools.
|
||||
|
||||
### Building for Development
|
||||
|
||||
The `expo-dev-client` allows you to create custom builds of your application for development purposes.
|
||||
|
||||
To create a development build for each platform, run the following command:
|
||||
|
||||
- For iOS:
|
||||
|
||||
```sh
|
||||
npm run build:dev
|
||||
```
|
||||
|
||||
This command generates a `.tar.gz` file containing an "app folder". Extract this folder and drag the app binary into your iOS Simulator to install it.
|
||||
|
||||
- For Android:
|
||||
|
||||
```sh
|
||||
npm run build:dev
|
||||
```
|
||||
|
||||
For Android, this command generates an APK file that can be installed on your emulator or physical device. Drag and drop the APK file into your Android Emulator to install it.
|
||||
|
||||
After building and installing the development client, you can open your project directly from the simulator/emulator. The Expo development menu can be accessed within the simulator/emulator, enabling you to use features like live reloading and debugging.
|
||||
|
||||
### Running the Development Build
|
||||
|
||||
After installing the development build on your emulator/simulator, you can run the project using the `npm start` command. This will start the Metro bundler and enable you to load and test your app with the development build just installed.
|
||||
|
||||
Start the project using the Expo dev client:
|
||||
|
||||
```sh
|
||||
npm run start
|
||||
```
|
||||
|
||||
To run the game on a specific platform, use the corresponding script:
|
||||
|
||||
```sh
|
||||
npm run android # for Android
|
||||
npm run ios # for iOS
|
||||
npm run web # for Web
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `assets`: Contains static assets such as images and sounds.
|
||||
- `game`: Core game logic.
|
||||
- `entities`: Game entity components (e.g., `Balloon.tsx`).
|
||||
- `systems`: Game system logic (e.g., `GameLoop.ts`).
|
||||
- `types.ts`: TypeScript type definitions and interfaces.
|
||||
|
||||
## Modifying the Template
|
||||
|
||||
To modify the template and create your own game, follow these steps:
|
||||
|
||||
1. **Design Your Game**: Plan out your game's mechanics, rules, and entities.
|
||||
2. **Create Game Entities**: Modify or create new TypeScript React components in the `game/entities` directory to represent your game's entities.
|
||||
3. **Implement Game Logic**: Write the game logic inside the `game/systems` directory. The `GameLoop.ts` file is where you will define how your game state updates with time.
|
||||
4. **Update Assets**: Add or replace assets in the `assets` folder as needed for your game.
|
||||
5. **Define Types**: If you create new entities or systems, update the `types.ts` file to define the TypeScript types that correspond to your game's components.
|
||||
6. **Integrate Entities and Systems**: Use the `GameEngine.tsx` component to integrate your entities and systems into the game loop.
|
||||
7. **Launch the Game**: Use the Expo CLI to run and test your game.
|
||||
8. **Build and Publish**: Once your game is complete and tested, follow the Expo documentation to build and publish your game to the iOS App Store and Google Play Store.
|
||||
|
||||
## Building and Submitting
|
||||
|
||||
The `eas.json` file is configured with profiles for local, development, preview, and production builds:
|
||||
|
||||
- `build:adhoc`: Build a local adhoc app binary locally.
|
||||
- `build:dev`: Build a development client app binary locally.
|
||||
- `build:preview`: Increment the app version, build and upload the app to Expo's preview channel.
|
||||
- `build:production`: Increment the app version, build and upload the app to Expo's production channel.
|
||||
- `eas:preview`: Build and submit the app to the preview channel automatically.
|
||||
- `eas:production`: Build and submit the app to the production channel automatically.
|
||||
- `submit:preview`: Submit the app to the preview channel.
|
||||
- `submit:production`: Submit the app to the production channel.
|
||||
- `update`: Update your app on the fly without needing to resubmit to app stores.
|
||||
|
||||
```sh
|
||||
npm run build:adhoc # Build for physical device testing
|
||||
npm run build:dev # Build for Expo development client
|
||||
npm run build:preview # Build for preview
|
||||
npm run eas:preview # Build and submit for preview
|
||||
npm run eas:production # Build and submit for production
|
||||
```
|
||||
|
||||
## Continuous Updates
|
||||
|
||||
With EAS Update, you can keep your app up to date without waiting for app store reviews:
|
||||
|
||||
```sh
|
||||
npm run update:preview # Update the preview channel
|
||||
npm run update:production # Update the production channel
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
File issues on the repository issue tracker.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. Please fork the repository and submit a pull request.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the Apache-2.0 License.
|
10
app.json
10
app.json
|
@ -14,11 +14,10 @@
|
|||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"assetBundlePatterns": ["**/*"],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.nightness.reactnativegameengineexpotypescripttemplate"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
|
@ -28,6 +27,7 @@
|
|||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
}
|
||||
},
|
||||
"jsEngine": "jsc"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,5 @@ export const BalloonSVG = (props) => {
|
|||
<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,61 @@
|
|||
{
|
||||
"cli": {
|
||||
"version": ">= 2.7.1"
|
||||
},
|
||||
"build": {
|
||||
"adhoc": {
|
||||
"distribution": "internal",
|
||||
"channel": "internal"
|
||||
},
|
||||
"adhoc:dev": {
|
||||
"distribution": "internal",
|
||||
"channel": "internal",
|
||||
"developmentClient": true
|
||||
},
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
"distribution": "internal",
|
||||
"android": {
|
||||
"buildType": "apk",
|
||||
"image": "latest"
|
||||
},
|
||||
"ios": {
|
||||
"simulator": true,
|
||||
"resourceClass": "m-medium"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"channel": "preview",
|
||||
"android": {
|
||||
"buildType": "app-bundle",
|
||||
"image": "latest"
|
||||
},
|
||||
"ios": {
|
||||
"resourceClass": "m-medium"
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"channel": "production",
|
||||
"android": {
|
||||
"buildType": "app-bundle",
|
||||
"image": "latest"
|
||||
},
|
||||
"ios": {
|
||||
"resourceClass": "m-medium"
|
||||
}
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"preview": {
|
||||
"android": {
|
||||
"releaseStatus": "draft"
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"android": {
|
||||
"track": "production",
|
||||
"releaseStatus": "completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +1,53 @@
|
|||
import React from 'react'
|
||||
import Matter from 'matter-js'
|
||||
import { ColorValue, View } from 'react-native'
|
||||
|
||||
import { BalloonSVG } from '@svg'
|
||||
import { Position2D, Size2D } from '@types'
|
||||
import { GameEntity, 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>
|
||||
);
|
||||
const Balloon = ({ body, color }: GameEntity) => {
|
||||
const heightBody = body.bounds.max.y - body.bounds.min.y;
|
||||
const widthBody = body.bounds.max.x - body.bounds.min.x;
|
||||
|
||||
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,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<BalloonSVG color={color} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default (
|
||||
label: string,
|
||||
world: Matter.Composite,
|
||||
color: ColorValue,
|
||||
pos: Position2D,
|
||||
size: Size2D
|
||||
) => {
|
||||
const body = Matter.Bodies.rectangle(pos.x, pos.y, size.width, size.height, {
|
||||
label,
|
||||
isStatic: false,
|
||||
id: 5,
|
||||
frictionAir: 0.5,
|
||||
});
|
||||
Matter.Composite.add(world, body);
|
||||
|
||||
return {
|
||||
body,
|
||||
color,
|
||||
pos,
|
||||
renderer: Balloon,
|
||||
};
|
||||
|
||||
export default (
|
||||
label: string,
|
||||
world: Matter.Composite,
|
||||
color: ColorValue,
|
||||
pos: Position2D,
|
||||
size: Size2D
|
||||
) => {
|
||||
const body = Matter.Bodies.circle(pos.x, pos.y, 50, {
|
||||
label,
|
||||
isStatic: false,
|
||||
id: 5,
|
||||
// restitution: 0.4,
|
||||
// friction: 1,
|
||||
frictionAir: 0.5,
|
||||
// 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,
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -2,9 +2,9 @@ import Matter from 'matter-js'
|
|||
import React from 'react'
|
||||
import { ColorValue, View } from 'react-native'
|
||||
|
||||
import { Position2D, Size2D } from '@types'
|
||||
import { GameEntity, Position2D, Size2D } from '@types'
|
||||
|
||||
const Wall = ({ body, color }: any) => {
|
||||
const Wall = ({ body, color }: GameEntity) => {
|
||||
const widthBody = body.bounds.max.x - body.bounds.min.x
|
||||
const heightBody = body.bounds.max.y - body.bounds.min.y
|
||||
|
||||
|
|
|
@ -1,113 +1,118 @@
|
|||
import { Dimensions } from "react-native";
|
||||
import Matter from "matter-js";
|
||||
|
||||
import { windowHeight, windowWidth } from "@game";
|
||||
import { Balloon, Wall } from ".";
|
||||
import { Balloon, Wall } from ".";
|
||||
import { GameEngineEntities } from "@types";
|
||||
|
||||
export const entities = (restart: boolean = false) => {
|
||||
let engine = Matter.Engine.create(undefined, {
|
||||
export function entities(): GameEngineEntities {
|
||||
const engine = Matter.Engine.create({
|
||||
enableSleeping: false,
|
||||
gravity: { x: 0, y: 0.001 },
|
||||
gravity: { x: 0, y: 1.75 },
|
||||
} as Matter.IEngineDefinition);
|
||||
|
||||
let world = engine.world;
|
||||
const topInset = (global as any).topInset; // for notch handling
|
||||
const world = engine.world;
|
||||
const [top, bottom] = [
|
||||
global.topInset,
|
||||
global.bottomInset,
|
||||
global.leftInset,
|
||||
global.rightInset,
|
||||
];
|
||||
|
||||
const newBalloon = () => Balloon(
|
||||
const newBalloon = () =>
|
||||
Balloon(
|
||||
"Balloon",
|
||||
world,
|
||||
"red",
|
||||
{
|
||||
x: Math.random() * (windowWidth - 100) + 50,
|
||||
y: 150,
|
||||
y: 200,
|
||||
},
|
||||
{ width: 50, height: 50 }
|
||||
{ width: 40, height: 50 }
|
||||
);
|
||||
|
||||
let entities = {
|
||||
const entities = {
|
||||
physics: { engine, world },
|
||||
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 }
|
||||
),
|
||||
// LeftWall: Wall(
|
||||
// "LeftWall",
|
||||
// world,
|
||||
// "orange",
|
||||
// { x: 0 - 25, y: windowHeight / 2 },
|
||||
// { height: windowHeight - topInset - bottomInset + 560, width: 50 }
|
||||
// ),
|
||||
// RightWall: Wall(
|
||||
// "RightWall",
|
||||
// world,
|
||||
// "orange",
|
||||
// { x: windowWidth + 25, y: windowHeight / 2 },
|
||||
// { height: windowHeight - topInset - bottomInset + 560, width: 50 }
|
||||
// ),
|
||||
Ceiling: Wall(
|
||||
"Ceiling",
|
||||
world,
|
||||
"orange",
|
||||
{ x: 0, y: 0 },
|
||||
{ height: 110 + topInset, width: windowWidth * 2 }
|
||||
{ height: 110 + top, width: windowWidth * 2 }
|
||||
),
|
||||
Floor: Wall(
|
||||
"Floor",
|
||||
world,
|
||||
"orange",
|
||||
{ x: windowWidth / 2, y: windowHeight + 60 },
|
||||
{ height: 60, width: windowWidth }
|
||||
{ x: windowWidth / 2, y: windowHeight - top },
|
||||
{ height: 60 + bottom, width: windowWidth }
|
||||
),
|
||||
};
|
||||
|
||||
Matter.Events.on(
|
||||
engine,
|
||||
"removeBalloon",
|
||||
({ pairs }: Matter.IEventCollision<any>) => {
|
||||
// pairs.forEach((pair: Matter.IPair) => {
|
||||
// if (pair.bodyA.label === "Balloon" && pair.bodyB.label === "Floor") {
|
||||
// Matter.Events.trigger(engine, "removeBalloon");
|
||||
// }
|
||||
Matter.Events.on(engine, "removeBalloon", () => {
|
||||
// Remove old balloon
|
||||
const balloonBody = entities.Balloon.body;
|
||||
Matter.World.remove(world, balloonBody, true);
|
||||
|
||||
// Remove old balloon
|
||||
const balloonBody = entities.Balloon.body;
|
||||
Matter.World.remove(world, balloonBody, true);
|
||||
|
||||
// Add new Balloon
|
||||
entities.Balloon = newBalloon();
|
||||
// @ts-ignore
|
||||
Matter.World.add(world, entities.Balloon);
|
||||
// });
|
||||
});
|
||||
// Add new Balloon
|
||||
entities.Balloon = newBalloon();
|
||||
// @ts-expect-error, for some reason this doesn't work as expected if passed as entities.Balloon.body
|
||||
Matter.World.add(world, entities.Balloon);
|
||||
});
|
||||
|
||||
Matter.Events.on(
|
||||
engine,
|
||||
"collisionStart",
|
||||
({ pairs }: Matter.IEventCollision<any>) => {
|
||||
for (var i = 0, j = pairs.length; i != j; ++i) {
|
||||
({ pairs }: Matter.IEventCollision<object>) => {
|
||||
for (let 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
|
||||
);
|
||||
|
||||
// We only want collisions between the balloon and the floor
|
||||
if (
|
||||
(bodyA.label !== "Balloon" && bodyB.label !== "Balloon") ||
|
||||
(bodyA.label !== "Floor" && bodyB.label !== "Floor")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const balloonBody = entities.Balloon.body;
|
||||
const floorBody = entities.Floor.body;
|
||||
|
||||
// Remove balloon if it hits the floor
|
||||
Matter.World.remove(world, balloonBody, true);
|
||||
|
||||
// Subtract a point from the score
|
||||
const gameEngine = (global as any).gameEngine;
|
||||
const gameEngine = global.gameEngine!;
|
||||
gameEngine.dispatch({
|
||||
type: "subtractFromScore",
|
||||
});
|
||||
|
||||
entities.Balloon = newBalloon();
|
||||
|
||||
// Add new Balloon
|
||||
// @ts-ignore
|
||||
entities.Balloon = newBalloon();
|
||||
// @ts-expect-error, for some reason this doesn't work if passed as entities.Balloon.body
|
||||
Matter.World.add(world, entities.Balloon);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return entities;
|
||||
};
|
||||
}
|
||||
|
||||
export const useEntities = () => {
|
||||
return {
|
||||
entities
|
||||
}
|
||||
}
|
||||
entities,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
import { Dimensions } from "react-native";
|
||||
|
||||
export { windowHeight, windowWidth } from "@game";
|
||||
|
||||
import Wall from "./Wall";
|
||||
import Balloon from './Balloon';
|
||||
import Balloon from "./Balloon";
|
||||
|
||||
export {
|
||||
Wall,
|
||||
Balloon
|
||||
};
|
||||
export { Wall, Balloon };
|
||||
|
||||
export * from './entities';
|
||||
export * from "./entities";
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import { IGameEngine } from "@types";
|
||||
|
||||
declare global {
|
||||
var topInset: number;
|
||||
var bottomInset: number;
|
||||
var leftInset: number;
|
||||
var rightInset: number;
|
||||
var gameEngine: IGameEngine | null;
|
||||
}
|
||||
|
||||
export {};
|
|
@ -0,0 +1,78 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Text, TouchableOpacity } from "react-native";
|
||||
import { GameEngine as ReactGameEngine } from "react-native-game-engine";
|
||||
|
||||
import { entities } from "@entities";
|
||||
import { GameLoop } from "@systems";
|
||||
import { GameEngineEvent, IGameEngine } from "@types";
|
||||
|
||||
export default function GameEngine() {
|
||||
const [isRunning, setIsRunning] = React.useState(true);
|
||||
const [score, setScore] = useState(0);
|
||||
const [gameEngine, setGameEngine] = useState<IGameEngine | null>(null);
|
||||
const [top, bottom, left, right] = [global.topInset, global.bottomInset, global.leftInset, global.rightInset]
|
||||
|
||||
const gameEntities = entities();
|
||||
// const { engine, world } = gameEntities.physics;
|
||||
// const gameEntities = entities();
|
||||
|
||||
useEffect(() => {
|
||||
if (gameEngine) {
|
||||
gameEngine.swap(entities());
|
||||
}
|
||||
global.gameEngine = gameEngine;
|
||||
}, [gameEngine]);
|
||||
|
||||
return (
|
||||
<ReactGameEngine
|
||||
ref={(ref) => setGameEngine(ref as IGameEngine)}
|
||||
systems={[GameLoop]}
|
||||
entities={gameEntities}
|
||||
running={isRunning}
|
||||
onEvent={({ type }: GameEngineEvent) => {
|
||||
switch (type) {
|
||||
case "addToScore": {
|
||||
setScore(score => score + 1);
|
||||
break;
|
||||
}
|
||||
case "subtractFromScore": {
|
||||
setScore(score => score - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}}
|
||||
style={{ position: "absolute", top, left, right, bottom }}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
// gameEngine?.swap(entities());
|
||||
if (isRunning) {
|
||||
gameEngine?.stop();
|
||||
setIsRunning(false);
|
||||
} else {
|
||||
gameEngine?.start();
|
||||
setIsRunning(true);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: 55,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
textAlign: "center",
|
||||
fontSize: 40,
|
||||
fontWeight: "bold",
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
{isRunning ? score : "Press to Resume"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</ReactGameEngine>
|
||||
);
|
||||
}
|
|
@ -1,26 +1,27 @@
|
|||
import Matter, { Vector } from "matter-js";
|
||||
import { GameEngineEntities, GameEntity } from "@types";
|
||||
import Matter from "matter-js";
|
||||
import {
|
||||
GameEngineUpdateEventOptionType,
|
||||
TouchEvent,
|
||||
} from "react-native-game-engine";
|
||||
|
||||
import { windowHeight, windowWidth } from "@game";
|
||||
import { Balloon } from "@entities";
|
||||
|
||||
export const GameLoop = (
|
||||
entities: any,
|
||||
entities: GameEngineEntities,
|
||||
{ touches, time, dispatch }: GameEngineUpdateEventOptionType
|
||||
) => {
|
||||
let engine = entities.physics.engine;
|
||||
let world = entities.physics.world;
|
||||
const engine = entities.physics.engine;
|
||||
|
||||
touches
|
||||
.filter((t: TouchEvent) => t.type === "press")
|
||||
.forEach((t: TouchEvent) => {
|
||||
const balloonBody = entities.Balloon.body;
|
||||
const balloonPos = balloonBody.position;
|
||||
const { locationX, locationY } = t.event;
|
||||
if (locationX < 50 && locationY < 50) {
|
||||
const balloonBody = (entities.Balloon as GameEntity).body;
|
||||
const balloonPos = balloonBody.position as Matter.Vector;
|
||||
|
||||
const { pageX, pageY } = t.event;
|
||||
if (
|
||||
Math.abs(pageX - balloonPos.x) < 100 &&
|
||||
Math.abs(pageY - balloonPos.y) < 100
|
||||
) {
|
||||
dispatch({
|
||||
type: "addToScore",
|
||||
});
|
||||
|
|
|
@ -1,18 +1,39 @@
|
|||
import { ColorValue } from "react-native";
|
||||
import { GameEngine } from "react-native-game-engine";
|
||||
|
||||
export interface Position2D {
|
||||
x: number;
|
||||
y: number;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Size2D {
|
||||
width: number;
|
||||
height: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface IGameEngine extends GameEngine {
|
||||
stop: () => void;
|
||||
start: () => void;
|
||||
swap: (newEntities: Promise<any> | any) => void;
|
||||
dispatch: (event: string) => void;
|
||||
}
|
||||
export interface IGameEngine<T = never> extends GameEngine {
|
||||
stop: () => void;
|
||||
start: () => void;
|
||||
swap: (newEntities: Promise<GameEngineEntities> | GameEngineEntities) => void;
|
||||
dispatch: (event: GameEngineEvent<T>) => void;
|
||||
}
|
||||
|
||||
export interface GameEngineEvent<T = never> {
|
||||
type: string;
|
||||
[key: string]: T | string;
|
||||
}
|
||||
|
||||
export interface GameEntity {
|
||||
body: Matter.Body;
|
||||
color: ColorValue;
|
||||
pos: Position2D;
|
||||
renderer: React.ComponentType<GameEntity>;
|
||||
}
|
||||
|
||||
export interface GameEngineEntities {
|
||||
physics: {
|
||||
engine: Matter.Engine;
|
||||
world: Matter.World;
|
||||
};
|
||||
[key: string]: GameEntity | { engine: Matter.Engine; world: Matter.World };
|
||||
}
|
||||
|
|
67
package.json
67
package.json
|
@ -3,36 +3,57 @@
|
|||
"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"
|
||||
"start": "npx expo start --dev-client",
|
||||
"android": "npx expo start --android",
|
||||
"ios": "npx expo start --ios",
|
||||
"web": "npx expo start --web",
|
||||
"build:adhoc": "eas build --profile adhoc --local",
|
||||
"build:dev": "eas build --profile development --local",
|
||||
"build:preview": "eas build --profile preview --local",
|
||||
"build:production": "eas build --profile production --local",
|
||||
"eas:preview": "eas build --profile preview --auto-submit",
|
||||
"eas:production": "eas build --profile production --auto-submit",
|
||||
"submit:preview": "eas submit --profile preview",
|
||||
"submit:production": "eas submit --profile production",
|
||||
"update": "eas update --auto",
|
||||
"update:preview": "eas update --branch preview --message \"Updating Preview\"",
|
||||
"update:production": "eas update --branch production --message \"Updating Production\"",
|
||||
"pod:update": "pod repo update"
|
||||
},
|
||||
"dependencies": {
|
||||
"expo": "~45.0.0",
|
||||
"expo-status-bar": "~1.3.0",
|
||||
"expo": "^49.0.21",
|
||||
"expo-dev-client": "~2.4.12",
|
||||
"expo-status-bar": "~1.6.0",
|
||||
"matter-js": "^0.18.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-native": "0.68.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-native": "0.72.6",
|
||||
"react-native-game-engine": "^1.2.0",
|
||||
"react-native-reanimated": "~2.8.0",
|
||||
"react-native-safe-area-context": "4.2.4",
|
||||
"react-native-svg": "12.3.0",
|
||||
"react-native-web": "0.17.7"
|
||||
"react-native-reanimated": "~3.3.0",
|
||||
"react-native-safe-area-context": "4.6.3",
|
||||
"react-native-svg": "13.9.0",
|
||||
"react-native-web": "~0.19.6"
|
||||
},
|
||||
"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"
|
||||
"@babel/core": "^7.23.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0-0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0-0",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.0.0-0",
|
||||
"@babel/plugin-transform-shorthand-properties": "^7.0.0-0",
|
||||
"@babel/plugin-transform-template-literals": "^7.0.0-0",
|
||||
"@babel/preset-env": "^7.23.7",
|
||||
"@expo/webpack-config": "^19.0.0",
|
||||
"@types/matter-js": "^0.19.5",
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/react-native": "^0.73.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.18.0",
|
||||
"@typescript-eslint/parser": "^6.18.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"rxjs": "6.6.7"
|
||||
}
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
{
|
||||
"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"],
|
||||
}
|
||||
}
|
||||
"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"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*", "game/global.d.ts"],
|
||||
"exclude": ["node_modules", "**/*.spec.ts"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue