A Tale of Two Game Engines
We’re big fans of Unity — so why aren’t we using it?
My first game was written in Qbasic and believe it or not, it was terrible.
I made text adventure games with massive numbers of if/else branches. I didn’t even know what a subroutine was. From Qbasic I moved to HTML (my site had frames) and then to Flash, Cocos, and proprietary engines. At some point, once some rich guy decided Flash should die with no reasonable replacement, the industry moved to Unity, where we’ve largely been stuck for some time (maybe I should give Godot a shot soon?).
I’m a big fan of Unity.
Why shouldn’t I be?— it’s an excellent game engine.
I’ve shipped a looooot of games with it, from simple 2D arcade games to big budget RTS to (canceled) 3D AAA action RPGs. I used Unity to build the world’s first 100 person HoloLens multiplayer game (and the best darn AR content platform on the Interwebs). I used Unity to build an exhibit for Universal Studios. I’ve scaled Unity as a backend service for realtime asset processing. I’ve abused the crap out of it.
I say all of this because I want you to know that I freaking know Unity. I am an honest man, though, and I have to admit that Unity has some weak spots — and by weak spots, I mean that Unity freaking blows for web game development.
This is not a hit piece.
I’m not here to insult the hard work that Unity has done in moving the games industry forward. However, folks ask us, as a web-game studio, why we’re not using Unity at the moment and so here I am to explain our reasons. We are not a “never-Unity” shop, we are a “not-Unity-right-for-our-current-projects” shop.
Instead of Unity, we’ve chosen to focus on web-native engines: ThreeJS (for 3D) and PixiJS (for 2D). Both of these are written in stuff that browsers love to eat. Neither of them has anywhere near the tooling that Unity does and are arguably not even “game” engines, but rather “rendering” engines.
So why the heck have we picked them? Four reasons.
Like what you’re reading? Consider a highlight or some claps.
Follow our adventure on X or play our first game, Hangman Clash!
1. Pick Tech for What It’s Good At
I’ll start here. I’m not sure if you’ve played our first game, Hangman Clash (play now at https://hangmanclash.com!) but the game is probably 75% UI. This is not unusual for casual games. Believe it or not, most casual games don’t use Cinemachine, ECS, or Unity’s terrain system (though to be fair, no one actually uses Unity’s garbage terrain system). I could go on with a giant list of things Unity provides that we don’t need, but you get the point.
What DO we need though? We needed tools that are great at building fast loading, responsive UI. There’s no faster loading UI in a browser than DOM elements, and while Unity’s anchor system is great, it is nowhere near modern web dev in terms of adapting to screen size.
The natural choice for web UI is a web framework like Angular or React (and Tailwind is pretty hot right now too). Regardless of your feelings on web frameworks, there is a reason React has eaten the world while it took about 14 tries for Unity to land a half-decent UI system.
Perhaps it’s a corollary of Atwood’s Law: “anything that can be written in React, will be written in React.”
2. Iteration Speed
Iterating in the Unity editor is great. Daggum if C# don’t compile fast. However, it is incredibly slow to iterate “on device” or in our case, in a browser. IL2CPP + Emscripten is two things: brilliant and slow to build. You know I’m right. Sometimes when IL2CPP + Emscripten are working away, I fear we may hit the heat death of the Universe.
If you’re already forced to build a website, and you have a bunch of native web UI that needs to interact with the Unity app, it can be a painful process to iterate.
On a past project, I was able to get past quite a bit of this headache by implementing a bridge API that was essentially a message passing interface: all messages between React and Unity went through that single point. This meant that for quick iteration, you would have your react app over on one screen, using the full react tool-chain — then on the other screen you’d have the Unity portion running in the Unity editor, and I pushed events from one to the other using a websocket as that bridge abstraction. When they ran on the same page, they of course would just pass messages over the Emscripten layer.
First, I want you to congratulate me on how awesome that approach is. However, there are still plenty of times when the WASM build behaves differently on a webpage than running on Mono in the Unity Editor.
Iterating on Pixi or ThreeJS, however, is lighting fast. No build times, no code stripping, no C# to IL to CPP to wasm. Just refresh the dang page.
This leads me to my next point…
3. Debug-ability
Have you ever tried to debug a WASM application? I don’t think you have because you still see good in the world. You can have all the source maps and development builds you want: debugging Unity WASM applications is an activity I wouldn’t wish upon my own worst enemy — which is, incidentally, Unity WASM builds.
You know what happens when a WASM application crashes? It’s unrecoverable. There’s nothing to be done but refresh the page. This means you need to stick your WASM application in an iframe so you can refresh that iframe if you need.
Alternatively, you know what’s great? Debugging Javascript in a browser. Especially when the engine code is sitting there too, complete with full source. It’s actually been awhile since I’ve worked with a game engine that you can make meaningfully fixes to. I can count on zero hands the number of times I’ve had a PR accepted into Unity’s codebase.
Like what you’re reading? Consider a highlight or a few claps!
4. Performance
Finally, there’s performance. Let’s talk about how quickly a Unity browser game loads. This sounds hyperbolic, but it’s not: in our tests, our entire game loads over 100x faster than an empty Unity project.
This isn’t all Unity’s fault. Your browser has to download giant WASM gzip files. It has to allocate a giant memory buffer for your WASM application. Your browser has to compile that WASM — at least, the first time it’s loaded (subsequent loads, if you do it right, will use the browser’s build cache).
However, it’s still partially Unity’s fault because the engine is so daggum big. Code stripping, asset bundles (addressables still suck, come at me), serving your WASM files with gzip headers so your browser asynchronously inflates — these are all great tricks but they still lead to load times several orders of magnitude larger than a web-native JS engine like Pixi or ThreeJS (which, coincidentally, get many of those benefits too).
This is all very interesting, but I can tell you’re skeptical of how much this actually matters in the age of 5G and fiber. Let me point you to a little tidbit I found in the 2024 Browser Games Global Market Report:
“The frustration of gamers due to slow downloads is the major restraining factor in the browser games market. The amount of time it takes to download games has been reported as the biggest issue worldwide… 87% of players complained that downloading games was difficult…”
If you are a web game developer, you need a good answer to this problem.
Chances are, you’re not building the next Fortnite, which is for some reason allowed to take 5 minutes to boot. As a casual games studio, the fact of the matter is that we have to make tech choices from the beginning that will attack the “major restraining factor in the browser games market.”
That means we need insanely low load times.
Final Thoughts
Obviously, web-native engines aren’t all rosy. Or maybe they are rosy because they have thorns of their own…
The beauty of Unity is that it has tools on top of tools, as well as a thriving asset marketplace. ThreeJS and PixiJS don’t have those. They are underdeveloped in comparison to Unity with its $8B market cap. We are stuck with writing many of our own tools. This is a conceit and Unity wins this round, hands down.
At the same time, consider what I’ve described having to write on past projects to make Unity in a browser feasible. And consider that even with all of that work, there’s nothing I can do to make Unity load 100x faster on a webpage. Consider also that, regardless of which engine you choose, you will likely be writing at least some custom tooling anyway.
This is why, for now, not forever-on-every-project-for-all-time, we’ve decided to go with web-native engines. We’ve said it before and we’ll say it again: we’ll always pick what most benefits the user.
Consider leaving some claps!
Follow our adventure on X, or play our first game, Hangman Clash!