Raising chickens and rendering sprites with React

ezgif.com-crop (1).gif

Two sprite components rendering a chicken and egg without any images.

I spent my second week at the Recurse Center learning React and rebuilt one of my old repositories, Chicken Rancher, a clicker game about raising as many chickens as possible. It’s inspired by my childhood experience raising chickens and the long-running farming game Harvest Moon.

After whipping up a Game of Life, I was excited about how easy React makes rendering and updating the DOM. That let me spend more time on the game logic; I even learned how to use Redux to manage the game’s state and completely refactored the game in a more componentized way in the latter half of the week.

The game is powered by a game-wide time counter that increments every 500 milliseconds. Every half-second each <Chicken /> (which receives the game time as props, triggering an update) has a chance to become hungry. After 30 ticks, without being fed, they send an action to Redux to die. When healthy, every adult chicken has a chance to lay an <Egg />, which can be sold or hatched. Repo here.

Decoding sprites into arrays of 256 RGB values #

Older games (& some current games by design) have limited processing power and screen resolution. Tiny images called sprites are drawn pixel-by-pixel and stitched together into maps and characters. Each system has its own limits for size and color: the Game Boy Color supports 56 colors and 8x8 sprites (four make one 16x16 sprite).

I found sprite sheets for Harvest Moon GBC on The Spriters Resource, but had trouble displaying them on the page: I wanted the sprites to be larger than 16x16 pixels but stay pixelated. When browsers size up pixel art, the default algorithm resamples them in a blurry and gross way.

corn.gif

(As it turns out, there’s an upcoming image-rendering: pixelated CSS property that could fix this. It’s still experimental and not supported consistently in Chrome/Safari/Firefox/IE.)

I wanted to try my hand at displaying pixel art at any size on the screen. My idea was to redraw them pixel by pixel in a 16x16 table. That would require knowing each pixel’s color, so the next step was to decode each sprite into pixel-by-pixel color values.

Thankfully, someone already figured it out. It’s possible to use <canvas> to draw an image file and then iterate over each pixel to construct an array like this:

{
    corn: [
        {"r":0,"g":0,"b":0},
        {"r":32,"g":32,"b":0},
        {"r":32,"g":32,"b":0},
        …
    ]
}

I ran all of my sprites through this and combined them in one large spriteSheet file that I could use in the React app. It’s a bit fussy, but it works. In the future, I’ll work on making it easier to load in many images at once and automatically build a sprite sheet object.

Rendering sprites with plain HTML/CSS #

Next, I built a Sprite component that takes a sprite’s name and looks for it in the sprite sheet. It renders a containing div, then loops through each pixel and assigns the correct background color (or none, if the pixel data is null).

return (
    <div className='Sprite'>
      {sprite.map((pixel, index) => {
        let style = {}
        style.background = (pixel.r || pixel.g || pixel.b)
          ? `rgb(${pixel.r}, ${pixel.g}, ${pixel.b})`
          : 'none';
        return (
          <div
            key={index}
            className="pixel"
            style={style}
          />
        );
      })}
    </div>
  );

The containing div is a CSS grid:

display: inline-grid;
grid-template-columns: repeat(16, 1fr);
grid-template-rows: repeat(16, 1fr);

All 256 pixel-divs fit themselves into a perfectly proportioned 16x16 grid. In the parent components’ CSS where you call <Sprite />, you can apply any height/width you need, or even transform them. In the first image, I added a bouncing animation to the egg sprites.

Every single graphic in Chicken Rancher is rendered like this.

Animating sprites with React #

Most sprites in Harvest Moon for Gameboy Color have just two frames. The Sprite component accepts an optional alternator (0 or 1 value). If the alternator is updated and there is a second array of pixels in that sprite’s sheet, it will re-render the component, updating all the background color variables.

ezgif.com-crop (3).gif

In Chicken Rancher, I send the remainder of time divided by 2 as an alternator, so as the game’s time ticks on, alternating even and odd values cause the sprite to animate.

Performance #

Surprisingly, the game doesn’t run half bad! It’s possible to get up to ~20 chickens before framerate gets gnarly.

I also think a lot of that comes from other parts of the game’s architecture, which probably does more re-rendering than necessary. Writing memoized selectors to speed up re-renders and using shouldComponentUpdate to stop unnecessary re-renders could help.

I’ve just started researching ways to measure the performance of this component, so I don’t have any specific numbers yet. I’d like to start by comparing the render time of the component to an image. If you have ideas, resources, or advice, I’d love if you hit me up on Twitter!

Publishing the Sprite component #

I’ve never published anything open source or on NPM, so I’m going to use this project as another excuse to try something new. I’m working on getting the Sprite component ready for prime time. Specifically, I’m thinking about ways to decode sprites easily and add an animating function that’s internal to the component.

This has been a really fun project and a great example of React’s (and modern browsers’) power. It’s also a really fun way to display and animate graphics on-screen without the use of any images.

 
10
Kudos
 
10
Kudos

Now read this

The subway game

Rules # Don’t hold the bars. Don’t lean on anything. Don’t fall. Tips # Hold a wide stance # If there’s room, stand wide and lean into the heels of your feet to stabilize you. Throw your weight backward to brace against the momentum of... Continue →