Building our MMO Engine from Scratch: a brief overview
Added 2025-05-27 19:06:51 +0000 UTCSo here I am, sitting in front of a blank document, wondering which of the 33 things in my mind I'm supposed to write about for our first dev blog post.
The obvious choice would be to get into some technical deep-dive: maybe explain how we're doing our net code, or walk through our Lua-to-C# component binding system. But honestly? I think that would be boring, and a lot of it would be lost on the reader if they’re not familiar with our architecture first. Thus, all the above and more will be detailed in future posts, but for now:
Building our MMO Engine from Scratch
Before I dive into the technical stuff, I should probably mention something: I don’t have a computer science degree. Hell, I don’t have any institutional programming education at all (I’m formally a linguist!). I’m just someone who really, really enjoys programming and systems design. It’s like a weird hobby that got completely out of hand.
When I decided we needed to build our own MMO engine, I knew I had to do my homework. So, I went down a massive research rabbit hole. I Devoured Game Engine Architecture, Game Programming Patterns, and pretty much every RPG and MMO architecture blog post I could find. I read the entire Unity documentation—all of it, even the obscure stuff. Same with Godot’s docs. I even spent weeks digging through parts of the Godot source code, trying to understand how they handle scene trees and serialization. (Serialization will get its own post pretty soon, as it is one of the most surprisingly complicated and frustrating systems to get right for a game of this scale)
The Irony wasn't lost on me: here I was, learning everything I could about engines I couldn't actually use.
But all that reading paid off. It gave me a really solid foundation for understanding what makes engines tick, what design patterns work well, and more importantly, what problems no one wrote about.
The Uncomfortable Truth About Audio Game Development
Allow me to go on a little tangent. Here’s where things got weird for me. After diving deep into mainstream game engine architecture, I started looking at what we have in the audio gaming community. And honestly? Let’s say I noticed a pattern that seemed to hold back progress in some areas.
Most audio games are built with ancient tools, or cobbled-together frameworks with architecture that ignores basically every advance in game development since 2010. And I really get why this happens. I’ve been there myself. When you’re a solo developer trying to ship a game, you use what you know and what works. But the result is that our entire community is stuck reinventing the wheel, over and over again, mostly without telling anyone else how to make a similar wheel.
It feels like the entire community decided that sharing knowledge might hurt their competitive edge, when really it just keeps everyone stuck using primitive tools and solving the same problems badly.
So we figured, why not document what we’re building? We’re using some game development patterns that might be interesting to other audio game developers, and it feels weird to just keep all that to ourselves when it could help some people. Plus, as Talon put it, keeping knowledge locked up is “How you get the hundredth fps where you can’t move diagonally.”
The development stack
Our core engine runs on dotnet, and is written in C#. We considered many options, like C++, Rust, the usual low-level languages you’d expect a game engine to be written in. We’re a small team though, and we have a game we want to have a hope of shipping. C# sits on a quite perfect line of being a high-level language with excellent performance, with constructs to drop down to low level optimizations should we want them.
We use OpenAL-soft as our audio engine, LiteNetLib for reliable UDP for our net code, and MongoDB as our database. There will be a post at some point about why we picked a no-sql database, and our struggles with sql and Microsoft’s entity framework core as we used to use that. Finally, Lua is the scripting language we chose to embed for runtime scripting and it’s what some designers will use to bring the world to life! Again, all of the above will get some dedicated posts. There’s just so much to talk about, too much to put in one post.
The building blocks (entities, components, templates, assets…)
At the heart of our engine is a component-based architecture, though not quite an ECS. It's "ECS without the systems," as I like to think of it. We have entities, we have components, but we handle the logic through events and lifecycle methods on components rather than systems that iterate over component pools. This gives us a lot of the flexibility of ECS while keeping the mental model simpler.
Everything that exists in the game world is an entity. Players, NPCs, items sitting on the ground, doors, invisible trigger volumes, they're all entities. An entity by itself is basically an empty container with a unique ID and a display name. They need components to gain behavior. Components are defined in code, with fields that can be configured by the in-game editor, so a lot of the time, creating new entities is just a matter of mixing and matching existing components through the in-game editor, with different configurations, without touching a single line of code.
We also have templates, which is our version of a prefab. Templates are entities, except they exist only to be cloned and don’t participate in physics or game logic. It means you could open up the in-game editor, create a new entity as a template, place components on it and configure them, and that becomes a blueprint that you could spawn entities off of. When a template is updated, all entities that were spawned from that template will also get the updates, preserving any state that this specific instance might have overridden. This is really powerful because it allows for iterative design where you could see your changes immediately.
And finally, assets. Assets is honestly a terrible name for what these are, but it works. They are pure data objects that define the properties of things. They're not entities, they're not components. They're more like configurations that everything else references. Let's take the Race asset type for example. Instead of hardcoding "Elf has +2 Dexterity" into our code, we create a Race asset. Our code defines what fields a Race can have (name, description, stat bonuses, etc.), and then designers use the asset editor to create actual race data: "Elf" with its specific stat bonuses, "Dwarf" with different bonuses, and so on. Now, the power of this system is that designers can create new races, edit existing ones, or rebalance stats without touching any code. And since multiple components can reference the same Race asset, updating the "Elf" race immediately affects every elf character in the game. We organize assets into collections, which are basically folders. All the Race assets go in the "Races" collection, all the Class assets go in the "Classes" collection, and so on.
The beauty of this system is its flexibility. Want to create a new type of NPC? Just create an entity with the right mix of components, without having to touch code. Want to add a new class? Just create a new class asset, without having to touch code! Want to prototype a new game mechanic? Write a new component and start attaching it to entities (You will touch code here)
Wrapping up
I'm genuinely sorry if this feels like I just threw a textbook at your face. That wasn't the plan, I swear. I actually think this chaotic brain dump might be useful as a foundation. All those upcoming posts I keep promising are going to make a lot more sense if you have this baseline understanding of our overall game architecture. Future posts will be way more focused, I promise. Less "here's everything at once" and more "here's one specific thing we built and why we built it that way," with code samples. Indeed I’m not a writer, and I’m still trying to figure out the best way to deliver content like that.
Thanks for sticking with me through this organizational disaster. Feel free to comment, or we could also have a chat on Discord!