• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

bberak/react-native-game-engine: A lightweight Game Engine for React Native

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

bberak/react-native-game-engine

开源软件地址:

https://github.com/bberak/react-native-game-engine

开源编程语言:

JavaScript 100.0%

开源软件介绍:

React Native Game Engine

React Native Game Engine · npm version mit license

Some components that make it easier to construct dynamic and interactive scenes using React Native.

If you are looking for the React (Web) version of this library, go to react-game-engine.

Table of Contents

Examples

Take a look at Studious Bear, a super-polished puzzle game with great visuals and music. One of the first published games to use RNGE.

See the React Native Game Engine Handbook for a complimentary app, examples and ideas.

Single Touch Preview Multi Touch Preview Rigid Bodies Preview

Quick Start

If you've used react-native-game-engine before and understand the core concepts, take a look at react-native-game-engine-template. It's a sort of game kickstarter project that allows you to prototype ideas quickly and comes preloaded with a bunch of stuff like:

  • A 3D renderer
  • Physics
  • Particle system
  • Crude sound API
  • Sprite support with animations
  • Etc

Otherwise, continue reading the quick start guide below.


Firstly, install the package to your project:

npm install --save react-native-game-engine

Then import the GameEngine component:

import { GameEngine } from "react-native-game-engine"

Let's code a scene that incorporates some multi-touch logic. To start with, let's create some components that can be rendered by React. Create a file called renderers.js:

import React, { PureComponent } from "react";
import { StyleSheet, View } from "react-native";

const RADIUS = 20;

class Finger extends PureComponent {
  render() {
    const x = this.props.position[0] - RADIUS / 2;
    const y = this.props.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 { Finger };

Next, let's code our logic in a file called systems.js:

const MoveFinger = (entities, { touches }) => {

  //-- I'm choosing to update the game state (entities) directly for the sake of brevity and simplicity.
  //-- There's nothing stopping you from treating the game state as immutable and returning a copy..
  //-- Example: return { ...entities, t.id: { UPDATED COMPONENTS }};
  //-- That said, it's probably worth considering performance implications in either case.

  touches.filter(t => t.type === "move").forEach(t => {
    let finger = entities[t.id];
    if (finger && finger.position) {
      finger.position = [
        finger.position[0] + t.delta.pageX,
        finger.position[1] + t.delta.pageY
      ];
    }
  });

  return entities;
};

export { MoveFinger };

Finally let's bring it all together in our index.ios.js (or index.android.js):

import React, { PureComponent } from "react";
import { AppRegistry, StyleSheet, StatusBar } from "react-native";
import { GameEngine } from "react-native-game-engine";
import { Finger } from "./renderers";
import { MoveFinger } from "./systems"

export default class BestGameEver extends PureComponent {
  constructor() {
    super();
  }

  render() {
    return (
      <GameEngine
        style={styles.container}
        systems={[MoveFinger]}
        entities={{
          1: { position: [40,  200], renderer: <Finger />}, //-- Notice that each entity has a unique id (required)
          2: { position: [100, 200], renderer: <Finger />}, //-- and a renderer property (optional). If no renderer
          3: { position: [160, 200], renderer: <Finger />}, //-- is supplied with the entity - it won't get displayed.
          4: { position: [220, 200], renderer: <Finger />},
          5: { position: [280, 200], renderer: <Finger />}
        }}>

        <StatusBar hidden={true} />

      </GameEngine>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#FFF"
  }
});

AppRegistry.registerComponent("BestGameEver", () => BestGameEver);

Build and run. Each entity is a "finger" and is assigned to a particular touch id. The touch ids increase as you place more fingers on the screen. Move your fingers around the screen to move the entities. As an exercise, try add a system that will insert another finger entity into the game state when a "start" touch event is encountered. What about adding a system that removes the closest entity from the game state when a "long-press" is encountered?

If you're curious, our GameEngine component is a loose implementation of the Compenent-Entity-System pattern - we've written up a quick intro here.

GameEngine Properties

Prop Description Default
systems An array of functions to be called on every tick. []
entities An object containing your game's initial entities. This can also be a Promise that resolves to an object containing your entities. This is useful when you need to asynchronously load a texture or other assets during the creation of your entities or level. {} or Promise
renderer A function that receives the entities and needs to render them on every tick. (entities, screen, layout) => { /* DRAW ENTITIES */ } DefaultRenderer
touchProcessor A function that can be used to override the default touch processing behavior DefaultTouchProcessor
timer An object that can be used to override the default timer behavior new DefaultTimer()
running A boolean that can be used to control whether the game loop is running or not true
onEvent A callback for being notified when events are dispatched undefined
style An object containing styles for the root container undefined
children React components that will be rendered after the entities undefined

GameEngine Methods

Method Description Args
stop Stop the game loop NA
start Start the game loop. NA
swap A method that can be called to update your game with new entities. Can be useful for level switching etc. You can also pass a Promise that resolves to an entities object into this method. {} or Promise
dispatch A method that can be called to dispatch events. The event will be received by the systems and any onEvent callbacks event

FAQ

Is React Native Game Engine suitable for production quality games?

This depends on your definition of production quality. You're not going to make a AAA title with RNGE. You could however create some more basic games (doesn't mean they can't be fun games), or even jazz up your existing business applications with some interactive eye candy.

Simple turn-based games, side-scrollers and platformers with a handful of entites and simple physics would be feasible. Bullet-hell style games with many enemies, particles and effects on the screen at one time will struggle with performance - for these sorts of projects React Native is probably not the right choice of technology at the moment.

Lastly, for quick prototyping, self-education, and personal projects - React Native (and RNGE) should be suitable tools. For large projects that are commercial in nature, my recommendation would be to take a look at more established platforms like Godot and Unity first.

Do you know of any apps that currently utilize this library?

Studious Bear and React Native Donkey Kong both use this library. The React Native Game Engine Handbook is a complimentary app that showcases some examples and ideas. If you're aware of any others or wouldn't mind a shameless plug here - please reach out.

How do I manage physics?

RNGE does not come with an out-of-the-box physics engine. We felt that this would be an area where the game designers should be given greater liberty. There are lots of JS-based physics engines out there, each with their pros and cons. Check out Matter JS if you're stuck.

Do I have a choice of renderers?

How you render your entities is up to you. You can use the stand React Native components (View, Image) or try react-native-svg or go full exotic with gl-react-native.

RNGE doesn't give me sensor data out of the box - what gives?

I felt that this would be a nice-to-have and for most use cases it would not be required. Hence, I didn't want to burden RNGE users with any native linking or additional configuration. I was also weary about any unnecessary performance and battery costs. Again, it is easy to integrate into the GameEngine and then RNGE Handbook will have an example using react-native-sensors.

Is this compatible with Android and iOS?

Yes.

Won't this kind of be harsh on the battery?

Well kinda.. But so will any game really! It's a bit of a trade-off, hopefully it's worthwhile!

Introduction

This package contains only two components:

  • GameLoop
  • GameEngine

Both are standalone components. The GameLoop is a subset of the GameEngine and gives you access to an onUpdate callback that fires every 16ms (or roughly 60 fps). On top of this, the GameLoop will supply a reference to the screen (via Dimensions.get("window")), touch events for multiple fingers (start, end, press, long-press, move) and time + deltas. The GameLoop is useful for simple interactive scenes, and pretty much stays out of your way.

The GameEngine is more opinionated and is a react-friendly implementation of the Component-Entity-Systems pattern. It provides the same features out of the box as the GameEngine but also includes a crude event/signaling pipeline for communication between your game and your other React Native components. You probably want to use the GameEngine to implement slightly more complex games and interactive scenes.

The Game Loop

The game loop is a common pattern in game development and other interactive programs. It loosely consists of two main functions that get called over and over again: update and draw.

The update function is responsible for calculating the next state of your game. It updates all of your game objects, taking into consideration physics, ai, movement, input, health/fire/damage etc. We can consider this the logic of your game.

Once the update function has done its thing - the draw function is responsible for taking the current state of the game and rendering it to the screen. Typically, this would include drawing characters, scenery and backgrounds, static or dynamic objects, bad guys, special effects and HUD etc.

Ideally, both functions complete within 16ms, and we start the next iteration of the loop until some loop-breaking condition is encountered: pause, quit, game over etc. This might seem like a lot of processing overhead, but unlike regular applications, games are highly interactive and ever changing. The game loop affords us full control over scenes - even when no user input or external events have fired.

The Game Loop vs React Native

A typical React Native app will only redraw itself when this.setState() is called on a component with some new state (for lack of better words). Often times, this is a direct response to user input (button presses, keystrokes, swipes) or other event (WebSocket callbacks, push notifications, etc).

This works perfectly fine (and is even ideal) for a business-oriented app - but it doesn't give the developer fine grained control to create highly interactive and dynamic scenes.

Unlike most other software, games keep moving even when the user isn’t providing input. If you sit staring at the screen, the game doesn’t freeze. Animations keep animating. Visual effects dance and sparkle. If you’re unlucky, that monster keeps chomping on your hero.

This is the first key part of a real game loop: it processes user input, but doesn’t wait for it. The loop always keeps spinning - Robert Nystrom

That said, React Native and game loops are not mutually exclusive, and we can use React Native Game Engine to bridge the two paradigms.

Using the GameLoop Component

The GameLoop component is suitable for simple scenes and interactions only. For more complex scenes and games, please take a look at the GameEngine component and have a quick read through Managing Complexity with Component Entity Systems

Firstly, install the package to your project:

npm install --save react-native-game-engine

Then import the GameLoop component:

import { GameLoop } from "react-native-game-engine"

Let's code a basic scene with a single moveable game object. Add this into your index.ios.js (or index.android.js):

import React, { PureComponent } from "react";
import { AppRegistry, StyleSheet, Dimensions, View } from "react-native";
import { GameLoop } from "react-native-game-engine";

const { width: WIDTH, height: HEIGHT } = Dimensions.get("window");
const RADIUS = 25;

export default class BestGameEver extends PureComponent {
  constructor() {
    super();
    this.state = {
      x: WIDTH / 2 - RADIUS,
      y: HEIGHT / 2 - RADIUS
    };
  }

  updateHandler = ({ touches, screen, layout, time }) => {
    let move = touches.find(x => x.type === "move");
    if (move) {
      this.setState({
        x: this.state.x + move.delta.pageX,
        y: this.state.y + move.delta.pageY
      });
    }
  };

  render() {
    return (
      <GameLoop style={styles.container} onUpdate={this.updateHandler}>

        <View style={[styles.player, { left: this.state.x, top: this.state.y }]} />

      </GameLoop>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#FFF"
  },
  player: {
    position: "absolute",
    backgroundColor: "pink",
    width: RADIUS * 2,
    height: RADIUS * 2,
    borderRadius: RADIUS * 2
  }
});

AppRegistry.registerComponent("BestGameEver", () => BestGameEver);

Behind the Scenes

  • The GameLoop starts a timer using requestAnimationFrame(fn). Effectively, this is our game loop.
  • Each iteration through the loop, the GameLoop will call the function passed in via props.onUpdate.
  • Our updateHandler looks for any move touches that were made between now and the last time through the loop.
  • If found, we update the position of our lone game object using this.setState().

Where is the Draw Function

Nice observation! Indeed, there is none. The logic of our scene is processed in the updateHandler function, and our drawing is handled by our component's out-of-the-box render() function.

All we've done here is hookup a timer to a function that fires every ~16ms, and used this.setState() to force React Native to diff the changes in our scene and send them across the bridge to the host device. React Native Game Engine only takes care of the game timing and input processing for us.

Managing Complexity with Component Entity Systems

Typically, game developers have used OOP to implement complex game objects and scenes. Each game object is instantiated from a class, and polymorphism allows code re-use and behaviors to be extended through inheritance. As class hierarchies grow, it becomes increasingly difficult to create new types of game entities without duplicating code or seriously re-thinking the entire class hierarchy.

               [GameEntity]
                    |
                    |
                [Vehicle]
               /    |    \
              /     |     \
             /      |      \
            /       |       \
   [Terrestrial] [Marine] [Airborne]
           |        |        |
           |        |        |
         [Tank]   [Boat]   [Jet]

How do we insert a new terrestrial and marine-based vehicle - say a Hovercraft - into the class hierarchy?

One way to address these problems is to favor composition over inheritance. With this approach, we break out the attributes and behaviours of our various game entities into decoupled, encapsulated and atomic components. This allows us to be really imaginative with the sorts of game entities we create because we can easily compose them with components from disparate domains and co


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
orangeduck/Corange: Pure C Game Engine发布时间:2022-06-06
下一篇:
phoboslab/Impact: HTML5 Game Engine发布时间:2022-06-06
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap