XNA provides a ContentManager class that is used to load and unload content. However, we are going to create our own that caches loaded content so we only have to load an asset once to use it repeatedly, and also supports unloaded specific peaces of content (when entering a different area, for example). Add a new c-sharp code file to your project, and create the class below:

public class IEContentManager : ContentManager
{
}

The first thing we’ll need is a list of all the loaded content:

// Cache of all the loaded content
Dictionary<string, object> loaded = new Dictionary<string, object>();
List<IDisposable> disposableAssets = new List<IDisposable>();

Next we need to create two constructors that match those of the original ContentManager class. We won’t add any extra code here.

public IEContentManager(IServiceProvider services)
    : base(services)
{
}

public IEContentManager(IServiceProvider services, string rootDirectory)
    : base(services, rootDirectory)
{
}

We can now add the Load() function, which reads a single asset from file and stores it, or returns the cached version if there is one.

// Load an asset and cache it
public override T Load<T>(string assetName)
{
    // Return the stored instance if there is one
    if (loaded.ContainsKey(assetName))
        return (T)loaded[assetName];

    // If there isn't, load a new one
    T read = base.ReadAsset<T>(assetName, RecordDisposableAsset);
    loaded.Add(assetName, read);

    return read;
}

void RecordDisposableAsset(IDisposable disposable)
{
    disposableAssets.Add(disposable);
}

As this ContentManager is caching content, we also want to add a way to ask for a piece of content and be guanteed a fresh copy, which can be modified or worked with freely without worrying about other components touching it. We will call this function LoadFreshCopy():

// Load an asset and be guaranteed a clean copy of the object.
// Note that if this function is used this copy of the asset cannot
// be individually unloaded
public T LoadFreshCopy<T>(string assetName)
{
    return base.ReadAsset<T>(assetName, null);
}

Next we will add a function that unloads a specific piece of content (assuming it was loaded with the regular Load() function):

// Unload a single asset
public void UnloadAsset(string name)
{
    if (loaded.ContainsKey(name))
    {
        if (loaded[name] is IDisposable && disposableAssets.Contains((IDisposable)loaded[name]))
        {
            IDisposable disp = (IDisposable)loaded[name];
            disposableAssets.Remove(disp);
            disp.Dispose();
        }

        loaded.Remove(name);
    }
}

Finally, we will add a function that unloads all content:

// Unload everything
public override void Unload()
{
    foreach (IDisposable disposable in disposableAssets)
        disposable.Dispose();

    loaded.Clear();
    disposableAssets.Clear();
}

And that’s it! Now we just need to update the rest of the engine class to use our new ContentManager. Simply replace all the references to the ContentManager class with references to the IEContentManager class. In the engine class, the following three lines of code:

ContentManager content = null;
public ContentManager Content { get { return content; } }

and

content = new ContentManager(Services);

become:

IEContentManager content = null;
public IEContentManager Content { get { return content; } }

and

content = new IEContentManager(Services);

And we’re done! We now have a ContentManager class that will be more useful to us when we start making complex games that require more direct control over content.

Note: much of this post is inspired by Shawn Hargreaves’ post “ContentManager.ReadAsset” (http://blogs.msdn.com/shawnhar/archive/2007/03/09/contentmanager-readasset.aspx).

Download the Code for this Chapter

« »