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.