TL;DR #1: Hate reading? Watch the video (please subscribe if you like the content)
TL;DR #2: Just want the code? Grab it here: https://github.com/lepunk/react-native-videos/tree/master/Snake
Snake is a classic video game made popular by the Nokia phones in the 90s. The gameplay is simple: you control the head of the snake and your objective is to eat food. Every time you eat food the snake’s tail grows. (in the classic version) If you hit the wall or hit your own tail, you die.
For me this game is especially nostalgic, as this was the first game that I successfully cloned in Turbo Pascal, during the first year of high school. I even sold a couple of copies to my class mates. #profit
In order to re-create this game I decided to use the (to my knowledge) only game engine currently available for React Native: react-native-game-engine
The engine itself is pretty barebones, but it does implement the basics you need to develop a game:
- A game loop
- An entity rendering system
- It handles touch events
- It has the ability to send and receive custom events to and from the engine
Installing the engine is simple:
npm install --save react-native-game-engine
The engine requires you to define two things:
- A set of entities to be rendered at each tick.
- One or more “systems”. These are essentially instructions to be executed on each tick.
Snake is super simple. If you think about it there are only 3 entities we need to implement
The “Head” will be the character our player controls. It will have an xspeed, a yspeed, a position and a size attribute. In our app the size will be a constant. position will be a list of [x,y] coordinates. xspeed and yspeed could take up the values of 1, 0 or -1. If xspeed is either 1 or -1 then yspeed must be 0 and similarly if yspeed is either 1 or -1 then xspeed should be zero (the snake can’t go diagonally). We will implement the rendering of Head in the <Head /> component
Tail will represent a list of blocks on the screen, that follow our head. This tail will grow as Head eats more and more Food. It will have a size prop (which will be a constant) and an elements prop, which initially will be an empty list and as the Head eats the Food we will keep adding [x,y] coordinates to it.
Our Food entity will be placed on the playing area randomly, and it will stay it’s place until the Head collides with it. Once it happens we should re-render it to another random location. It will have a position prop which will be a list of [x,y] coordinates and a size, which will be a constant.
Let’s get to it
I like to define some constants in a Constants.js file in my apps so let’s do that.
After this let’s set up our index.js with the main scene and some control buttons.
Ok, a couple of things we haven’t discussed yet:
- Notice we are using a running state variable and we are passing it in to the GameEngine as a prop. It will be very useful to stop the game loop if the user dies.
- We keep a reference to the GameEngine in the this.engine instance variable. We will use this to call certain methods on the GameEngine
- We are passing in an onEvent event handler to the GameEngine. This method will be executed every time a new event is dispatched using GameEngine.dispatch. Dispatch is the way we can communicate between the main app and the game loop. We will emit different type of events.
- Notice these props on our Head entity: nextMove: 10, updateFrequency: 10
The out of the box game loop is close to 60 frame per sec. We don’t want to update our Head’s position on every tick so we will use nextMove and updateFrequency to slow the update down. More on this later.
- I’ve added some on screen controls. Each of them will emit an event with type “move-*” that our Systems will listen to and change the Head’s x and yspeed accordingly.
Besides these points, I think the code is pretty self explanatory.
Rendering our entities is pretty simple. We just have to create components for <Head />, <Food /> and <Tail />
Head and Food are very similar. They could very well be the same components with different props for their color. But for now let’s keep things simple:
All they do is rendering a square at a specified x and y coordinate. Not exactly rocket science.
Tail is lightly more complicated as it needs to render multiple blocks, but I don’t think it needs too much explanation:
And thats all we need to render our entities
With react-native-game-engine you can define multiple “Systems”. These systems will be called on every “tick” with a set of useful parameters. They are essentially implementing the logic of your game.
The will always receive the list of entities and optionally they can receive touches, events and many more.
In our case we will have only one system, and we will call it GameLoop.
The most minimal implementation would look something like this.
Cool, cool, cool, but it doesn’t do anything. Let’s get our snake moving
Ok, let’s unpack this:
- Remember how we passed in nextMove = 10 and updateFrequency = 10 to our Head entity? With each tick we decrement the nextMove by one. Once it hits 0 we are performing some calculations on the environment. This way the game is slowed down to move at every 10 ticks
- First we are checking if the head did move outside of our playing grid. If so we are dispatching an event with the “game-over” type. Our index.js’ onEvent method will listen to this event and stops the game.
- If the Head is inside the playing grid we update it’s position by xspeed and yspeed
Amazing, we have something moving but we can’t control it. Let’s fix that:
All right. Remember how we dispatched a “move-*” type event each time the end user tapped on one of the control buttons? Those dispatched events are passed in to our game loop. All we need to do is change the x and yspeed based on the user input and our Head will magically change directions.
Time to eat some food:
In this step we are checking if our Head’s position is equals to the Food’s position. If so we are doing two things:
- Prepend the food’s position to our Tail’s elements
- Generate a new random position for our Food so it will pop up at a new location.
Amazing. Except our Tail is not following our Head. Also we need to make sure that when the Head hits one of the Tail elements the game is over:
What we do here is simply prepending the current Head’s position to the Tail’s elements and then removing the last element of the Tail. Then with a simple for loop we check if the Head collides with any of the Tail’s elements. If so we dispatch a game-over event.
I’ve also added a commented out code block, in case if you want swipe controls. My original idea was to use this as a control mechanics but it felt very wonky on a real device so I decided against it.
Thats all! Of course this is just a hack and can be improved upon greatly. Feel free to send a PR or fork the GitHub repo: https://github.com/lepunk/react-native-videos/tree/master/Snake