Devlog / Post-Mortem

Developer Diary: How We Built Chronos Drift without WebGL

Creating a sense of blazing speed and time-bending mechanics in a pure 2D Canvas environment required specific math tricks, layered parallax, and brutal object pooling.

AdvancedGame DesignCanvas API8 min read

The Premise: Time Moves When You Move

When we designed Chronos Drift, we wanted more than just a typical endless runner. The core mechanic was heavily inspired by modern indie shooters where "time moves only when you move," but applied to a high-speed racing context. The faster you drive, the faster the world around you reacts, including the enemy obstacles and the procedural environment.

From an engineering perspective, this meant that `deltaTime` (the time elapsed between rendering frames) could no longer be a constant or purely dependent on the system clock. It had to be a dynamic multiplier directly tied to the player's current speed state.

Pseudo-3D Projection on a 2D Canvas

Why didn't we use Three.js or Babylon.js? The answer lies in the Supagames philosophy: maximum accessibility, zero loading screens, and an extremely lightweight footprint. We wanted the game to load instantly on 3G connections and run at 60 FPS on older smartphones.

To achieve the "synthwave highway" look, we used a classic pseudo-3D raster effect (often called "Mode 7" style or OutRun style). The trick involves drawing horizontal lines that scale in thickness and scroll vertically based on speed.

// Pseudo-code for the grid projection
for (let y = horizonY; y < canvas.height; y += step) {
    let perspective = (y - horizonY) / (canvas.height - horizonY);
    let scale = 1 / perspective;
    let gridWidth = baseGridWidth * scale;
    
    // Draw horizontal lines
    ctx.fillRect(centerX - gridWidth/2, y, gridWidth, 2);
}

By offsetting the X coordinates of the vertical grid lines based on the camera's fake X-position, we create the illusion of turning. The perspective formula `1 / perspective` ensures that objects closer to the bottom of the screen appear larger and move faster, which is the cornerstone of depth perception in 2D engines.

Time Dilation Mechanics

The "Time Moves When You Move" mechanic was implemented using a `timeScale` variable. This multiplier affects EVERYTHING in the game loop: enemy movement, background parallax scrolling, and particle lifetimes.

// Core time dilation logic
let targetTimeScale = isAccelerating ? 2.5 : 0.2;
timeScale += (targetTimeScale - timeScale) * 0.1; // Smooth interpolation

// Apply to objects
enemy.z -= baseSpeed * timeScale * deltaTime;
parallax.x -= turnSpeed * timeScale * deltaTime;

When the player releases the gas, `targetTimeScale` drops to `0.2`. The interpolation ensures a smooth "bullet-time" transition rather than a jarring halt. This gives the player crucial milliseconds to plan their path through the "Laser Firewall" obstacles, turning a twitch-reflex game into a tactical puzzle at high speeds.

Procedural Sectors and Escalating Threat

To keep the player engaged over longer sessions, Chronos Drift is divided into "Sectors". Instead of purely random obstacle placement, we use a controlled procedural generation algorithm.

Each sector has a specific "budget" for obstacles. Sector 1 might spawn single stationary blocks. By Sector 4, the game introduces moving "Laser Firewalls". The spawning logic checks a predefined grid of "lanes" to ensure there is always at least one navigable path.

Design Tip: Truly random generation is often unfair. If you randomly place 3 wide obstacles in a 3-lane road, the player is guaranteed to crash. Always implement a "path verification" step in your procedural generator to guarantee that a solution exists.

As sectors progress, we also dynamically shift the color palette—moving from deep purples and pinks (Sector 1) to aggressive reds and oranges (Sector 5). This is done efficiently in Canvas by manipulating global CSS variables mapped to the canvas drawing styles, avoiding expensive per-pixel operations.

Performance & Object Pooling

JavaScript garbage collection can cause micro-stutters (jank) which are fatal in a high-speed racing game. If we created a new object via `new Obstacle()` every time a laser spawned and destroyed it when it passed the camera, the browser's Garbage Collector would eventually pause the game to clean up memory.

To solve this, we implemented Object Pooling. We initialize an array of 50 obstacle objects at the start of the game. When an obstacle is needed, we grab an inactive one from the pool, reset its coordinates, and mark it active. When it goes off-screen, it is marked inactive again. Zero objects are created or destroyed during gameplay.

function spawnObstacle() {
    let obs = pool.find(o => !o.active);
    if (obs) {
        obs.active = true;
        obs.z = 1000; // Far distance
        obs.lane = Math.floor(Math.random() * 3);
    }
}

Conclusion

Building Chronos Drift proved that HTML5 Canvas is still an incredibly capable platform for fast-paced, visually striking games. By strictly managing state, embracing old-school rendering tricks, and tying the core physics loop to a dynamic time scale, we delivered a "Big Game" experience that runs in milliseconds.

Play Chronos Drift now and see the engine in action.