Skip to main content

Game animation logic in React

I've been working on a small minigame written in JavaScript using React. It's similar to Robo Rally: you write a small program for a robot, it executes it, if it gets to the end without dying, you win. The actual robot logic is turn-based, the display isn't. It's split up like this:

One module contains a pure functional implementation of the actual robot / program execution logic. It exposes two functions: one that takes a level description and returns an initial game state, and one that takes the level description and a game state and produces a new game state.

The next module contains a Flux store (just something that holds data) which holds the level descriptions, the current game state, and can run a timer. If you tell the store to start, it starts the timer, runs the robot logic every tick, and emits an event when its data changes. (It also handles a whole bunch of other state, but that's irrelevant for this description.)

Then there is a React component for the robot (obviously one of many components). React components are, ideally, pure functions of their inputs. They're best written declaratively: you use React to declare what you want the user interface to be like. This usually works like a charm.

But game logic can be hard to fit into this model.

The approach I've taken is to say that the robot logic knows nothing about animations, and neither does the data store. When the robot makes a move, the store emits a change event, and the robot component gets new data from its parent component (a standard approach in React). This includes its position and orientation, but also an 'action'. This action is like an instruction in a stage play. For movements, actions are "move in from the left", say. The robot component then goes and plays an animation on itself for this action. So it goes "OK, I am here, I'm moving in from the left, so at t=0 I am one square to the left, and at t=1 I am over here". Then it runs a timer, and can calculate exactly where it needs to be at any given time.

Another approach could have been to store the robot's current coordinates locally inside the component (in this.state, in React terms), and to compare them to new incoming coordinates, and animate based on that. The problem with that is distinguishing between 'you just made a move' and 'the entire board was just reset'. The action-based approach solves that: I can just send an action called 'none' or something.

So far, so good. A problem occurs when the data store emits a change event that is not tied to a robot logic tick, because that makes the robot go "oh you want me to come in, alright then" and it resets its timer and does the move all over again. And that looks like a bad glitch.

What I expect I need to do is intercept the incoming data (in the shouldComponentUpdate part of the React life cycle), see that its the same as what I had already, and only start a new animation when the data has changed.

This way I can distinguish 'a tick happened and you should move' from 'something changed so let's re-render everything because *shrug* React'. (The main selling point of virtual DOM libraries like React is that they can make re-rendering everything very fast.)

Does that make sense? Are there problems or solutions I'm not seeing? Is this a terrible way of going about things? I have a slightly bad feeling about inferring events from data changes. But as far as I can tell this would be the React way, and I don't see a better way that would permit the same level of encapsulation.