Dynamically Refreshed Assets in XNA

March 23rd, 2009 | 5 comments

One of the things I wanted to tackle for my framework is the idea of dynamically reloading assets that were altered while the game is running. The first step is just laying down the basic idea of how to accomplish such a thing. So here’s my first stab.

First we define a common base for all assets that will be able to be refreshed. These all need a file path and the date the file was last written to. We also define an abstract Load method that will handle actually loading the asset for us. This isn’t the most robust solution as there’s no guarantee that the derived class’s Load method will use the proper file, but like I said this is just my first go at such a system. Here’s the class I came up with:

public abstract class AutoReloadedAsset
{
	public string FilePath { get; private set; }
	public DateTime FileWriteDate { get; set; }

	protected AutoReloadedAsset(string file)
	{
		FilePath = file;
	}

	public abstract void Load(GraphicsDevice graphicsDevice);
}

Pretty basic, really. Next I needed to define a game component that would monitor changes in the game window’s focus and make sure that anytime the game became active any changed files were updated. This is how mine turned out:

public class AutoReloadedAssetManager : DrawableGameComponent
{
	private readonly List<AutoReloadedAsset> assets =
		new List<AutoReloadedAsset>();
	private bool loaded;
	public ReadOnlyCollection<AutoReloadedAsset> Assets { get; private set; }

	public AutoReloadedAssetManager(Game game)
		: base(game)
	{
		Assets = new ReadOnlyCollection<AutoReloadedAsset>(assets);

		// use the Activated event to know
		// when the user returns to the game window
		game.Activated += game_Activated;
	}

	public void AddAsset(AutoReloadedAsset asset)
	{
		if (assets.Contains(asset))
			return;

		assets.Add(asset);

		if (loaded)
		{
			asset.FileWriteDate = File.GetLastWriteTime(asset.FilePath);
			asset.Load(GraphicsDevice);
		}
	}

	public bool RemoveAsset(AutoReloadedAsset asset)
	{
		return assets.Remove(asset);
	}

	protected override void LoadContent()
	{
		foreach (var a in Assets)
		{
			a.FileWriteDate = File.GetLastWriteTime(a.FilePath);
			a.Load(GraphicsDevice);
		}

		loaded = true;
	}

	void game_Activated(object sender, EventArgs e)
	{
		foreach (var a in Assets)
		{
			DateTime fileWriteDate = File.GetLastWriteTime(a.FilePath);
			if (fileWriteDate.CompareTo(a.FileWriteDate) > 0)
			{
				a.FileWriteDate = fileWriteDate;
				a.Load(GraphicsDevice);
			}
		}
	}
}

Again, a pretty straightforward class. I have a private list to hold the actual assets, giving the user two methods with which to add and remove assets. For fun I decided to also make a read only collection available as well so users can iterate the list if needed (but not edit it directly). The class also makes sure to keep track of when it is actually loaded so that assets added later are automatically loaded when added.

Then the last thing was the actual texture wrapper I wanted to test this out. This one’s just as simple as the other two:

public class AutoReloadedTexture2D : AutoReloadedAsset
{
	public Texture2D Texture { get; private set; }

	public AutoReloadedTexture2D(string file)
		: base(file)
	{
	}

	public override void Load(GraphicsDevice graphicsDevice)
	{
		Console.WriteLine("Loading texture");
		Texture = Texture2D.FromFile(graphicsDevice, FilePath);
	}

	// define an implicit cast for ease of use later on
	public static implicit operator Texture2D(AutoReloadedTexture2D tex)
	{
		return tex.Texture;
	}
}

Because I’m lazy I also provided an implicit cast operator to the Texture2D type. Probably not the best design decision, but sometimes I let laziness guide me.

So with that in place I created a blank image and whipped up a quick test application as you see here:

public class Game1 : Game
{
	SpriteBatch spriteBatch;
	AutoReloadedTexture2D texture;

	public Game1()
	{
		new GraphicsDeviceManager(this);
		Content.RootDirectory = "Content";
		IsMouseVisible = true;
	}

	protected override void Initialize()
	{
		var assetManager = new AutoReloadedAssetManager(this);
		Components.Add(assetManager);
		texture = new AutoReloadedTexture2D("test.png");
		assetManager.AddAsset(texture);

		base.Initialize();
	}

	protected override void LoadContent()
	{
		spriteBatch = new SpriteBatch(GraphicsDevice);
	}

	protected override void Draw(GameTime gameTime)
	{
		GraphicsDevice.Clear(Color.CornflowerBlue);

		spriteBatch.Begin();
		spriteBatch.Draw(texture, Vector2.Zero, Color.White);
		spriteBatch.End();

		base.Draw(gameTime);
	}
}

Then I ran it. The texture appeared as expected. I then went and edited the file. When I brought the game window to focus, the texture was automatically updated to reflect the new changes.

It’s my first start, but this system will likely grow to be a nice, complex system that I can use for my games and editors. As I’m moving forward, I’m liking the idea of building the editor right into the game itself so this is a first step for that.

On a side note, let me know what you think of the syntax highlighter used for this post. I want to get some highlighting on this site with all the code I post, but I want it to work nicely. This one has a nice feature of viewing the code in a new window without the line numbers (for copying it), but it runs as a JavaScript after the page loads which sucks. I’m looking into others, but let me know what you think until I find another one I like.


Possibly Related Posts

(Automatically Generated)
Catching Exceptions on Xbox 360
Pong in F# with XNA Game Studio
Pixel Man Post Mortem #3
A More Robust Exception System
Life of an XNA Game

  1. Gary
    March 24th, 2009 at 06:54

    Code highlighter works well for me.

    Was there a reason you didn’t go with a FileSystemWatcher on your content directory?

  2. March 24th, 2009 at 07:10

    When I considered implementing a similar system, I thought using the FileSystemWatcher class ( as Gary suggests ) was a better way to go as it already had filtering and subdirectory capabilities built into it. It would also be able to handle the case where the asset is updated while the game is running and you haven’t switched out of it yet, such as background dowloading.

    BTW, I like the highlighter, which one is it?

  3. March 24th, 2009 at 07:28

    @Gary: Because I didn’t know it existed. :) I’ll probably use that for my second go at the system.

    @Michael: I use a Wordpress plugin called SyntaxHighligher Pro which uses this as it’s core: http://code.google.com/p/syntaxhighlighter/

  4. Gary
    March 24th, 2009 at 13:16

    Ah. I thought it might be some ugly performance issue or something. I’ve only ever used it once (in the place-of-past-employment’s updater web service) but it only watched one file, not potentially hundreds of them. Looking forward to read how that works out.

  5. 16 ms
    April 15th, 2009 at 13:17

    Nick, good stuff here. I stumbled across this post via somewhere else Community Games related. Anyway, I found this post particularly interesting because I tackled this very same problem back in February. Here was my take on it: http://16milliseconds.blogspot.com/2009/02/dream-build-play-2-editor.html

    Looking forward to seeing how the conjoined editor/game stuff evolves.

You must be logged in to post a comment.