Pavel Vlasov

Pavel Vlasov
Jul 24, 2018

Learn advanced React patterns by developing a game. Sprites

Learn advanced React patterns by developing a game. Sprites

Ever wanted to learn some advanced React patterns? Or build your own game engine? If at least one answer is yes, then this article is for you.


In this tutorial you'll learn how to build basic sprite animation using React, styles-components and requestAnimationFrame. At the end you'll be able to create characters like this:

Sprite animation

You may ask me, why can't I learn it another way. Well… There're three reasons for that:

Pie chart

So, let's do it! 💪

Let's start with a bit of a theory

What is sprite animation? Wikipedia says that

In computer graphics, a sprite is a two-dimensional bitmap that is integrated into a larger scene.

So basically, sprite animation is repeatedly changing two-dimensional bitmap. Sprite is usually represented like a png image with different states of the animation:

Bitmap

We'll start with creating a tile component, that will show us one frame at a time and allow us to change frames with state property:

Sprites states

Basically, we'll need to show one part of the image at a time and hide the rest. Pretty straightforward.

Tile

First of all, we'll create a container component to create a shape of our frame:

const Container = styled.div`
width: ${({ width }) => width}px;
height: ${({ height }) => height}px;
overflow: hidden;
transform: scale(${({ scale }) => `${scale}, ${scale}`});
transform-origin: top left;
`;
view raw container.js hosted with ❤ by GitHub

width and height represents size of the tale, and scale increases size of the image. overflow: hidden will hide unused part of the image andtransform-origin will make a container to remain it's top and left the same when we scale it.

Now we need to adjust position of the inner image. We'll use transform: translate CSS property for that..

const Image = styled.img`
transform: translate(-${({ left }) => left}px, 0);
`;
view raw image.js hosted with ❤ by GitHub

Now let's combine everything together in the tile component.

class Tile extends React.Component {
static propTypes = {
src: PropTypes.string.isRequired,
tile: PropTypes.object.isRequired,
state: PropTypes.number.isRequired,
scale: PropTypes.number.isRequired
};
render() {
const { src, tile, state, scale } = this.props;
const left = tile.width * state;
return (
<Container width={tile.width} height={tile.height} scale={scale}>
<Image src={src} left={left} />
</Container>
);
}
}
view raw tile.js hosted with ❤ by GitHub

  • src property contains the link to the image
  • tile is the object with width and height fields, represents the size of the tile
  • state frame index
  • scale property to increase the size of the image (e.g. scale = 2 is 2x image)

In the next step, we'll add some dynamic to our image.

Sprite

We'll use requestAnimationFrame for that. You probably may ask, why we don't use setTimeout or setInterval? The problem with timeouts, is the callback will fire somewhere in between frames, that may result in clunky animation.

requestAnimationFrame vs setInterval

And also requestAnimationFrame allows us to synchronise animations of different objects on the screen. In the game, you'll have lots of them!

Let's put together a Sprite component..

class Sprite extends React.Component {
static defaultProps = {
src: PropTypes.string.isRequired,
tile: PropTypes.object.isRequired,
scale: PropTypes.number.isRequired
};
tick = 0;
frame: number = 0;
state = {
state: 0
};
componentDidMount() {
this.animate();
}
componentWillUnmount() {
cancelAnimationFrame(this.frame);
}
animate = () => {
// TODO: implement
}
render() {
const { src, tile, scale } = this.props;
const { state } = this.state;
return <Tile src={src} state={state} tile={tile} scale={scale} />;
}
}
view raw sprite.js hosted with ❤ by GitHub

In animate function we should change state of the frame and request new animation frame:

const { state } = this.state;
const { framesPerStep, states } = this.props;
if (this.tick === framesPerStep) {
this.tick = 0;
this.setState({
state: (state + 1) % states
});
}
this.tick += 1;
view raw animate.js hosted with ❤ by GitHub

We use framesPerStep property to control number of states per frame, so our animation won't be too fast.

What about a gun? 🔫

Now the only thing we need to do is just to combine our sprite with the gun image:

<Container>
<Gun top={15} left={18}>
<Tile src={gun} state={0} tile={{ width: 17, height: 17 }} />
</Gun>
<Sprite
src={sideAnimation}
states={4}
tile={{ width: 20, height: 24 }}
scale={1.5}
framesPerStep={8}
/>
</Container>
view raw player.js hosted with ❤ by GitHub

And you should get the following result:

Player

The best way to learn something it to build it by yourself. So I encourage you to use this codesandbox:

Typescript version is available as well.

As the bonus you can implement different animations using files from assets folder.

You can find source code here. I used game assets made by finalbossblues. Hope you enjoyed the article! 😉


Some more resources about the topic: http://www.javascriptkit.com/javatutors/requestanimationframe.shtml