Building a mod system for a game
I decided I wanted to build a game with an inventory system and items to populate the inventory. This isn’t a unique feature, so I decided to do some research to see how others do it. There are a couple of ways I saw item systems get implemented, but most usually deal with an item data structure referencing a series of systems, components, or properties which are executed when the item is used. This allows the developer to quickly extend capabilities and reduce code duplication while allowing the game designer to quickly create items since the item itself does not implement any functional code, it just describes the item’s values and which systems/components to run.
I want my game to be modded, though. Ideally I need to open the system up a little more in order to allow others to write the features that the item describes and add them to my game.
I began to think about a moddable item/property system. The idea isn’t too far different from the original underlying method. Conceptually, you have an item that acts as a pure data structure and properties which implement the features described in the item.
The item data structure has an id, name, description, perhaps the name or location of textures and icons, and then it has a list of property id’s that are referenced. When the item is “used” it runs each attached property and loops through their execution in the update loop.
The trick here to make it moddable is to get properties that others have developed into the game for the items to reference.
Exposing the Property Interface
Games that can be modded have feature rich API’s for other developers to work from. It provides the context and structure for the mod code to interact with the game’s baseline code. For others to effectively write code, the game developer would provide the API to the community which they, in turn, would use to encapsulate the new features. The code is ingested by the game and stored in a way that can easily be called during runtime. Here is a quick diagram:
The developer creates the new ideas, the API provides the interfaces which results in new mod code. The user converts it into a .dll which is imported at load into a database using a unique id and the Property class type. At runtime, an instance of the type is created by referencing the unique id.
To expose the Property interface, your best bet is to create an external library that you allow developers to download. They would simply need to create a new project and add your library as a reference before they can create classes that extend your interface or abstract classes.
This is the real tricky part. Allowing a user to reference your libraries does nothing if your code has no way to then accept the external .dll that’s created. Your game needs a way to dynamically find .dll’s and load them at runtime.
Personally, I really like the way Kerbal Space Program’s folder structure looks. It has a GameData folder which then stores discrete directories for each mod. The developer simply needs to compress their compiled code and support content into a .zip file, and all the game user needs to do is unpack and drop the zip file into the GameData folder. Kerbal Space Program is then able to locate the .dll’s and load them into the game.
The first step, to importing new content is to narrow down where to look. We tell the system which folder to look at and it will search in all sub-directories for content. You need to go one step further, though, to make sure you are not accidentally importing .dll’s which are not relevant. In theory I don’t think it would matter because we will do checks to make sure we are importing the right classes (and in some cases, depending on how you want to set yours up, you might want to allow developers to make a single assembly for everything instead of breaking them into different parts). Regardless, from the base directory I check to find any folder labeled “gameProperties”. If a developer wants to make new properties, under their mod folder they will need to make a new one labeled “gameProperties” and place the code and support files in there. Inside of that, I do a search for all .dll files:
The first string array stores all of the directories which contain gameProperties folders. The next string array holds the list of found .dll files. Once the libraries are found, they are loaded using Assembly.loadFile found in System.Reflection. This step is actually important because I want to load all of the assemblies before I actually try to use them, or there’s potential for errors to crop up due to failed dependencies.
Checking for Property Types
Now that the assemblies have been loaded and stored, it’s time to find any classes which inherit from my Property interface. I only want these class types to be saved so that I can instantiate them later with a keyword. We loop through each assembly which was loaded and get the assembly types:
The first thing you’ll notice is a utility class that I created called AssemblyUtils. There is an interesting quirk with getting all of the class types from an assembly which is actually pretty difficult to avoid in many cases. If there is an issue whatsoever with finding a class type, it throws an error. A common solution that I found was to use a try-catch which attempts to get all types. If it fails, it takes all of the types which were successfully loaded and returns them in a type array:
The method accepts the assembly to check and the type to check against. You’ll see that I check to make sure the interface exists through GetInterfaces(). I use GetExportedTypes() because I want to make sure the classes I’m checking are intended to be public. If there is an error, all of the non-null types are returned.
Back to getting the types stored:
In this case, my property interface requires any implementing classes to have a UID getter. In order to actually get that UID, I create an instance of it through Activator.CreateInstance and cast it into a Property type. Finally, provided the object is not null, I simply add to my database, in this case a simple <string,Type> dictionary, the UID string for the key and the actual class type as the value.
Instantiating at Runtime:
Finally we come to how you can instantiate an imported type during runtime. If you look at the above code, we actually already did it once. We use the Activator.CreateInstance to make a new object which is then returned:
In this case, I require the UID since that is how I will find the type to instantiate, but I also allow the for the change of value, name, and description of the new object, in case there are unique items to be used. Finally, I simply return the object to the calling code.
This is, all in all, a pretty basic setup that should be pretty easy to implement. I’m sure there are easily better ways to improve upon this and add more functionality, but this should be a good head start in thinking about how you can build moddable games using C#.