Game Engine Tutorial Part III, Chapter 5 – Content Manager

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

12 Responses to “Game Engine Tutorial Part III, Chapter 5 – Content Manager” »

  1. Pingback by Back from holidays. News Recap « Sgt. Conker — December 31, 2009 @ 2:07 am

    [...] Innovative Game's "Game Engine Tutorial" continues with a new part about the Content Manager. [...]

  2. Comment by Peter — December 31, 2009 @ 3:30 am

    The XNA 3.x ContentManager shares loaded assets already as far as I know. I remember I read this in the creators forum somewhere.

  3. Comment by Allan Chaney — December 31, 2009 @ 8:57 am

    Yes XNA already does this. Though there may be utility for this with much larger projects where you don’t want to load everything upfront. I’m not sure becuase I don’t know how XNA caches stuff relative to memory availablity.

    XNA allows you to load all content upfront and then any subsequent loads of a given texture during runtime will simply be returning a reference to the already loaded content.

    This is why many developers pre-load everything upfront. It makes runtime loads instantaneous since it’s just a reference return if it was loaded previously in the contentmanager.

    Here is a brief Cretor’s Club Forum post that spells it out:
    http://poserworldsubscriptions.com/Clothes/baboots.asp

    Allan

  4. Comment by Sean — January 3, 2010 @ 6:47 am

    This is another one of those cases where, even though XNA already does something, I’m either re-implementing it or expanding on it for the sake of demonstration. XNA has it’s own component system, but if I just used that then we wouldn’t learn how to write that kind of code later on when we’re not working with XNA.

    This is just a simple demonstration of a caching system, which is used all the time elsewhere in code, and it also slightly expands on the content manager by allowing the unloading of specific content which may be useful for larger games.

  5. Pingback by Innovation Engine Roadmap 2009-2010 | Innovative Games — January 5, 2010 @ 9:05 pm

    [...] Content Manager [...]

  6. Comment by Tom Looman — February 26, 2010 @ 12:18 pm

    Hi,

    The UnloadAsset() method will never work when passing a Model. I’ve tried using your method inside my project, but the
    “if (loaded[name] is IDisposable && …)” is never True for a Model file. Texture2D works fine, a Model doesn’t use the IDisposable interface. The Model asset should be bound to his own set of IDisposables so it will know which disposables he must unload when the asset is called for unloading. The current implementation simply skips Model assets completely.

  7. Comment by Thomas — October 4, 2010 @ 3:11 am

    Thanks very much, this has helped me hugely. I’m now loading the same model repeatedly and am playing back whichever animation clip I wish on whichever copy of the model. Was really confusing me for a while, seeing the same animation play across them all despite my best efforts!

  8. Comment by Fan of Sean — October 25, 2010 @ 6:36 am

    “specific peaces” :)

    (right in the introducing paragraph)

  9. Comment by Moteriski kvepalai — January 22, 2011 @ 7:12 pm

    I like the helpful information you provide in your articles. I will bookmark your blog and check again here frequently. I am quite certain I’ll learn lots of new stuff right here! Good luck for the next!

  10. Comment by Grayson Peddie — February 20, 2011 @ 8:45 am

    Interesting. Couldn’t I create my own content manager without deriving from content manager that XNA already has? That way, I can port my content management code to SlimDX or OpenTK when I need to, but I’m currently working with creating my own component system that works with OpenTK that does not have one.

  11. Comment by Jai McMahon — June 13, 2011 @ 8:10 am

    Hey,

    I’m not too clued up on C#/XNA just yet so I was wondering if I can ask a question regarding the LoadFreshCopy function:
    By passing ‘null’ as the 2nd parameter to base.ReadAsset function, does that not mean that the ContentManager keeps track of the reference to the loaded asset?
    If it does, then will overriding ContentManager.Unload not cause a leak since the assets loaded from the LoadFreshCopy function have references stored to them in the ContentManager class but that class’ Unload function got overridden and now those asset references don’t get dealt with properly?

    I’m not at all sure how the Garbage Collector works; would that take care of this?

    Thanks for any replies.
    Jai.

  12. Comment by Henri — June 28, 2011 @ 4:08 pm

    From what I can gather you’re right. The code should probably be changed to call base.Unload() from the Unload method. LoadFreshCopy actually doesn’t work at all as intended. It will load a fresh copy once, and then return a reference to the base ContentManager’s instance of the asset.

RSS feed for comments on this post. TrackBack URI

Leave a comment