Before I joined the video game industry, I spent about 5 years making corporate software. During this time, I learned a lot about wearing ties, avoiding red tape, and modularizing software. Needless to say, when I began making video games, I was shocked at the "dump everything in the executable" design pattern. After a few projects, I began to see some benefits and rationale, but I was never completely sold on the idea. Now that I'm on my own, it's time to try modular game development. In this post, I'll describe exactly what I'm doing and why.
The Pantheon Engine isn't a traditional engine. Rather than a bucket of code that you integrate into your project, it's a collection of interconnected modules (DLLs mostly) which can be loaded and used as needed. Each module is named for a Greek mythological figure, often a god. Names are chosen which reinforce the purpose of the module:
* Chaos is a Greek mythological entity representing the primordial universe and the source of creation. He is the namesake for the executable which loads all other modules. This executable essentially loads several DLLs into memory, then enters a loop, ticking all modules and checking windows messages, until it's time to exit.
* Prometheus is the most basic module which defines things like strings, collections, and data file formats. It's currently the only statically linked library, all other libraries are dynamically loaded. Prometheus is named after the Greek god who gave mankind the knowledge of fire.
* Aphrodite is the DirectX 11 module which renders the 3D scene and the UI elements. It's named after the Greek goddess of beauty.
* Apollo, the XAudio2 module, is named after the Greek god of music. It's responsible for all sound including music, special effects, vox, and UI chirps.
* Atlas, named after the Greek titan who holds the heavens upon his shoulders, is the PhysX module. It's responsible for all collisions, movements, acceleration and so on.
* Theseus is a game which allows players to navigate a labyrinth -- or at least it will be. That's my ProcJam project. This module is named after the labyrinth defying Greek hero who slayed the Minotaur.
* Dionysus is a lighthearted shooter where players recklessly romp through an alien world. It's named after the Greek god of merriment.
At startup, Chaos loads into memory. Chaos loads a config file which details the other modules that need to be loaded. Then it loads and initializes the modules specified by the config file. For each module, it queries and stores an interface pointer to the main object in that module. It then enters the main loop which ticks each interface and checks windows messages until it's told to exit. In the future, it will accept run-time requests to replace one module with another.
The obvious draw to this approach is replacabilty. Any module can be removed and replaced with another, just so long as the interface to that module is honored. For example, someone could write a DirectX 9 module, update the config file, and without changing anything else, Chaos would load the DirectX 9 module in place of Aphrodite. Everything would work with no external changes. This is a cool benefit but (with one exception which I'll discuss in a minute) not one that's likely to get exercised during my development. Granted, I love the idea that this could happen, but the honest corner of my brain assures me, YAGNI.
There's another, less obvious, benefit though: enforced decoupling. In the Pantheon Engine, this means that data from one module can't be easily shared with another module. My programming mantra has always been "Make good decisions easy to implement. Make bad decisions hard to implement." Tight coupling of data leads to rigid code. A design which makes tight coupling require effort ensures that tight coupling only happens when it's necessary and only after it's been thought through. Using interface classes between DLLs forces coders to be mindful of the boundaries between code systems and highlights the methods which share data between the systems.
Another benefit is compile and link times. By organizing source code such that only necessary headers are included you minimize compile times. By dividing code into separate modules, the linker has less data to look through when resolving externals.
Last but not least, I gain the befit of (drum roll ...) habitability! That's right, my code can now be hacked! Isn't that great? Yes, yes, I know. "Piracy is the bane of all developers." Why would anyone celebrate it? Because, as an indie, Piracy is the least of my concerns. Obscurity is a fight for life itself. If I can't conquer obscurity then piracy isn't even a topic of discussion. By making my code hack-able mod-able I hope to attract the attention of modders and other developers who want to build their own games using the Pantheon engine. As far as cheating is concerned, my first few games aren't going to support multiplayer, so my users are welcome to cheat if they can figure out how.
Oh, remember that exception I promised to talk about? Well, here it is: the one exchangeable module I expect is the game itself. Simply write your own DLL and Chaos will happily load it and fire up your game. No strings attached.