Game | Part 1 — Intro to Building an Interactive Animated Game in JavaScript

Today we're going to begin a journey  🚀  of programmatically creating a video game  🕹ī¸Â  in JavaScript without any libraries  📚  using the HTML5 <canvas> element  🎨 .

🔲 Setting Up the HTML Canvas

First things first. We need to define our canvas - the area where we'll be doing all our drawing.

<canvas></canvas>

Easy, right? 👌 Let's give it some style.

🎨 Styling the Canvas

Using CSS, let's paint our canvas a cool deepskyblue color.

canvas {
  background: deepskyblue;
}

Beautiful! Now we're ready to play with some JavaScript.

📜 JavaScript Time

Our JavaScript code will handle the drawing and user interaction. Let's break it down.

Grabbing the Canvas Context

The canvas.getContext('2d') method returns a drawing context on the <canvas> - it's the magic brush 🖌ī¸ we'll use to draw and manipulate graphics.

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

Creating an Entity Class

Here we'll create our drawable entity - a cute little rectangle that we'll move around our canvas. We'll first define an Entity class 🏛ī¸ that will have a constructor to set initial position and size, and a draw method to paint the entity as a darkorange rectangle.

class Entity {
  constructor() {
    this.width = 30;
    this.height = 30;
    this.position = {
      x: canvas.width / 2 - this.width / 2,
      y: canvas.height / 2 - this.height / 2 ,
    };
  }

  draw() {
    ctx.fillStyle = 'darkorange';
    ctx.fillRect(this.position.x, this.position.y, this.width, this.height);
  }
}

const entity = new Entity();

We then instantiate an entity object from our Entity class.

Making Things Move - Animation

Let's bring our little rectangle to life! The animate function is called repeatedly, clearing the <canvas> and drawing the entity in each frame. This creates the illusion of movement 🏃. We also measure the time between each call and log it to the console.

function animate(t1) {
  requestAnimationFrame(animate);
  
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  entity.draw();

  const dt = Math.round(t1 - t0);
  console.log(`animate() \ndt: ${dt}ms.`);
  t0 = t1;
}
animate();

Adding Interactivity

Time for some user input! We set up an event listener for keyboard interaction. When an arrow key is pressed, the entity moves 5 pixels in the corresponding direction.

addEventListener('keydown', ({ key }) => {
  console.log('key: ', key);
  const dist = 5;

  if (key === 'ArrowLeft') {
    entity.position.x -= dist;
  }
  if (key === 'ArrowRight') {
    entity.position.x += dist;
  }
});

Adding Physics

We can add a gravity effect by continuously applying a downward force to our entity. We'll need to update the Entity class to account for velocity and then adjust this velocity each frame to simulate gravity.

class Entity {
  constructor() {

    // ...

    // Added velocity property
    this.velocity = {
      x: 0,
      y: 0,
    };
  }

  draw() { ... }

  // Added update method to adjust position and apply gravity
  update() {
    this.position.x += this.velocity.x;
    this.position.y += this.velocity.y;

    // Gravity
    this.velocity.y += 0.5;
  }

} // class Entity

We've added a velocity property to the Entity class. We also added an update method to adjust the entity's position and apply gravity.

Every frame, the entity's position is updated based on its velocity. Then, a gravitational force is applied by continuously adding a small amount to the entity's vertical velocity.

    function animate(t1) {
      requestAnimationFrame(animate);
      
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      
      // Added update method invocation
      entity.update(); 
      
      entity.draw();

      // ...
    }
    animate();

We now need to update the entity's position and velocity each frame by invoking the update method inside the requestAnimationFrame callback.

Collision detection

Let's add a simple check for collisions with the bottom of the <canvas>. When such a collision occurs, we set the entity's position to just above the bottom of the canvas and its vertical velocity to 0 to simulate it hitting the ground.

  class Entity {
    constructor() { ... }

    draw() { ... }

    // Added update method to adjust position and apply gravity
    update() {

      // Update position
      // ...

      // Update y-dimension velocity (with gravity)
      // ...
      
      // Check for collision with canvas bottom
      if (this.position.y + this.height > canvas.height) {
        this.position.y = canvas.height - this.height;
        this.velocity.y = 0;
      }
    }
  }

🛑 But wait, there's more!

Currently, the user must have a physical keyboard to even move the entity! Let's set up some development controls that will allow the user to move the entity if they don't have a physical keyboard.

Development Non-Keyboard Controls

<html>
  <head> ... </head>
  <body>
    <canvas></canvas>

    <!-- Controller Buttons -->
    <div class="controller">
      <div class="row">
        <button id="up">↑</button>
      </div>
      <div class="row">
        <button id="left">←</button>
        <button id="right">→</button>
      </div>
      <div class="row">
        <button id="down">↓</button>
      </div>
    </div>

  </body>
</html>

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

class Entity {
  constructor() { ... }
  draw() { ... }
  update() { ... }
}

const entity = new Entity();

// ==========================================

let t0 = 0;
function animate(t1) { 
  requestAnimationFrame(animate);
  .
  .
  .
}
animate();

// ==========================================

addEventListener('keydown', (e) => { ... });

// ==========================================

// On screen controller
const dist = 5;
document.getElementById('left').addEventListener('click', () => {
  entity.position.x -= dist;
});
document.getElementById('right').addEventListener('click', () => {
  entity.position.x += dist;
});

Now the user can click the left and right icons on the screen to control our entity.

Live Demo

Wrapping Up

🎉 Voila! We've got a rectangle đŸŸĻ moving 🏃‍♀ī¸ on the screen đŸ–Ĩī¸. Our current game is pretty simple and rough around the edges đŸ—ŋ, however it gives us something to start đŸšĻ iterating on â™ģī¸.

In the next episode đŸŽŦ, we'll polish up the controls 🕹ī¸, smooth out the physics đŸ”Ŧ, give our player the power to jump đŸĻ˜, make our game loop utilize a more professional timing methodology, fix the geometric distortion and blurriness, and level up our collision detection đŸŽ¯.

So stay tuned đŸ“ē for more exciting posts coming soon to a web browser 🌐 near you! 🔜 👀