Using SpriteKit inside a React Native app

This article explains how to integrate a SpriteKit based game into a React Native app.

Backstory

I set out to build a simple 2D iOS game during my Christmas staycation to realize my long time dream of game development. You can find the game at playarroe.com Having already developed Eat or Not using React Native, it was my first choice for mobile app development. Though RN performance is awesome for simple transitions and animations, it’s not meant to be game engine. After some researching, I picked Apple’s SpriteKit as my game engine.

SpriteKit is a graphics rendering and animation infrastructure that you can use to animate arbitrary textured images, otherwise known as sprites. SpriteKit provides a traditional rendering loop that alternates between determining the contents of and rendering frames.

Setup

First, setup the React Native project.

Follow the super simple instructions in the React Native docs and you should see this.

NOTE: This post will work only on iOS 9.0 and higher due to SpriteKit limitations.

The game!

For the purpose of this demonstration, my goal is to pass a number from the RN app which will be displayed on the SK end. A timer on the SK end will send back a randomly generated number to RN. You can think of this as passing some config from RN to your game for bootstrapping and the game returning the score back.

Ok, enough talk, let’s start coding!

STEP 1: Render a SpriteKit scene from ReactNative

Let’s create our base screen to launch the game. This is RN 101.

index.ios.js

export default class RNSK extends Component {
  constructor(props) {
super(props);
this.state = {
lastScore: 0,
};
}
  startGame() {
// render game screen
}

render() {
return (
<View style={styles.container}>
<Text style={styles.text}>
Welcome to React Native SpriteKit demo!
</Text>
<Text style={styles.text}>
Last Score: {this.state.lastScore}
</Text>
<TouchableOpacity onPress={this.startGame.bind(this)}>
<Text style={styles.text}>
START GAME
</Text>
</TouchableOpacity>
</View>
);
}
}

For all you JS developers, here comes the complicated part — Swift coding! Let me come clean by saying that I’ve never done any Swift or SpriteKit before. But, once you understand things a bit, it’s actually surprisingly simple to wire things up. I just followed one of the many Swift and SpriteKits available online.

First, let’s start by creating a GameScene.swift inside XCode. This scene is where all the game logic will reside. For the sake of organization, I created a group inside XCode to hold all my game related files. Note the label that I’ve added that should display “GameScene” at the center of the screen when rendered.

Now, we need a SKView to present our scene. This will be the view that act as a controller from the RN side. I like to consider this as outside the game, so I created a GameView.swift at the top level.

Time to create the bridge between the native view and RN. I followed the docs and came up with my RCTGameManager.m

Back on the JS side, lets load this view and see what happens

const GameView = requireNativeComponent('RCTGame', null);
export default class RNSK extends Component {
constructor(props) {
super(props);
this.state = {
lastScore: 0,
inGame: false,
};
}
startGame() {
// render game screen
this.setState({
inGame: true,
})
}

render() {
if (this.state.inGame) {
return (
<GameView
style={{
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
}}
/>
);
}
return (
<View style={styles.container}>
<Text style={styles.text}>
Welcome to React Native SpriteKit demo!
</Text>
<Text style={styles.text}>
Last Score: {this.state.lastScore}
</Text>
<TouchableOpacity onPress={this.startGame.bind(this)}>
<Text style={styles.text}>
START GAME
</Text>
</TouchableOpacity>
</View>
);
}
}

When you click on the START GAME game button, this is what you’ll see. Progress, but where the $^&@# is the “GameScene” label that I added?!?!?!?.

Let me save you the hours of head banging :) So, when you render a view from RN through the bridge, RN will call init() on the view but will set the bounds on it at a later time. So, the scene that’s been rendered essentially has a width and height of 1. We need to listen for the changes in bounds and re-render the scene once it’s ready. Notice the didSet listener that I’ve added to the view bounds.

Now, you’d see the label!

Step 2: Pass data from RN to SK

As stated in the docs, lets modify a few files to include a property that will be passed in

Back in index.ios.js

...
return (
<GameView
input={this.state.input}
style={{
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
}}
/>
);
...

Tada! you should see when you click on START GAME.

Step 3: Pass data from SpriteKit back to RN

Again, as stated in the docs, we’re going to use events.

Add the onEnd property to RCTGameManager.m and GameView

Note: In order for RCTBubblingEventBlock to be available in Swift, it needs to be imported in the Bridging-Header.h

Back in RN

...
onEnd(e) {
this.setState({
lastScore: e.nativeEvent.score,
inGame: false,
});
}

render() {
if (this.state.inGame) {
return (
<GameView
input={this.state.input}
onEnd={this.onEnd.bind(this)}
style={{
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
}}
/>
);
}
...

Now, 1 second after clicking START GAME, you’d see a random number

That’s all folks!

Hope this article helps you in some way. I intentionally glossed over the SpriteKit parts for two reasons — firstly, this article was not meant for that; secondly, I’m still a super beginner at Swift and did not want to share my half baked understanding.

All the code for this demo is available at

Don’t forget to checkout the game at playarroe.com.

Feedback/comments below or tweet at @sharathprabhal

Hit the recommend button if you want others to read this.

Check out other interesting React Native articles at