We've all been there. You have ten or fifteen minutes to kill while a heavy Docker build is compiling or a CI/CD pipeline is running its course. You just want to play a quick game to clear your head. But modern gaming often means sitting through a massive update, logging into multiple different launcher accounts, and playing through a mandatory tutorial just to get to the actual gameplay. The friction between wanting to play and actually playing is incredibly high.
I wanted something different. I wanted to build an experience that you could play instantly, without downloads, straight from your browser. That frustration with modern gaming overhead led me to create Echorunner.
Why I Built Echorunner
The goal was simple: create an accessible, instant-play arcade game for desktop and mobile browsers. No friction, just gameplay. But I also wanted a mechanic that felt fresh. Echorunner is an endless runner where your past self—your "ghost echo"—trails exactly three seconds behind you, destroying hazards in its path. It forces you to think spatially and temporally to survive, creating a unique loop of dodging obstacles while strategically placing your future ghost to clear the way.
Building a web game that runs smoothly across varying devices without native app performance requires some specific architectural choices.
The Tech Stack
I kept the stack lean to ensure fast load times and maximum compatibility.
- HTML5 Canvas: The core rendering engine. It provides the raw drawing performance needed for a fast-paced arcade game without the overhead of heavy WebGL libraries.
- Vanilla JavaScript: No bulky frontend frameworks here. For a game loop running at 60 frames per second, keeping garbage collection to a minimum and directly manipulating the state is crucial for a smooth experience.
- Web Audio API: For low-latency sound effects, which are critical for timing, rhythm, and feedback in an action game.
Technical Challenges
1. The Ghost Mechanic
The defining feature—your echo trailing three seconds behind—was the trickiest part to implement efficiently. Initially, I was pushing the player's exact coordinates into an array every frame and reading from the beginning of that array three seconds later. At 60 FPS, that array grows rapidly, and continuously shifting it was causing performance hiccups on lower-end mobile devices.
The solution was implementing a circular buffer. Instead of a constantly expanding and shifting array, I allocate a fixed-size array based on the target framerate and the delay (e.g., 60 FPS * 3 seconds = 180 slots). An index pointer loops through this buffer, overwriting the oldest position with the newest one. Reading the echo's position is just a matter of looking at the current index. This memory-efficient approach smoothed out the framerate dramatically.
2. Procedural Obstacle Generation
To keep an endless runner engaging, the obstacles cannot be static. I built a procedural generation system that spawns hazards based on a difficulty curve that scales the longer you survive. However, ensuring that the generated paths are actually passable—especially factoring in the echo mechanic—required building a validation check before spawning a new chunk of the level. If a sequence is mathematically impassable, the algorithm regenerates it on the fly. Doing this without dropping frames required optimizing the generation logic to run within a couple of milliseconds.
3. Consistent Speed Across Refresh Rates
Browsers try to sync requestAnimationFrame to the monitor's refresh rate. If a player has a 144Hz monitor, the game loop runs more than twice as fast as on a standard 60Hz screen. If physics calculations are tied directly to frames, the game becomes unplayable at high refresh rates.
I had to decouple the rendering loop from the logic loop using a fixed timestep. The game logic updates in fixed increments based on actual elapsed time, regardless of how often the screen is drawn. This ensures that the speed of the runner and the obstacles remain consistent whether you are playing on a standard phone screen or a high-end gaming monitor.
Lessons Learned
Building Echorunner reinforced how powerful the modern web platform is. We don't always need an app store or a massive, multi-gigabyte engine to deliver a fun, performant experience.
It also reminded me that constraints breed creativity. By limiting myself to browser technologies and aiming for zero-friction access, I had to be meticulous about performance, memory management, and asset sizes. That tight focus ultimately resulted in a better, more accessible product.
Give It a Try
If you have a few minutes to spare and want to see how the circular buffer and fixed timestep feel in action, you can play Echorunner right in your browser. There is nothing to install, and no accounts are required.
Try it out here: https://echorunner.getinfotoyou.com
See how long you can survive using your own ghost to clear the path. I would love to hear your thoughts on the performance and mechanics in the comments!
Top comments (0)