A Game Any Web Dev Can Build in Minutes Using PixiJS

Herbert Ng
The Startup
Published in
6 min readDec 11, 2020

No, it won’t be Animal Crossing or Cyberpunk 2077. But we all have to start somewhere right? We’ll be building a really simple enemy dodging game in HTML5 with PixiJS, and we can do it pretty quickly (medium suggests a 6mins read, so let’s say 10mins!). It looks like this:

Live Demo: https://www.undeveloped.games/game0a.html

Benefits of building games

Games come in many different genres, and more often than not, games are highly interactive, which somtimes require very different ways to approach a solution than most front-end development tasks. Getting acustomed to ‘game-building way of thinkings’ benefits web developers alot if your projects consist of interactive elements. Yet it could be daunting to jump right into learning about a game engine, I think it’ll be easier to illustrate some techinques purely in JavaScript ES6, with PixiJS, a popular and easy to use graphics library.

Step 1: Creating a blank stage

We’ll start by creating a blank canvas for our game, some variables, and the main gameLoop(). Games run continuously, and in this tutorial we assume it’s simple enough to run at 60fps, hence gameLoop() will be called 60 times a second, in which we call the .update() function in all game objects every frame. update() will be where we update the locations of each object, handle collisions, along with other logic.

A more advanced approach is to use requestAnimationFrame() which update graphics as smooth as the target device could support. But be advised to keep animation speed consistent across different devices (different framerates), we need to account for delta time elapsed between frame in our movements, which I’m sure you could jump into later. But let’s keep it simple for now.

Step 2: Spawning a monster

First let’s create some monsters! Our monsters (and our player, also the reward…) would simple be little circles so that makes sense to create a circle base class. Constructing a circle takes three paramters, color, radius, and velocity. The base constructor memorize these values, draw a circle as a new graphical object this.circle, and add it to the main stage.

The monster class extends the circle base class, and adds an update function, which moves this.circle every frame by velocity. Now you must know our velocity is specified as ‘pixels per frame’ , which is simple yet not ideal. In real life we would probably use ‘distance per second’ and calculate amount needed to travel per frame based on time between each frame. But the concept is the same.

The update() function also checks if the circle (anchored in the middle) reaches edges of the stage, if so, flip the v.x or v.y value.

Finally addMonster() creates a monster with random radius and random speed, and adds that to the main array of monsters.

Step 3: Adding a player and control

Next we’ll create the player, which extends the same circle base class but created with a different color. We added a Player.reset() function to reset its speed and its position to the center of stage, which would come in handy later when the player dies.

In Player.update(), instead of flipping velocity when hitting edges of the stage, we simply limit it.

Controls is straight forward, we listen to the keydown and keyup events, and update Player.v accordingly. Now, the seemingly long codes accord for a specific case in which one direction is released while the opposite direction is held down all along. For instance, player holds down LEFT, and hit RIGHT and release, we should detect this situation by storing the pressed states of keys. This add alot to the controls. We are making a game afterall, and it won’t be slick if controls are jerky.

Step 4: The lose condition

Monsters kill player. It’s simple, and we will do this by collision detection, which in this case is the fancy term for comparing distance of the centers of two circles and sum of their radii, see if they overlap. We will add the calculation to the collide() function to the circle base class.

Then, in Player.update(), we will call .collide() against each monster every frame. Yuck. But that’s life (see foot notes for more.) If player collides with any monster, the game resets.

In the main reset() function, we remove all monsters from the stage by Circle.remove(), reset arrays and other variables, add a new default monster.

Step 5: Something to pick up

Now our player can depressingly die, let’s add some rewards shall we? In here we create another Coin class extending the same Circle base class. We will add a random() function to randomize its position, and we will continuously scale the coin in the update() function, a little trick to communicate “hey I’m interesting, come pick me up.”

In Player.update() we further check if the player collides with the coin, if so, picks it up by increasing coins count and moving the coin into a new position. For coins counts (the score), I simply display that with a HTML element.

Picking up coins also makes the game harder, which is achieved by the following two lines in Player.update():

addMonster();
this.speed = Math.min(4, this.speed + 0.2);

An extra monster will be spawned and the player will move faster. This concludes the core mechanics of the game:

  1. players move around
  2. avoids danger
  3. picks up rewards
  4. avoids more danger, harder to play

What’s next?

The feeling of the game could be drastically improved with hints of interactivity and responses from the environment. Note that I didn’t specify animation or music, they are of course crucial but the goal of polishing is to improve ‘feeling of interactivity’ and ‘responses of whats happening.’ Music, sound effects, animations, etc are wonderful techniques that we could add in meaningfully.

A note on collision detection per object per frame on Step 4. In improve scalability we should be using some sort of spatial partitioning data structure to handle collision detections (say, a quadtree, or grid-based paritioning.) And we may not need to calculate these at a per frame basis, depending on how ‘reactive’ we want the game to feel.

Shall we implement our own data structure? If the project is complex enough we need to worry about these, maybe its time to consider using a game engine, which, along with many other game-related features, is what its made for.

Hope you enjoy and find this helpful in some ways!

Live Demo: https://www.undeveloped.games/game0a.html

Full source: https://www.undeveloped.games/downloads/game0a.zip

--

--

Herbert Ng
The Startup

Engineer, solo gamedev, working on a retro survival game. I share thoughts and lessons learnt from projects. https://twitter.com/TheNextSurvivor