Resource management is a crucial part of any game engine, so I’m going to give a quick rundown on how we’re managing resources for the new game. During my time at Oddworld I became thoroughly addicted to C++ templates, so our resource management system uses templates. I’m also a huge fan of smart pointers (thanks again to Charles Bloom and the Oddworlders). There are a lot of haters out there who have bad things to say about templates and smarties, but they don’t know what they’re talking about, so just ignore them.
Okay, so the first step is defining what a resource is. In the most general sense a resource is a chunk of data that is used by the game. For the purpose of this post I’m just going to talk about file based resources. The majority of resources are file based, so that covers almost all of them. Some examples of resources are: models, textures, shaders, effects, fonts, etc.
In our engine all resources are loaded and accessed the same way, through the resource manager. The resource manager exposes resources through templated functions. Here is an example of how we load a texture in our engine:
TexturePtr spTexture =
ResourceMgr::Load<Texture>("data\texture\my_texture.dds");
Similarly there are methods for getting a resource as well:
TexturePtrC spTexture =
ResourceMgr::Get<Texture>("data\texture\my_texture.dds");
TexturePtr spTexture =
ResourceMgr::GetMutable<Texture>("data\texture\my_texture.dds");
Now if we want to get a Model resource we simply point the path to the model file and wonderful magic of templates works out the different type.
ModelPtrC spModel =
ResourceMgr::Get<Model>("data\model\my_model.gr2");
With this example you can see that loading and accessing resources is a common interface no matter what type of resource you’re loading. Adding new resources is really easy because all of the code to manage loading and accessing is already done in the resource manager. It also means that no matter where you are in code you always have access to all your resources through the resource manager.
We’ve added an additional construct to our resource management system that allows for factory creation of resources. We enforce some rules for how our resources are laid in the file system and use those constructs to determine the type of the resource. All of our resources register class factories that use their class name as the factory key. So, for example, a Model registers the class factory “Model”, and a Texture registers the class factory “Texture”. When we want to load a generic resource without knowing what type it is the engine determines the type by looking at the parent directory names to find a class factory. If the current directory doesn’t match a registered factory then it continues to walk up the directory chain until it finds one. I find this also keeps the resource data in a clean and orderly state by keeping our resource files organized in the same way they are organized in code. In addition these rules aren’t too draconian, and they allow room for Ryan to have sub-directories to keep his assets organized within these “factory” folders.
You might be wondering why we would need to go through the trouble of having a mechanism for creating a resource without knowing ahead of time what type it is, so I’ll go through an example. Having a generalized system for loading resources is useful for things like game objects. Let’s say we have a treasure chest and it contains a number of game objects that are defined as a list files. For example:
m_vContents:
{
#: "data\GameObject\WeaponDef\ice_rod.txt"
#: "data\GameObject\MeleeWeaponDef\sword.txt"
#: "data\GameObject\ShardDef\blue_shard.txt"
}
As you can see we have three very different things in this treasure chest (an ice rod, a sword, and a blue shard). Now we need to spawn these items when the treasure chest is opened, but because we have a generalized resource manager we don’t need to know anything about what type of game object we are spawning. The code just looks at the resource path to find the appropriate class factory and loads the correct game object definition. Then we use the game object def to spawn the game object. This means we can always just add new game objects, and they will work with everything (in this case treasure chests) without the need to retro fit. When we first started making the treasure chests we filled them with pretty standard stuff, but then I told Ryan that you can put *any* game object into the chest. Naturally he tried putting some spine slugs into a chest, so you open them up and blamo you’re getting attacked by spine slugs! Sweet! Of course we probably won’t have enemies hidden inside treasure chests, but it just goes to show how flexible class factories make the code.