A classic example of this is the MonoGame content loading process which, on a small machine can be lengthy and give the appearance of the target device having crashed completely unless some indication of activity is provided.
The XNA Game system which is the basis for MonoGame defines a nice neat structure for game operations. The Game class is instantiated, The Initialize routine is called which in-turn calls LoadContent where we can load in all the textures, sounds, fonts and whatever else we need to run the game and finally a loop is entered where Update and Draw are called repeatedly in a loop for the life of the game. It stands to reason that if there is a lengthy delay between the call of the LoadContent method and the beginning of the Update-Draw loop then our user may be left staring at an uninterestingly blank screen.
This code shows how to use the .net BackgroundWorker to load content while still running the game loop to show a progress loading screen. As soon as the content is loaded, the game swaps over to normal operations and your users won't be left looking at a blank screen for fifteen seconds.
using Microsoft.Xna.Framework;
using
Microsoft.Xna.Framework.Graphics;
using
Microsoft.Xna.Framework.Input;
using System.ComponentModel;
using System.Threading;
namespace BackgroundLoad
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 :
Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager
graphics;
SpriteBatch
spriteBatch;
SpriteFont
font;
//Used to
determine if we are ready to go
bool
_isInitialized;
int _loadingProgress;
public
Game1()
{
graphics = new
GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.IsFullScreen = true;
graphics.PreferredBackBufferWidth =
800;
graphics.PreferredBackBufferHeight
= 480;
graphics.SupportedOrientations = DisplayOrientation.LandscapeLeft | DisplayOrientation.LandscapeRight;
}
/// <summary>
/// Allows the game to perform any initialization it needs to
before starting to run.
/// This is where it can query for any required services and
load any non-graphic
/// related content.
Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected
override void
Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
/// <summary>
/// LoadContent will be called once per game and is the place
to load
/// all of your content.
/// </summary>
protected
override void
LoadContent()
{
// Create
a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//Here we
can load anything that is absolutely necessary
//to the
basic operation of the game, such as a font to display progress
font =
Content.Load<SpriteFont>("spriteFont1");
//For all other
content we can use a BackgroundWorker
BackgroundWorker
bgw = new BackgroundWorker();
bgw.WorkerReportsProgress = true;
//Give
the background worker something to do:
bgw.DoWork += bgw_DoWork;
//Handle
progress updates:
bgw.ProgressChanged += (s, e) =>
_loadingProgress = e.ProgressPercentage;
//Handle
completion:
bgw.RunWorkerCompleted += (s, e)
=> _isInitialized = true;
//Kick
off the loading process
bgw.RunWorkerAsync();
}
void
bgw_DoWork(object sender, DoWorkEventArgs e)
{ //This
happens on a thread not associated with the
//rest of the
game
BackgroundWorker
bgw = (BackgroundWorker)sender;
for
(int i = 0; i < 100; i++)
{
bgw.ReportProgress(i);
Thread.Sleep(30);
}
/*
*
//Really we may do something like
this...
int p = 0;
_gameTexture1 =
Content.Load<Texture2D>("gametexture1");
bgw.ReportProgress(p++);
_gameTexture2 = Content.Load<Texture2D>("gametexture2");
bgw.ReportProgress(p++);
_gameTexture3 =
Content.Load<Texture2D>("gametexture3");
bgw.ReportProgress(p++);
_gameTexture4 =
Content.Load<Texture2D>("gametexture4");
bgw.ReportProgress(p++);
_gameTexture5 =
Content.Load<Texture2D>("gametexture5");
bgw.ReportProgress(p++);
_gameTexture6 =
Content.Load<Texture2D>("gametexture6");
bgw.ReportProgress(p++);
//.............
//.............
_gameTextureN =
Content.Load<Texture2D>("gametextureN");
bgw.ReportProgress(p++);
*/
}
/// <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)
{
if
(GamePad.GetState(PlayerIndex.One).Buttons.Back
== ButtonState.Pressed)
{
Exit();
}
// TODO: Add your update logic here
if
(!_isInitialized)
{
//Do
things here that you can do without the main resources
}
else
{
//Do
all the normal game stuff here
}
base.Update(gameTime);
}
/// <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)
{
if
(!_isInitialized)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.DrawString(font, string.Format("Loading...{0}%
complete",_loadingProgress), new Vector2(16, 16), Color.White);
spriteBatch.End();
}
else
{
graphics.GraphicsDevice.Clear(Color.Green);
//Place
all your normal game-drawing stuff here
spriteBatch.Begin();
spriteBatch.DrawString(font, "Hello from MonoGame!", new Vector2(16,
16), Color.White);
spriteBatch.End();
}
base.Draw(gameTime);
}
}
}
1 comment:
Thanks a lot for this, I will try and implement this in my MonoDroid Game.
Angus
Post a Comment