How It's Made
Since I've gotten a lot of questions about what Debug is built with, I figured I may as well write an article about it! I'll be covering not just which tech I'm using to build Debug, but also why I chose the tools I did. If you've got further questions, make sure to join our Discord!
Choosing a Language
When we built the original version of Debug, it was for the js13k game jam where teams have a month to build a game using JavaScript and other web tech. The catch is that the final bundle — JavaScript, HTML, and CSS — must be less than 13kb.
Between that and my 2 decades of writing JavaScript, building with JavaScript was a no-brainer when I decided to start from scratch and rebuild the game from scratch.
That said, I had a few goals for the game's reimagining that would be challenging to address with a JavaScript app. Some of the things I wanted to be able to do included...
Publishing to platforms like Steam and Epic, whose revenue potential overshadows any platform that publishes web games.
Storing custom maps on the local machine, which is difficult since JavaScript doesn't have direct filesystem access from the browser.
Giving users fine-grained control over game settings, which is often challenging to implement in the sandboxed JavaScript environment.
That leads me into the first piece of the tech stack...
JavaScript on the Desktop
Most games are using engines like Unity, Unreal, or Godot to build for the desktop. A lot of AAA studios even have their own custom engines, but that's an entirely different article. There are a couple of JavaScript engines that can compile for multiple platforms — such as Construct 3 and Impact.js — but I wanted the freedom to choose what technologies I used to build every aspect of the game.
To that end, I decided to go with Electron.js for publishing on desktop. It's a powerful desktop framework built on the Chromium binary, allowing a web app to be married to Node.js APIs for extremely powerful JavaScript-backed apps. It actually solves all 3 of the goals I listed in the previous section.
While I ultimately settled on Electron, I did review a couple other options...
NW.js has been around since before Electron was released, but Electron's development ecosystem is much more robust. Solving issues with Electron is generally easier just because it has a larger userbase with more content written about it.
Tauri and Neutralino are really interesting as well. They're written in Rust and C (respectively), so the overhead of building with them is minimal and apps tend to be very zoom zoom. That said, they also use the native OS WebView. That's fine, but it means you have to deal with browser inconsistencies (Edge on Windows, Safari on macOS, etc). Electron bundles its own Chromium with each build, so you can focus on building for the same web environment regardless of OS.
UI Is UwU
The first thing any user encounters when playing a game is UI. Loading screens, menus, settings... it's all UI. The web is brilliantly suited for building UI, so that's where I really started the development journey. I'm using React.js for managing UI rendering and interaction. While there are lots of other frontend frameworks I could use, React is the most mature of them with the largest ecosystem.
I'm also using Framer Motion to animate all of the panels and buttons that slide in and out of view, adding a lot of life to the UI.
Game Engines, Shmame Shmengines
There are several game engines built for JavaScript, but they all feel heavier than necessary for Debug. A renderer, pathfinding, and input capture were the primary requirements; everything else the engine provides is cruft.
We can get away with ditching a lot of typical game engine needs since we only have 1 character. The whole game is 2D, so there's no need for any fancy 3D tooling. I don't even need a physics engine. Instead of using something off-the-shelf, I decided to build everything from scratch. This was ~~probably~~ definitely a bad choice, but I've been enjoying myself anyway. 😅
Rendering
For rendering, I started by building a custom renderer based on the one we'd built for the original version of the game. It was simple, but it solved a lot of traditional rendering challenges in novel ways.
Unfortunately, it used Canvas2D which is quite slow, and it had become impossible to work with due to being a convoluted mess. I recently decided that rather than continuing down this path, I should shift gears and replace the old renderer with Pixi.js. It's been a dream so far. Pixi uses WebGL by default, which provides significant performance benefits over Canvas2D. 🤩
Pathfinding
Pathfinding is the core of everything in Debug, so to call it important would be an understatement. I started out using an outdated JavaScript pathfinding implementation, but a couple of months ago I ripped that out and replaced it with the amazing ngraph
library. ngraph
handles pathfinding much more efficiently.
As an aside, I'm also using ngraph
for a really awesome UI navigation experience when using a gamepad. I may write an article on it at some point. 👀
Input Capture
I'm still handling this one manually. I'm currently using the JS Gamepad API to handle controllers, and I've implemented a similar system for tracking keyboard keys and mouse movement/clicks.
State Management
Since the UI and the game engine need to share a fair amount of data, the state management decision was vital. It had to be fast, and it had to be simple. In the past I've invested a lot into learning and using libraries like Redux and MobX, but they're incredibly heavy and they get in the way of development a lot. I switched to using Zustand for a lot of projects in 2022, but even Zustand's relatively simple APIs provide enough footguns that it was slowing down my development.
Earlier this year I discovered Statery, made by the inimitable Hendrik Mans. Statery provides exactly the right amount of control for usage across different control paradigms. It's simple, but its simplicity actually makes it difficult to shoot yourself in the foot.
Wrapping It Up
There you have it - a behind–the–scenes look into the tech stack that makes Debug tick. From JavaScript as the language of choice, Electron for desktop publishing, React and Framer Motion for creating the UI, to Pixi, ngraph, and handcrafted systems for the game mechanics, and finally, Statery for state management.
Every piece of the stack has been chosen with care, each one playing a critical role in building the game. It's been a journey of learning, experimenting, and often just trial and error. But that seems to be what game development often looks like, especially when basically none of us have any idea hwat we're doing. 😅
Keep in mind: this absolutely is not the right tech stack for every game. That said, it's been a dream to use for building Debug. The technology choices I've made have allowed me to meet a lot of goals while overcoming all of the challenges I've encountered so far. And remember, at the end of the day, the best tools are the ones that enable you to bring your vision to life.
If you have further questions or want to discuss any of these topics in more detail, make sure to join our Discord! Thanks for taking the time to read about how Debug is made. Stay tuned for more insights and updates as I continue to develop and enhance the game. ❤️