Legend of Worlds

Players
create
worlds!

Legend of Worlds
Dev Log 3 - How I spent 2 years building my own game engine (Rust, WASM, WebGPU)
June 9, 2024

Welcome back legends! ⚔️

Welcome to the latest dev log for Legend of Worlds, the cross-platform, cross-play, 2D pixel art online sandbox multiplayer game where you can join, play, create, and share player-created worlds!

Today, I'm excited to share the journey of how I spent the last two years building a custom game engine for Legend of Worlds. I'll dive into the reasons behind this ambitious decision, the highs and lows of the process, and our plans for the future. Let's get started!

Building My Own Engine

For the past two years, I've devoted myself to developing a custom, open-source game engine named "Toxoid" for our cherished project, Legend of Worlds. This has been a labor of love, fueled by determination and a relentless desire to innovate. The Toxoid Engine, an efficient, multithreaded engine crafted with Rust, WebAssembly, and WebGPU, that leverages Flecs for it's entity component system, Sokol (C WGPU equivalent) for rendering, and Emscripten for web compatibility. Later in this article, I’ll demystify these technical aspects and explain their practical impact on the game, especially valuable for those not deeply familiar with software development. This unique blend of technologies has enabled us to elevate what's possible in a 2D sandbox multiplayer game, particularly one centered around user-generated content (UGC).

Undertaking such a project was undoubtedly ambitious and time-intensive. With years of experience in tech startups, game development, and high-pressure contracting environments, I've encountered the dual edges of product development. On one side, aggressively cutting scope to release a minimum viable product sometimes led to subpar quality, jeopardizing the product's success. On the other, avoiding such cuts could mean depleting funds before achieving market readiness—both scenarios fraught with high stakes and potential pitfalls.

However, the true beauty of indie development lies in the unparalleled freedom it provides—a freedom that accentuates our role as "artists" in the industry. This autonomy enables us to uphold a vision that champions quality and the best possible solutions, fully unleashing the potential of an idea with a steadfast commitment to excellence. It demands a high level of self-discipline and passion, qualities essential for those of us driven to realize our creative ambitions to their fullest, without compromise.

Partnering with a publisher would indeed be advantageous, providing support and expanding our reach. Nonetheless, these past two years have been critical, allowing me the essential time to meticulously refine the Toxoid game engine. This groundwork prepares us for the swift development tempo that future publishers, partners, collaborators, developers, and players will anticipate. Having this period to develop the engine independently ensures that I can meet these expectations effectively, armed with a robust foundation tailored to my project's unique needs.

Let me go into some of the reasons why this engine had to be made.

Quality of Life

Quality of Life Pixel Art First, let's ponder an existential question: If life, as we know it, is the sum of our conscious experiences, then how crucial is the quality of those experiences? For programmers and engineers, many of these moments are spent in front of a screen, coding. Considering the significant portion of our lives dedicated to this task, shouldn't we aim for this environment to be as beautiful as possible?

Tools

The tools being used, and other factors than the beauty of the final product, mean something as well. Sure one can chisel a beautiful statue without a hammer, but would it be practical? Sure one can chisel the statue with an ugly or mildly broken hammer, but why not a beautiful, hand engraved and smelted, efficient hammer as well? Rust is an elegant programming language. To simplify it, think of it as a modern C++ (the language, besides C# + Unity, most typically used in the gaming industry, especially AAA, due to it's performance). It allows for high level ergonomics, a beautiful syntax, functional programming features (iterators, closures, etc.), and low level expressiveness, which means fantastic performance, and memory saftey without runtime compromises. This low level expressiveness allows for things that other higher level languages with managed code and runtimes (such as C# has) do not allow. Entity Component Systems (ECS) echo this beauty; they are composable, ergonomic, and architecturally sound, effectively separating data from logic. Like Rust, WebAssembly is not just practically useful but also represents the cutting edge, a venture into the new and unknown. These tools for me, were the beautiful hammer (language, technology, tools, engine) used to build the statue (Legend of Worlds).

Compile Times

Compile times are important as well. One has to not only consider how compile times add up over time for a developer, which in and of itself is already a very significant chunk of time. In that sense, it’s invaluable, not only in time but in quality of life. We stare at computers and code for hours, consuming large chunks of our life, such time should be pleasurable, not an excruciating test of endurance. The Toxoid game engine uses WebAssembly for gameplay scripts, which means that theoretically, any language that compiles to WebAssembly (including Rust, C#, AssemblyScript (TypeScript / JavaScript), etc), can be used to execute gameplay scripts at near native performance. This combined with dynamic linking allows my engine to have short compile times, rapid iteration and less frustration.

Lifelong Commitment

Game development is more than a career for me; it's a lifelong passion. For as long as I have the capacity to create and continue working, I simply have to ask myself, what engine do I want to spend the next 30 years in front of making games?

Having worked with a variety of engines, I have a lot of experience with a variety of different types of game engines. Unity, Unreal, open source ones like Bevy, Macroquad, Phaser, MonoGame, HaxeFlixel, OpenFL, etc. I've encountered their numerous limitations, both general and specific to my needs. This led me to develop my own engine, one that I could continue to use for the rest of my career, even into my later years, much like author Terry Pratchett who dedicated himself to his craft right up until his final days. This decision sets me up for a fulfilling lifelong journey, immersed in the art I love.

Discussion in game development often revolves merely around practical considerations. However, what does it mean to truly stand out in this field? Often overlooked are the aspects of quality of life, aesthetic beauty, and the unconventional factors that define success. How do we define success? While we anticipate the success of Legend of Worlds in terms of player engagement and adoption, the underlying code, the tools for creating both user-generated and procedurally generated content, and the quality of the final product are equally paramount. These elements contribute to the game's aesthetic and engineering artistry.

Conclusion

So, I pose the question again to my fellow developers, engineers, modders and gamers: As we spend countless hours in front of computers, wouldn't we prefer to gaze upon something beautiful?

Alternatives

Choosing Engines

Which of course, leads us to the alternatives. There have been several prototypes of this game over the years, ones written in Haxe, C#, TypeScript, Go, and a variety of other languages, as well as various engines such as Unity. Some were very far along prototypes and demos. Sadly, each came with their own issues.

C# Custom Engine

Similar to other UGC games such as Roblox, UGC games like Legend of Worlds are ideally accessible on all platforms, including desktop, the web, mobile, and game consoles, in order to have the greatest reach. This led me to the decision that having the same experience for the game on web was paramount.

One of the options I considered and worked on was a C# custom game engine. This would allow for a faster code native workflow without a dependency on an editor like Unity. However, C# at the time was very slow in the browser when compiled to WebAssembly, and came with it's own heavy runtime (Mono). Even then, the performance would still only be interpreted performance (very slow) in the browser due to the limitations of Mono and sandboxed WebAssembly in the browser for generation arbitrary code pages and JIT compilation, which meant performance on my custom C# engine was not acceptable. The build times for AOT compilation for C# were also unacceptable slow at the time. I planned to run on Nintendo Switch as well. This is made possible in Toxoid using Rust as a low level language with no runtime, where as languages like C# with the .NET runtime needs custom licenced solutions like BRUTE or Unity to run on consoles.

TypeScript Custom Engine

So why not TypeScript / JavaScript which is more native to the web? Well this was a dynamically typed JIT compiled language, which meant for a game as ambitious as this, it would have performance issues, as well as even more intense performance issues if we were try to run it on consoles due to consoles often having restrictions on JIT compilation as well, meaning the JavaScript language would then have to be intepreted. Not only that, but it would be much more difficult to write bindings to the console SDKs from a high level language, and we would have overhead between boundaries, further adding to performance issues. CrossCode for example, while a fantastic and successful indie game, was a originally a JavaScript / browser game. There was custom contracting done by a Haxe community developer, for a custom JavaScript runtime for consoles similar to BRUTE for C# but for JavaScript. Due to inherent limitations, the result was while it was a playable game, reviews had pointed out performance issues / stuttering and less than 60 FPS on Nintendo Switch, and since rendering is typically the greatest bottleneck and this game was a 2D pixel art game and didn't have a million entities or anything like that at any given point, this can be quite dissapointing to some to not get solid 60 FPS performance on a singleplayer 2D pixel art game, especially with modern animation, movement and particles effects to make the framerate more noticeable. This is something I obviously want to avoid for a highly accessible UGC game experience with expanded audience reach.

Unity

Well what about Unity? Certainly this would have been one of the more practical options. But of course, are we thinking about making a quick buck, or are we thinking about making quality games for the next 30 years? If the latter, there are some serious flaws that come with Unity in terms of that level of long term investment.

First of all there's the vendor lock-in, meaning you are under the thumb and dependent on the support of a company for the rest of your game's life. Given that companies in general rise and fall, go out of business, update licensing agreements, stock prices crash, have mass layoffs, etc. This is an additional risk you're adding to the table, as well as locking yourself into their ecosystem and paying them a hefty fee to do so. One also has to consider how you're locked to a specific Unity version, that may also not receive as much support in the future. In my experience in professional environments, managing Unity versions is incredibly tedious and disjointed.

The worst part for me, is the fact that the editor freezes to deterministically refresh assets on every change, including one line of code changed, takes execrutiating long to just change platforms such as from Desktop to Android before doing any work on other platforms, and has very long compile times on other platforms such as mobile, without being able to see what it really looks like in the player on those platforms.

While Unity’s editor can be slow, especially with large projects, a custom Rust engine can be designed to streamline development processes, allowing for faster iteration times. Unity is also a general-purpose engine designed to cater to a wide range of games and applications. This can lead to unnecessary bloat for a specific project. A custom engine allows me to implement only what I need, keeping the engine lean and efficient. Building my own engine also gives me full control over every aspect of the game’s performance, rendering, and networking. You can tailor the engine specifically to the needs of the game, optimizing areas that are critical to my vision.

Not only that, this is also a UGC game, the tools that players use should not be a heavy game engine for Unity or Unreal, which are generalized solutions, vs. using focused tools that are meant for rapid development in a specific sandbox game environment.

Haxe / HaxeFlixel / OpenFL / Kha

Then there was the Haxe engines, and Haxe is a niche language, home to fantastic indie games such as Dead Cells, Wartales, Papers Please, etc. So it has proven to be capable. It also had a special place in my heart because I started off over 10 years ago making Flash games on the web, and Haxe was an evolution of that legacy and had roots in it (OpenFL for example).

However, because it was a niche language, there were many things missing in the ecosystem that I ended up having to develop myself, wasting precious time that could be spent on the engine and game. It also, like these other higher level languages like C#, came with a garbage collector, which meant performance issues. Haxe is a great idea, a language that could transpile to all other major languages, and had console support because of this, but it came with too many tradeoffs. The ecosystem and userbase was small and losing members over time for similar reasons to myself, and never really took off in the mainstream. So while I admire Dead Cells success, this was not the right solution for me, even though I had to hung on to Haxe for many years as my tool of choice.

Bevy (and Macroquad)

Then there is the current leader of Rust game engines, Bevy. Bevy is a fantastic engine, and seems on the surface, closely aligned with my vision of the game. It's written in Rust, uses a fast, elegant entity component system, and compiles to WebAssembly, etc. I appreciate that it did all these things, made people take the Rust gamedev ecosystem more seriously, and picked up where other Rust game engine contenders have slowed or given up development (Piston, Amethyst, etc) had left off.

However, there were a few major issues for me, which may put it lower the the totem poll than even Unity. First all, long, long compile times. This is even with dynamic linking enabled. This is often a problem with Rust frameworks in general (except for Macroquad which magically through engineering genius compiles fast, but is not a full game-engine and does not compile to Emscripten). My engine was made to be focused, use dynamic linking and WebAssembly modules, which meant fast compile times.

A typical large project in Bevy would take me 30 seconds i7-9700k to compile, and at least 15 seconds with dynamic linking enabled (which was often broken). Add some more major libraries and it can take up to 2 minutes or more to compile on a fairly fast CPU. This is not great for rapid gameplay development.

My engine on the other hand, takes 0.2 seconds to compile a large project. This is because every piece of gameplay code is either a dynamically linked library or WASM module that simply uses the data driven entity component system as a universal interface. This is not common in Rust, but we're on the cutting edge of a new paradigm here! This same sandboxed interface can also be given to players / modders / developers for my UGC game. Bevy also does not have a stable release yet, which means investing in it is just as risky as my own engine, except the decisions for breaking changes are made by a third party. Bevy is also massive, which means code changes to the engine are unfamiliar, difficult, overwhelming and within their ecosystem.

Bevy also cannot compile to the Emscripten target, meaning you cannot have C bindings in your project on the web currently. This means we lose access to the vast C / C++ gaming ecosystem. It also does not allow the nessecary reflection / metadata information on components to allow for scripting, such as the C based entity component system Flecs, which for a UGC game with scripting, made it vital, and allows us to take full advantage of the benefits of ECS for a UGC game. The combination of that fact that I needed Flecs, which was written in C, and C libraries could not run on the web in Bevy, which web is a platform I wanted, it made this an untenable option for Legend of Worlds.

Conclusion

In conclusion, I have a more performant engine while still maintaining short compile times, ability to run on consoles / more cross-platform options, allow game scripting, and have no vendor fees.

Entity Component Systems

For the engine, we're using an entity component system called Flecs. This is a blazing fast entity component system written in the low level programming language C. It allows us to have a fast, data-driven, scalable, scripting and architectural solution for Legend of Worlds.

UGC Games

LoW Level Editor Gif

Again, for user generated content games similar to Roblox, GMOD, Arma, Minecraft, Fornite, etc., having a specific set of tools can be really helpful. Roblox pioneered this by making their own editor. Where as games like Fortnite require you to use a hefty generalized engine primarily made for AAA studios like Unreal Engine. This lowers the window of accessbility, and requires a hefty, gigantic download on the users system before they can start developing. People / modders who typically are not software engineers, especially of AAA games.

Legend of Worlds will also have a multiplayer collaborative in-game editor as an option, which mean using external tools will not be tenable. After all, what am I supposed to do, port Unity inside my game, and make it work on the web, mobile, and consoles, with the same menu maze PC UI? Obviously, this was not tenable or even possible depending on licesning constraints.

Lastly and most importantly, an entity component system allows for data driven scripting. This allows a generic, consistent and simple interface for modders / developers to write gameplay scripts for Legend of Worlds, which in many cases will not even require writing code / gameplay logic due to the data driven nature. They can simply define components such as Position { x: 100, y: 100 } for a character, and the game will do the rest, and other members of the community can also create systems which query those components for arbitrary logic, which are scripts that can be shared with the rest of the community, allowing a collaborative or seamless collaboration leading to emergent behaviours due to complex interactions. Imagine for example, adding an OnFire component to an game object, and that's all that's nessecary for something to be on fire in game, lose health and burn down.

ECS excels in environments where a high degree of modularity and dynamism is required. In sandbox games, where players can create, modify, and interact with a myriad of objects and systems, ECS allows for components to be dynamically added to or removed from entities based on runtime conditions. This flexibility is key to supporting the varied and unpredictable nature of user-generated content, enabling a rich, evolving game world without predefined constraints.

Networking / Scalability

LoW Chat Gif

One of the primary strengths of ECS is its cache efficiency, which is crucial when managing the simulation of thousands to millions of entities in a massive, open-world multiplayer environment. Unlike traditional object-oriented approaches where an object contains both data and methods, ECS separates data and behavior. This separation allows for component data of similar types to be stored contiguously in memory. When the game runs and needs to process these components, the CPU can efficiently load and process large blocks of component data with minimal cache misses. This translates to faster computation and smoother gameplay, which are essential for maintaining performance in large-scale multiplayer environments.

The decoupled nature of data and systems in ECS not only boosts performance but also enhances scalability. As new types of entities and components are introduced by either developers or users, they can be integrated into the existing system without disrupting existing code. This makes it easier to expand the game's features and content over time. Additionally, the scalability is crucial in multiplayer settings, where varying numbers of players interact with the game world simultaneously. ECS architecture allows for more predictable and manageable scaling of game logic as player counts increase.

In multiplayer games, particularly those that are massively multiplayer, efficient network communication is critical. ECS structures can help streamline the process of synchronizing entity states across the network. By segregating components that need to be networked (like position, health, or status effects) from those that don't (like rendering details), developers can optimize what data gets sent over the network, reducing bandwidth and improving network performance.

Although ECS can initially seem more complex than traditional object-oriented approaches, it ultimately simplifies the interaction between different game systems. By treating behaviors (systems) and data (components) as separate entities, it becomes easier to manage and understand the relationships and interactions in the game’s architecture. This clarity is particularly beneficial in a community environment, where different developers might be working on different systems but need a clear and consistent methodology to integrate their work seamlessly.

The Journey

LoW House Gif

The journey to get here was long and hard. Full of many adventures, as well as ups and downs. However, it all worked out in the end. In the following, I will detail different parts of the journey that led me here.

Fruitful Optimism

In the beginning, on paper all the justifications for the engine sounded both fantastic and nessecary. I was ready to give it my all. Once I made the decision to use Rust, I wasted no time diving right in. There was of course, a lot to learn when writing your own game engine. But I've written many prototypes of game engines, contributed to open source game engines, and used many game engines. How hard could it be, right?

Shadows of Doubt

Self Doubt Pixel Art

The initial year of developing Toxoid was particularly challenging. Familiarizing myself with the Rust ecosystem and integrating various low-level rendering libraries like wgpu—which serves as an abstraction layer over APIs like OpenGL, Vulkan, DirectX, and WebGPU—presented a steep learning curve. I encountered numerous limitations, especially when interfacing with C/C++ libraries within the Rust WebAssembly target (which turns out did not work on the main supported WebAssembly target for rust, wasm32-unknown-unknown and only on wasm32-unknown-emscripten which is not supported well officially as a target or by the community), often spending considerable time troubleshooting integration issues that were poorly documented and scarcely discussed within the community.

Moreover, the decision to build a custom engine using emerging technologies like Rust and WebAssembly was met with mixed reactions from peers. While many of my developer friends offered their support and excitement, others questioned the wisdom of investing significant time and effort into such an experimental and unproven approach. They wondered why I would choose to navigate the complexities of low-level engine development, especially given the well-established alternatives available.

This skepticism was not unfounded. Developing a game engine from scratch, particularly with experimental and evolving technologies, often felt like navigating uncharted waters. The lack of comprehensive documentation and established best practices meant that every new feature and integration could potentially lead to days of problem-solving and uncertainty.

The more time the development took, the greater the inner battles became—fighting off self-doubt, malaise, and bouts of depression. Our evolutionary wiring isn’t meant for long hours in front of computers; it’s an activity that can become inherently draining. Additionally, the delayed release of Legend of Worlds, a project I am deeply passionate about, added to the emotional strain. Each setback in pushing forward the game's debut compounded the frustration and anxiety, making the development process even more arduous.

Despite these challenges, the belief in the potential of these technologies to revolutionize game development—and my commitment to creating a highly optimized, scalable engine tailored to the needs of Legend of Worlds—kept me motivated. Each obstacle surmounted added a new layer of knowledge and confidence, gradually dispelling the shadows of doubt that had accompanied the early stages of this ambitious project.

No Regrets

In conclusion, I have no regrets. Now I have my ideal engine, instant recompile times, and one that can potentially run on Nintendo Switch or any other console thanks to Rust. All on my on own engine, with no fees that I have to pay to any compaye, such as Unity Technologies. I can also use this engine to build games for the rest of my life, performing on technologies far more performant than anything JS, Haxe or C# could have squeezed out. I also learned so much about engine development, and gained so much experience and knowledge. If I am going to do this for the rest of my life, which is the plan, this is the best long term investment I could have made.

This flexibility is necessary for a UGC game. One such example is Roblox, which uses Luau as executable bytecode on all devices, which is a similar tech to WASM. The only other equivalent options for cross-platform would be the big game engines, which come with heavy vendor fees and slow dev iteration times (especially on non-desktop environments where you can't just hit the "Play" button in the editor like Unity, where as I can just send a WASM module as a "script" over the network / USB to my engine on mobile devices for example). So there would have been some major tradeoff for vendor lock-in.

Maybe even with slower dev iteration time, the stability of the engine (even though they constantly release new versions that break old projects) might have been either just as fast or even faster than developing my own engine. But one must also consider mental fatigue as well, which can also be a project killer. I might be getting something on the screen quickly for prototyping, but are they really suitable for large projects? I've worked in professional environments working on Unity games, and it's a massive pain to do anything. Especially when trying to scale at a company level, and all kind of random massive binaries being added to version control, and maintaining different Unity versions constantly. As the project scales with these projects, difficulty of development and maintenance also (unfortunately) scale, and disproportionality on the side of difficulty as well.

Then after all of that, and all of the tradeoffs and compromises, would it even really do everything I want in the end? All of those tradeoffs plus I would have been vendor locked to Unity, Unreal, Godot or GameMaker (or MonoGame and have no / poor browser support and questionable / no console support) for the rest of my life. As been seen with the controversial changes to Unity pricing the past year, they can also change the prices any time they wish as well.

The dedication has paid off so far, and there were many benefits to taking this approach. I’m going to be quite frank, it was a lot of pain and suffering to get here. I had to make many sacrifices, and there’s still a lot of work to do, but I believed in my dream. I still do. I never will give up. Never have, never will.

We Made It

Celebrate

The engine is at a point now internally where it's ready for the development of Legend of Worlds, which I have been working on in parallel with the engine. Now that the engine is nearing feature completion for my needs, I can soon focus almost entirely on development of Legend of Worlds, with minor maintenance to the engine. There is still some work left to be done before it's a completely polished generic solution useful to general game developers, but it's been available to the public for the last 2 years, and will continue to be developed.

The link to the engine can be found here: https://github.com/toxoidengine/toxoid

Lighting Fixed Screenshot

The Future

Soon we will have a Toxoid Engine website, samples, examples, tests, and documentation.

Legend of Worlds will continue development, and I will be posting more blog posts as features get developed. There are a lot of interesting ideas in this game, an open world sandbox environment, user generated content, minigames, deep learning for AI NPCs and dynamic worlds, procedurally generated dungeons / worlds, PvPvE servers, etc., so be sure to stay tuned!

Also be sure to follow my Twitter / X, @ToxoidGames to follow progress on a more rapid basis!

Thanks you to everyone who has supported me and followed me on this journey, you're all legends, and the future is only bright from here!

Catch you again next time!

-Rou