Skip navigation

I posted the following on the Card Kingdom development blog.

Content management is a large part of any game. In terms of our engine, “content” is any external resource that needs to be loaded into the system. Shaders, textures, models, and sound files are all content that the system needs to load. Another aspect of content is that it only needs to be loaded once. Loading the same file multiple times is a waste of resources and load time. Creating instances of the content is more efficient in both memory and time. Multiple instances of the same content resource can be created and destroyed with no consequence in the original resource.

In the engine, this mechanic is realized with the ContentManager, IResource, IResourceCreator, Resource Instances and aliases.

The ContentManager functions as its name suggests: it manages content and has these main functions:

void registerFileExtension(
	const string& fileExtension,
	IResourceCreator* creator
);
bool loadResource( const string& filename, const string& alias );
IResource* getResource( const string& alias );

The first function, registerFileExtension(), registers an IResourceCreator to a particular file extension. These IResourceCreators create IResources depending on the file extension. For example, mesh resources are created when the file extension is “.hkx”, sound resources for “.mp3” or “.wav”, texture resources for “.jpg” or “.png”, and so on. The second, loadResource(), loads a file specified by the file name and gives it an alias by which it can be retrieved later. This abstracts out the need to know the exact file path to get a file. It also means that new content can be replaced easily by just changing the file path. The last function, getResource(), returns a pointer to the IResource with the given alias.

IResources are defined as the following:

class IResource {
public:
	IResource() {};
	virtual ~IResource() {};

	virtual bool load() = 0;
	virtual void unload() = 0;
};

The two main functions a resource can do is load() and unload() and do what their names suggest; loads or unloads the content and stores it in memory. load() is called in ContentManager::loadResource() and returns true if it successfully loads and false otherwise.

For Resource Instances, there is no interface or abstract class that can wrap this as each implementation needs to be different. The basic concept is the same for each though: each Resource Instance holds a reference to an IResource and may have extra information that is specific to that instance of the resource. A great example of this is sound and music. The same swoosh or explosion sound can be played in multiple locations at the same time. To realize this, there is a single swoosh or explosion sound resource that is loaded and multiple sound instances of those two resources that can be played in multiple locations with different parameters for each instance (volume, location, pitch, etc.)

Resource Instancing leads right into mesh instancing, but is more complex than just rendering the mesh in two different locations. For static meshes, this works fine; but for animated meshes, this just doesn’t work as expected. The real killer to this process is the animations. Sharing animation state across multiple instances gives you vastly incorrect results as each instance is trying to set the state of the animation of a single mesh at the same time. To fix this, each instance needs to keep track of the animations for the mesh.

The above image shows the basic process of loading in the mesh file, creating the mesh data as a resource, and then instancing the resource with their own animation states. When rendering, the mesh data uses the current instances animation state to render the model properly.

The above images shows the results of model instancing. Each model uses the same underlying mesh data, but each has their own animation state.