Background Loading in XNA

Introduction

If you are developing an XNA game for Windows Phone 7, you probably want an effective way to load your game’s content in the background while still presenting a screen to the user. This screen itself should use minimal resources, as you don’t want to slow down the loading process significantly just to show a screen and loading bar.

This article will show you how to load your XNA content in the background and how to show a progress bar to the user during this process. This method also does not use a background thread, as background threads have two major disadvantages that will drastically slow down the entire performance of your game. First, applications on Windows Phone 7 are optimized for single threads, and the overhead of switching to a worker thread may not yield good performance in a game. Second, by having two threads that interact with graphics and game resources, you are forcing them to fight for the locks that XNA uses internally to manage resources. This can add up to a significant overhead on any platform, especially on a cell phone!

Update 12-28-2010: I have corrected a bug where BlendStates were being created for each Texture that was loaded, sometimes resulting in an occasional incorrectly loaded texture. This new version now creates and stores the BlendStates used statically and only once to fix this problem.

No background thread?

Instead of a background thread, we will set up an Asset loading system that runs within the main Update/Draw loop of your game. On each call to your main Game Update method, we will load one asset. This means that the framerate of your game will not be high enough to run any impressive animations, but it will be good enough to show a progress bar. By doing things this way, you let the phone spend most of its time loading the game assets as quickly as possible.

Describing an Asset

To do this, we must create a class to describe each asset that we want our background loader to load. We want this class to describe two types of assets, plain XNA style content that we will load through mostly conventional methods, eg. Calling Content.Load, and more flexible “worker methods” which can do a particular small task during the loading process, such as queuing up some background music, or initializing some internal data structures.

The first thing we will do is describe a delegate for our worker methods. It will represent methods that take a Game object for input, do some work, and return void. Very simply, it’s as follows:

/// <summary>
/// Delegate describing a function that will perform a small bit of loading for the game
/// </summary>
/// <param name="game">Main Game Instance</param>
delegate void AssetLoad(Game game);  

Now, we should write our class data members.

/// <summary>
/// Describes a single asset during the load process,
/// Either contains a key, a content location and type to be loaded
/// or contains a delegate method to call
/// </summary>
class Asset  
{
    public string Key, Loc;
    public Type Type;
    public AssetLoad Call;

    #region Constructors
    #endregion
}

The two content types correspond to the two constructors we make for our Asset class. The first takes a key, a location and a type. This will let us load for example a Texture2D from a location, then save a reference to it by a specific key. The second type will just contain an AssetLoad type method to call during the loading process. We store it, and set the type of this asset to Delegate, which will let our loader know later to make a call to the specified function.

The code is as follows:

#region Constructors
    /// <summary>
    /// Creates an asset which is described by a content location, a key, and the type of content stored
    /// </summary>
    /// <param name="key">string based key for later retrieval</param>
    /// <param name="loc">Content location string</param>
    /// <param name="type">Type of the content, ex. Texture2D, SoundEffect, Model</param>
    public Asset(string key, string loc, Type type)
    {
        this.Key = key;
        this.Loc = loc;
        this.Type = type;
    }

    /// <summary>
    /// Creates an asset which is described by one method to call during the loading process
    /// </summary>
    /// <param name="loader">AssetLoad delegate method</param>
    public Asset(AssetLoad loader)
    {
        this.Call = loader;
        this.Type = typeof(Delegate);
    }
#endregion

AssetHelper

Now we can write our AssetHelper class, this one will store a list of Asset objects to be loaded, and a dictionary which will map the loaded assets from key to the object that was loaded. We create a List of assets statically, which will be loaded sequentially, starting from the top down. One item will be loaded per Update loop of the game. In this example, my first Asset is a call to my FirstLoad method, which prepares some data for the first screen we will show the user. Next is a call to my BackgroundMusic method, which will load and start playing some background music in the game. After that, I have a list of typical XNA assets to load, some fonts, sound effects, and textures. Finally, we will call our FinishedLoading method to wrap things up before the loading of our game is complete.

/// <summary>
/// Asset Helper class which supports loading your game content in small
/// chunks, then retrieving it from a cache later
/// </summary>
public static class AssetHelper  
{
    /// <summary>
    /// Private list of assets, the first asset in the list is guarenteed to
    /// load before the body of any Update or Draw calls is made
    /// </summary>
    private static List<Asset> assets = new List<Asset>{
        new Asset(FirstLoad),
        new Asset(BackgroundMusic),

        new Asset("Sea1", "1_SEA_ONLY_1", typeof(Texture2D)),
        new Asset("Sea2", "1_SEA_ONLY_2", typeof(Texture2D)),
        new Asset("Sea3", "1_SEA_ONLY_3", typeof(Texture2D)),      

        new Asset("boldFont", "BoldFont", typeof(SpriteFont)),
        new Asset("subFont", "SubFont", typeof(SpriteFont)),

        new Asset("EvilLaugh", "EvilLaugh", typeof(SoundEffect)), 

        new Asset(FinishedLoading),
        };

    /// <summary>
    /// Index storing which asset we are currently loading
    /// </summary>
    private static int index = 0;

    /// <summary>
    /// Map between asset keys and their loaded data
    /// </summary>
    private static Dictionary<string, object> data = new Dictionary<string, object>();

    /// <summary>
    /// Cache of the blendstates needed for the LoadTextureStream method
    /// </summary>
    public static BlendState blendAlpha = null, blendColor = null;

    #region Properties
    #endregion

    #region AssetHelper Methods
    #endregion

    #region Game Loading Methods
    #endregion
}

Some Useful Properties

We will also define two useful properties; one to see what the percentage loaded is of our game assets, and one which is a Boolean indicating if we are done loading yet.

#region Properties
/// <summary>
/// Returns the percent of assets which have been loaded as an integer 0 to 100
/// </summary>
public static int PercentLoaded  
{
    get
    {
        return (100 * index) / assets.Count;
    }
}

/// <summary>
/// Returns true if all loading is complete
/// </summary>
public static bool Loaded  
{
    get
    {
        return index >= assets.Count;
    }
}
#endregion

AssetHelper Methods

The main work of our AssetHelper is done by the LoadOne method. This method takes the next unloaded asset off the list, looks at its type, then performs the appropriate loading operation on it. If the asset is a simple XNA type like a SpriteFont, we just call game.Content.Load on the assets locator string and save it into our data Dictionary. I have also included my super fast Texture Loader from my other XNA article for loading Texture2D asset types. If we see an asset type of Delegate, we just call the function the programmer has put in. The Get method lets other parts of your game retrieve the objects which have been loaded by the AssetHelper.

#region AssetHelper Methods
/// <summary>
/// Loads the next asset in the list then returns, advancing the index by one step
///
/// Currently supports:
/// Texture2D loading from raw PNG's for greater speed
/// Model
/// SpriteFont
/// SoundEffect
/// Delegate (Calls a custom worker method)
///
/// Note: This is where you would implement any additional asset types
/// </summary>
/// <param name="game">The game underwhich you are running</param>
/// <returns>True if all assets have been loaded</returns>
public static bool LoadOne(Game game)  
{
    if (index >= assets.Count) return true;

    Asset nextAsset = assets[index];

    if (nextAsset.Type == typeof(Texture2D))
    {
        data[nextAsset.Key] = LoadTextureStream(game.GraphicsDevice, nextAsset.Loc);
    }
    else if (nextAsset.Type == typeof(Model))
    {
        data[nextAsset.Key] = game.Content.Load<Model>(nextAsset.Loc);
    }
    else if (nextAsset.Type == typeof(SpriteFont))
    {
        data[nextAsset.Key] = game.Content.Load<SpriteFont>(nextAsset.Loc);
    }
    else if (nextAsset.Type == typeof(SoundEffect))
    {
        data[nextAsset.Key] = game.Content.Load<SoundEffect>(nextAsset.Loc);
    }
    else if (nextAsset.Type == typeof(Delegate))
    {
        nextAsset.Call(game);
    }

    index++;

    return false;
}

/// <summary>
/// Returns an asset by its key
/// </summary>
/// <typeparam name="T">The type of this asset</typeparam>
/// <param name="key">The string based key of the asset</param>
/// <returns>The asset object representing this key</returns>
public static T Get<T>(string key)  
{
    return (T)data[key];
}

/// <summary>
/// LoadTextureStream method to speed up loading Texture2Ds from pngs,
/// as described in
/// http://jakepoz.com/jake_poznanski__speeding_up_xna.html
/// </summary>
/// <param name="graphics">Graphics device to use</param>
/// <param name="loc">Location of the image, root of the path is in the Content folder</param>
/// <returns>A Texture2D with premultiplied alpha</returns>

private static Texture2D LoadTextureStream(GraphicsDevice graphics, string loc)  
{
    Texture2D file = null;
    RenderTarget2D result = null;

    using (Stream titleStream = TitleContainer.OpenStream("Content/" + loc + ".png"))
    {
        file = Texture2D.FromStream(graphics, titleStream);
    }

    //Setup a render target to hold our final texture which will have premulitplied alpha values
    result = new RenderTarget2D(graphics, file.Width, file.Height);

    graphics.SetRenderTarget(result);
    graphics.Clear(Color.Black);

    //Multiply each color by the source alpha, and write in just the color values into the final texture
    if (blendColor == null)
    {
        blendColor = new BlendState();
        blendColor.ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue;

        blendColor.AlphaDestinationBlend = Blend.Zero;
        blendColor.ColorDestinationBlend = Blend.Zero;

        blendColor.AlphaSourceBlend = Blend.SourceAlpha;
        blendColor.ColorSourceBlend = Blend.SourceAlpha;
    }

    SpriteBatch spriteBatch = new SpriteBatch(graphics);
    spriteBatch.Begin(SpriteSortMode.Immediate, blendColor);
    spriteBatch.Draw(file, file.Bounds, Color.White);
    spriteBatch.End();

    //Now copy over the alpha values from the PNG source texture to the final one, without multiplying them
    if (blendAlpha == null)
    {
        blendAlpha = new BlendState();
        blendAlpha.ColorWriteChannels = ColorWriteChannels.Alpha;

        blendAlpha.AlphaDestinationBlend = Blend.Zero;
        blendAlpha.ColorDestinationBlend = Blend.Zero;

        blendAlpha.AlphaSourceBlend = Blend.One;
        blendAlpha.ColorSourceBlend = Blend.One;
    }

    spriteBatch.Begin(SpriteSortMode.Immediate, blendAlpha);
    spriteBatch.Draw(file, file.Bounds, Color.White);
    spriteBatch.End();

    //Release the GPU back to drawing to the screen
    graphics.SetRenderTarget(null);

    return result as Texture2D;
}
#endregion

GameLoading Methods

The last section of the AssetHelper code contains the implementation of the FirstLoad, BackgroundMusic, and FinishedLoading methods we told it to call during the loading process. With this system, the FirstLoad method gets called before any other work is done in your game, therefore it performs a bit of extra work to set up your splash screen. This will contain the code to load one splash screen image and a font we will use to draw a progress indicator. You can be flexible and write whatever else you need in here.

#region Game Loading Methods
/// <summary>
/// This method does any additional loading required before the first screen is
/// ever presented to the user. Currently, it loads the assets needed to display a splash screen
/// </summary>
/// <param name="game"></param>
private static void FirstLoad(Game game)  
{
    data["Sea1"] = LoadTextureStream(game.GraphicsDevice, "1_SEA_ONLY_1");
    data["mainFont"] = game.Content.Load<SpriteFont>("mainFont");  
}

Next, we define a BackgroundMusic method which will load and start a background sound effect shortly into the loading process.

/// <summary>
/// This is an example use of a Delegate asset type, where we want not only
/// load the background music, but start playing it
/// </summary>
/// <param name="game"></param>
private static void BackgroundMusic(Game game)  
{
    SoundEffect soundEffect = game.Content.Load<SoundEffect>("BackgroundSong/Background");
    SoundEffectInstance instance = soundEffect.CreateInstance();
    instance.IsLooped = true;
    instance.Play();
}

Finally, we have the FinishedLoading method which will be called at the end.

/// <summary>
/// Another example of a Delegate asset type, where we play a little sound to the user
/// at then end of asset loading
/// </summary>
/// <param name="game"></param>
private static void FinishedLoading(Game game)  
{
    Get<SoundEffect>("EvilLaugh").CreateInstance().Play();
}

Putting it all together

Now that we have our asset helper, all you need to do it call AssetHelper.LoadOne() at the very top of your main Game Update Method. At all times, AssetHelper.PercentLoaded can be used to display a progress bar, and once AssetHelper.Loaded is true, you can start displaying and updating your main game code. Anytime you need a game asset, just call AssetHelper.Get<{Type}>(“{AssetName}”);

Here is code for my update method:

/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)  
{
    // Allows the game to exit
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();

    //Call into the Asset helper to load your assets
    AssetHelper.LoadOne(this);

    // Show a simple example fade effect
    if (AssetHelper.Loaded)
    {
        if (fade < 255)
            fade += 4;
        else
            fade = 255;
    }

    base.Update(gameTime);
}

Here is code for my draw method:

/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)  
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);

    if (!AssetHelper.Loaded)
    {
        //Currently loading, display progress
        spriteBatch.Draw(AssetHelper.Get<Texture2D>("Background"),
            new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);

        spriteBatch.DrawString(AssetHelper.Get<SpriteFont>("mainFont"),
            "Loading Progress: " + AssetHelper.PercentLoaded + "%", new Vector2(50, 50), Color.DarkRed);
    }
    else
    {
        //Game is loaded, hoorary, do a silly little fade
        if (fade < 255)
        {
            spriteBatch.Draw(AssetHelper.Get<Texture2D>("Background"),
                new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height),
                new Color(255 - fade, 255 - fade, 255 - fade, 255 - fade));
        }

        spriteBatch.Draw(AssetHelper.Get<Texture2D>("Sea1"),
            new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height),
            new Color(fade, fade, fade, fade));

        spriteBatch.DrawString(AssetHelper.Get<SpriteFont>("mainFont"), "Loaded!!!", new Vector2(50, 50), Color.LawnGreen);
    }

    spriteBatch.End();

    base.Draw(gameTime);
}

Remember, don’t do any content loading in your game’s LoadContent, do it all through the AssetHelper instead. Although I don’t have exact performance numbers from a real device on this demo, I have applied these methods to another game for WP7. When we had a background thread doing the loading, as well as only the standard XNA Texture2D loader, load times were about 25 seconds. With these techniques, we got down to 5 or 6 seconds consistently!

Please check out Part 1 of this tutorial to learn my trick for speeding up Texture2D loading on XNA. The attached project zip file also contains a full example project to play around with.

Author Description

Jake Poznanski

Random Salad Games • Neat Projects • Donuts

comments powered by Disqus