JavaScript

02 Game Loops

April 7, 2022·4 min read
02 Game Loops

The Game Loop

Note: While the architecture discussed here is a classic and widely accepted foundation, it is not the only way to build a game. Software engineering offers many patterns, and as your games grow in complexity, your loop architecture will likely evolve.

At the heart of every video game—from a retro 2D platformer to a massive 3D open-world RPG—is a single, relentless engine: The Game Loop.

Strip away the graphics and audio, and a game is essentially an infinite loop. On every iteration, it performs three core tasks at a highly controlled frequency:

  • Processes Input (What did the player do?)

  • Updates Game Logic (How does the world react?)

  • Renders the Scene (What does the player see?)

While complex game engines separate these systems across multiple CPU threads using concurrent programming (mutexes, workers, and promises), JavaScript's single-threaded, asynchronous nature is incredibly capable. When leveraged correctly, JavaScript can run highly performant, buttery-smooth games right in your web browser.

Loop Life Cycle

In its most abstract form, the life cycle of a game loop looks like this:

let gameIsRunning = true;

function gameLoop() {
    if (!gameIsRunning) return;

    processInput();
    updateLogic();
    render();

    // Schedule the next frame
    requestAnimationFrame(gameLoop);
}

1. Process Input (processInput)

This phase gathers data from any peripheral device connected to the user's system—keyboards, mice, gamepads, or touchscreens.

Historically, developers manually managed Event Queues to store and poll hardware events. Today, modern operating systems and web APIs abstract much of this complexity away.

In browser-based game development, we typically use a Key Manager pattern. Instead of executing game actions immediately when a key is pressed, we simply record the state of the keyboard in a global object. The updateLogic phase then reads this object to decide how the player should move.

const keys = {};

window.addEventListener('keydown', (e) => { 
    keys[e.code] = true; 
});
window.addEventListener('keyup', (e) => { 
    keys[e.code] = false; 
})

2. Update Logic (updateLogic)

This is the brain of your game. It is where your mathematics, physics, AI, and rules live.

During the update phase, you calculate character movements, check for collisions, update animations, track scores, and evaluate enemy behavior. The input gathered in the previous step is consumed here. For example, if your Key Manager says keys['ArrowRight'] is true, the update function increments the player's X-coordinate.

Render

The rendering phase handles drawing your game objects onto the screen (usually via a HTML5 <canvas> API or WebGL).

A critical rule of game architecture is to never mix logic updates with rendering. Rendering should only ever happen after the entire game state has been updated for that frame. If you attempt to draw objects while they are still calculating their positions, you will introduce visual bugs, stuttering, and screen tearing. Rendering is simply a visual snapshot of your completed logic update.

The Crucial Element: Time & Frame Capping

If you run the primitive while(gameIsRunning) loop directly in JavaScript, it will freeze your browser tab. It executes as fast as the CPU allows.

This introduces a massive problem: Hardware Variance. A loop running uncapped on a 60Hz office laptop might execute 60 times a second, while on a 240Hz gaming rig, it might execute 240 times a second. Without managing time, your game characters would move four times faster on the gaming machine!

To fix this, games use a concept called Delta Time . Delta Time is the exact amount of time it took for the previous frame to finish executing.

Instead of moving a player by a fixed pixel amount per frame:

// Dangerous: Speed is dependent on frame rate
player.x += player.speed

We multiply the speed by Delta Time to make movement dependent on real world time:

// Safe: Speed is consistent regardless of hardware performance
player.x += player.speed * deltaTime;

Implementing a Modern Canvas Loop

In modern web development, we no longer use setInterval or setTimeout for games because they don't sync with the monitor's refresh rate. Instead, we use requestAnimationFrame. This native browser API pauses when the user switches tabs to save battery, and automatically targets a smooth 60 frames per second (or matches the monitor's native refresh rate).

Here is a robust, production-ready implementation of a time-aware game loop in JavaScript:

https://codepen.io/sudarshanrai/embed/BaJrxdb?default-tab=js%2Cresult&editable=true

Challenges: Put Your Knowledge to the Test

To help anchor these core concepts, try modifying the code snippet above to complete these tasks:

  1. Reverse it: Alter the logic so the object moves backward autonomously.

  2. Styling: Change the rendering function so it only draws the stroke/border of the player, rather than a solid filled square.

  3. Screen Boundaries: Constraint the player’s movement so they cannot cross outside the boundaries of the canvas width and height.

  4. Elastic Physics (The Bounce): Combine boundary constraints with direction manipulation to make the square bounce back and forth automatically whenever it hits a edge.

  5. Trigonometric Movement: Use Math.sin() or Math.cos() inside your update logic using accumulated time to make the square move smoothly in circles or ellipses.

  6. Multiplicity: Add a second game object array, updating and rendering both independently inside the single loop lifecycle.

What's Next?

Manually writing code for individual variables becomes incredibly messy when dealing with dozens of elements. In the next chapter, we will solve this bottleneck by introducing Object-Oriented Programming (OOP) and Classes, enabling us to spawn and manage hundreds of distinct game objects instantly.

#JavaScript#Game Development#HTML5#GameDev#canvas