ILoadableGameState.BeginLoad

May 21, 2011 at 3:51 PM

I cant get a grip of the asynch pattern, what is the minimum code i have to put in beginload to just make loadinggamestate system work ?
  public IAsyncResult BeginLoad(AsyncCallback callback, object state)
        {
     
Jul 3, 2011 at 7:21 PM

I've never done any thing with asynchronous loading either, and I'm having a tough time trying to figure out how to implement ILoadableGameState as well, can anyone out there give us a hand?

Coordinator
Jul 5, 2011 at 9:30 AM

It's the standard asynchronous method pattern from .NET.

Just invoke a method asynchronously, like this:

public class MainMenuGameState : GameState, ILoadableGameState {

  public event EventHandler<LoadProgressEventArgs> ProgressChanged;

  private delegate void LoadResourcesDelegate();

  public MainMenuGameState() {
    this.loadResourcesDelegate = loadResources;
  }

  public IAsyncResult BeginLoad(AsyncCallback callback, object state) {
    return this.loadResourcesDelegate.BeginInvoke(callback, state);
  }

  public void EndLoad(IAsyncResult asyncResult) {
    this.loadResourcesDelegate.EndInvoke(asyncResult);
  }

  private void loadResources() {
    // ... load stuff via content manager here ...
  }

  private LoadResourcesDelegate loadResourcesDelegate;

}

If your not well-versed in multithreaded programming, I'd recommend you to just ignore the ILoadableGameState and LoadingScreenState classes. If there's any synchronization issue in multithreaded code, it usually results in things working fine 999 out of 1000 times and then failing catastrophically once in a while. At the minimum, I'd use a separate ContentManager for the stuff that is being loaded asynchronously that's used nowhere else during this time.

In my current game, I even made sure that after loading finishes, the same thread is used for rendering as has been used for loading, but I don't know if that's really necessary.

Jul 6, 2011 at 9:33 PM
Edited Jul 6, 2011 at 11:10 PM

Where should be the logic for drawing loading animation? I guess it should be placed in some class derived from LoadingScreenState<T>, but this class is derived only from GameState and not from DrawableGameState, so it doesn't have Draw() method.

Could you be so nice and give us some example project of this loading screen in action? Thanks a lot.

Jul 6, 2011 at 11:29 PM
Edited Jul 6, 2011 at 11:34 PM

I'm new to programming, but I've managed to write up a quick example of how this will work passing Services and a SpriteBatch for drawing.

You'll need references to Nuclex.Game.States and System.Threading (for this example only to emulate long loading time)

MyGame.cs

 

namespace MyGame
{
    public class MyGame : Microsoft.Xna.Framework.Game
    {
        private GraphicsDeviceManager graphics;
        private SpriteBatch spriteBatch; 
        private GameStateManager stateManager;
        private LoadingMainmenuState loadingMainmenuState;
        private MainmenuState mainmenuState;

        public MyGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            stateManager = new GameStateManager();
            Components.Add(stateManager);
        }

        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            base.Initialize();
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            mainmenuState = new MainmenuState(Services, spriteBatch);
            loadingMainmenuState = new LoadingMainmenuState(stateManager, mainmenuState, Services, spriteBatch);
            stateManager.Push(loadingMainmenuState);
            // TODO: use this.Content to load your game content here
        }

        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
            if (Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();

            stateManager.Update(gameTime);

            base.Update(gameTime);
        }

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

            spriteBatch.Begin();
            stateManager.Draw(gameTime);
            spriteBatch.End();
        }
    }
}


 

And then two classes for your states, one is the actual loading screen state, and the other is the state being loaded, I've called mine the MainmenuState.

LoadingMainmenuState.cs

 

namespace MyGame
{
    class LoadingMainmenuState : LoadingScreenState<MyGame.MainmenuState>, IDrawable
    {
        private bool visible = true;
        private int drawOrder;
        private readonly ContentManager content;
        private readonly Texture2D background;
        private SpriteBatch spriteBatch;

        public LoadingMainmenuState(IGameStateService gameStateService, MainmenuState gameStateToLoad, IServiceProvider serviceProvider, SpriteBatch spriteBatch) : base(gameStateService, gameStateToLoad)
        {
            content = new ContentManager(serviceProvider, "Content");

 // Load the loading screen's content here in the constructor
background = content.Load<Texture2D>(@"LoadingMainmenuState/background"); this.spriteBatch = spriteBatch; StartLoading(); } public void Draw(GameTime gameTime) {
// Draw your loading screen here
 spriteBatch.Draw(background, new Vector2(), Color.White); } public int DrawOrder { get { return this.drawOrder; } } public event EventHandler<EventArgs> DrawOrderChanged; public bool Visible { get { return this.visible; } } public event EventHandler<EventArgs> VisibleChanged; } }

MainmenuState.cs

 

namespace MyGame
{
    class MainmenuState : DrawableGameState, ILoadableGameState
    {
        private ContentManager content;
        private SpriteBatch spriteBatch;
        private readonly LoadResourcesDelegate loadResourcesDelegate;

        private Texture2D background;

        public MainmenuState(IServiceProvider serviceProvider, SpriteBatch spriteBatch)
        {
            content = new ContentManager(serviceProvider, "Content");
            this.spriteBatch = spriteBatch;
            this.loadResourcesDelegate = LoadResources;
        }

        public override void Draw(Microsoft.Xna.Framework.GameTime gameTime)
        {
            spriteBatch.Draw(background, new Vector2(), Color.White);
        }

        public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
        {
            // Nothing to update yet
        }

        private void LoadResources()
        {

            background = this.content.Load<Texture2D>(@"MainmenuState/background");

            // Sleep to emulate a long loading time (5 seconds)
            Thread.Sleep(5000);

        }

        public event EventHandler<LoadProgressEventArgs> ProgressChanged;

        private delegate void LoadResourcesDelegate();

        public IAsyncResult BeginLoad(AsyncCallback callback, object state)
        {
            return this.loadResourcesDelegate.BeginInvoke(callback, state);
        }

        public void EndLoad(IAsyncResult asyncResult)
        {
            this.loadResourcesDelegate.EndInvoke(asyncResult);
        }
    }
}

I'm not sure this is done correctly at all, but I hope it will be of use to someone.