After a year of full-time development, it is time for the first semi-public release of HeroBound: The Epic Web-Based MMORPG!
We’re looking for anyone who might be interested in participating in the closed beta of HeroBound (Nov. 1 - TBD) to help provide feedback for the game’s ongoing development. If the game looks appealing to you, or even if you’re just a fellow developer looking to learn about the technical details behind implementing a browser-based MMO, we’d love for you to join us.
Edit: Previous issue with the form not validating the length of the responses correctly has been fixed.
For those interested in some of the technical aspects (since I’ve been absolutely terrible at sharing the process regularly on the dev blog), here’s the brief overview:
The game client is built using BabylonJS 7 and React, while the back-end is primarily Bun-powered Typescript. There are, however, several import factors in the game’s architecture that have allowed us to hit nearly 50,000 active player instances on a single server (utilizing under 50% of the server’s CPU), and several thousand rendered players in a client’s immediate region. This was done by utilizing every bit of performance optimization provided by Babylon for rendering (thin instances, baked animations, etc), creating custom web-assembly modules for handling heavy processes and data structures such as pathfinding and priority queues, and not least of all, taking full advantage of binary web socket streaming.
Side note: The µws library blows away any other TCP implementation, and on Bun, the performance benchmarks in a single thread were nearly the same as a multi-threaded C++ server.
As the goal has always been accessibility, there were also some graphical and mechanical tradeoffs made in favor of performance. One important rendering optimization was having the player (and some NPC) models synchronize their animations, so that they can all share a single animated skeleton. While not aesthetically pleasing, this was required to be able to attach equipment meshes to the animated skeleton, without needing a separate skeleton instance for each animated entity. Another solution would be to bake equipment animations, and perhaps that’s something that will be revisited in the future, but it was too cumbersome a solution this early on when the equipment models are frequently added and changed. Additionally, an important mechanical tradeoff was to represent the underlying game world as a 2D grid, which loses some immersion and opportunity for fun features, but gains an incredible performance boost to things like pathfinding. As it stands, the NBA* pathfinding WASM module is so fast, that generating a 64-cell path around dynamic obstacles is 900x faster on the client than the most optimized navigation mesh solution, and on the server, the average pathfinding operation takes less than 10ns!
That’s 0.00000001 second!
This does not necessarily mean that the game is unable to accomidate 3D features (which is already not the case), but it means that unless there is a large architectural change, the game cannot support native 3D elements like 3D paths or obstacles, and instead have to be simulated from a 2D origin.
And while we’re on the subject of the server, let’s talk about the scalable network. There are two primary servers that provide us with the full MMO network: The World Manager, which communicates with the authentication server and synchronizes data between world instances, and the World, of which there can be an infinite number of instances. A World instance, as you might expect, holds its own copy of the game world. Each 2D grid of the world (of which there can be several representing separate isolated areas of the game world) is split into 32x32 sectors. Initially, a World instance is responsible for the entire game world, and under load, begins to divide each grid into its own managed instance, which can further divide down to a single sector (although this is highly unlikely to ever occur). When a player first joins, his or her authentication token is sent from the world to the manager for validation, is provided the persistent player data in response, and is then subscribed to each sector’s channel, within a 2-sector radius. If that channel happens to be offloaded to another instance, the channel is relayed from the primary instance. And likewise, when the player moves to a new sector, their sector subscriptions are updated, and stale entities dropped, only ever maintaining data (and rendering) that 5x5 sector radius.
Admittedly, because RAM is cheap (and amazingly fast on Apple ARM machines – used by our servers), and the back-end is optimized to cache everything to keep CPU usage exceptionally low, the server instances had to be artificially restricted just to test the autoscaling feature, because even at 50,000 simulated, networked players, the initial, single world instance never hit the threshold required to subdivide.
The process has been LONG, and it’s still so far from anything resembling a “complete game,” but it’s slowly getting there. Having to build so many features from scratch due to either not being available or not being performant enough has been extremely time-consuming, but also an invaluable learning experience, even for a seasoned developer. From the ECS engine, to the world builder, to the entity and animation editor, to the custom binary serialization/deserialization module, so much heavy lifting has already been completed. (…If only it felt that way ).
I’d be happy to go further into any details, so if you have a question, don’t hesitate to ask. Hoping to see some new faces come November.