Frontend challenges when building a web based network game

Feb 8, 2025

My motivation for this project is to build multiplayer features on the Web. If your mind went to Figma, then you're thinking exactly like me.

The constraints I had for myself:

  • Project should be web based
  • I must build the multiplayer server
  • Render multiple cursors

This game is still very much in progress fyi

I ended up going with a multiplayer game project. Paint Fight is a game where you compete to throw more paint on the screen than your opponents. It will be a fast paced yet subtly strategic action game (once complete).

The approach

So what did I consider when architecting this project? And what challenges popped up along the way?

How should the game be rendered?

First challenge was deciding how to display the game and its state. The ultimate goal is a smoothly running game.

  • For games that don't update frames very often, you might choose raw HTML/CSS and a UI Framework like React. HTML/CSS provides relatively flexible 2D rendering capabilities and React could handle visual game state updates when it runs diffs in its virtual DOM.
  • For games that require custom rendering or animations beyond what's reasonable from CSS, you'll want to reach for <canvas> running the Canvas API, which gives you the lower level way to define your rendering.
  • For full fledged custom rendering (e.g. 3D), you can lean into WebGL running on the <canvas> element.

There's no need to keep all your eggs in one basket, so for Paint Fight, a hybrid approach combining HTML/CSS and <canvas> makes sense.

Paint Fight HTML Layering Rendering layers for Paint Fight

HTML/CSS/JS parts

  • Rendering Paint Fight's user interface, e.g. the game lobby or button actions.
  • Layering real-time mouse/touch cursors in the game.

Rendering cursor positions on the Canvas would require clearing and redrawing the canvas - either by consecutively loading an image of a cursor in different positions or manually drawing it like stop motion film. Since Paint Fight's player actions are purely additive in terms of rendering, the game does not require redrawing frames. Handling cursor rendering in canvas would introduce a significant performance cost. Essentially we would be going from only needing 1 fps to needing upwards of 120 fps.

Multiplayer cursors in Figjam Multiplayer cursors are also rendered outside the canvas in Figjam

Canvas parts

  • Perfect for the additive "painting" action of the game. Drawing persistent paint strokes is what the Canvas2D API is made for.
  • Great for exporting raw pixel data via Canvas.ctx.getImageData() which is a convenient way to determine the win condition at the end of the game (e.g. what RGB values are most frequent).

The painting action is additive

Quirks of canvas I had to account for

Rerendering upon canvas screen size changes

I said Paint Fight did not need to rerender frames, but that was actually a simplification. Paint Fight does not need to rerender frames as long as you don't resize your browser, turn your phone into landscape mode, or generally do anything to change the base size of the canvas.

In those cases, you'd want to rerender the game state with the newly sized canvas. One way of rerendering would be to replay the game state up to the point of resizing. But some of those actions are bound to be stale and covered up by newer player actions. Instead, I export current pixel data for the canvas and import it into any new canvas.

Crisp edges, or accounting for screen pixel ratios

Another quirk of working with Canvas - for crispy edges, you need to manually handle differing pixel density on different dpi screens. Without this scaling adjustment, your game will appear blurry and unsharp.

<canvas width="1081" height="2401" style="width: 412px; height: 915px;" />
Canvas attributes for a Pixel 7 with window.devicePixelRatio = 2.625

Can a browser actually manage a real-time multiplayer game?

The short answer is yes. The long answer is it depends on many factors, but for a game like Paint Fight, there should not be rampant performance issues. I'll detail performance optimization in future post, but there are real frontend limitations to running a game in a browser.

For example, I intended Paint Fight to be a full screen game with mobile support. Mobile web adds significant challenges for a full screen game, as built in actions like "pull to refresh" or "swipe right to go back" will conflict with player actions in the game.

But these are limitations I'm expecting, because at the end of the day, I'm trying to create a game that works like Figma but plays like agar.io.

Conclusion

The frontend challenges ranged from quirky canvas behavior to a hybrid rendering model. They are not trivial but they were more straightfoward to resolve than the performance and backend challenges of this project. In upcoming posts I'll cover those portions.