Loading Resources In The Background With A Loading Screen

Well, after searching for this for the last two days it seems that either no one has figured it out, or no one has shared such information.

So, here goes.

I read about people trying this with opening new threads and trying to put the loading on the update thread or the UI thread to no avail. I found the AsyncTask for android after remembering how we did it with XNA.

Making an AsyncTask is simple, here's the API from google: Here

For those who would like a little more instruction and some code, read on.

As far as how I implement my game as some background, I don't use different activities, I just use scenes. This is my first game and I only need 3 different scenes, I'll probably do activities in the future. (by no means is my way the best way I'm sure, this is for illustrative purposes)

I made my own subclass of scene:

import org.anddev.andengine.engine.Engine;
import org.anddev.andengine.entity.scene.Scene;

public abstract class GameScene extends Scene {

    protected Engine _engine;

    public GameScene(int pLayerCount, Engine baseEngine) {
        super(pLayerCount);
        _engine = baseEngine;
    }

    // ===========================================================
    // Inherited Methods
    // ===========================================================

    protected abstract void onLoadResources();

    protected abstract void onLoadScene();

    protected abstract void unloadScene();

    protected abstract void onLoadComplete();

    // ===========================================================
    // Methods
    // ===========================================================

    public void LoadResources(boolean loadSceneAutomatically){
        this.onLoadResources();
        if(loadSceneAutomatically){
            this.onLoadScene();
        }
    }

    public void LoadScene(){
        this.onLoadScene();
    }
}

From here I've got a subclass of this for each of my three game scenes. I create the scene, tell it to load it's resources and then set the scene as the main scene.

If I provide true to LoadResources it will load the scene automatically. I would use this for anything that doesn't have a lot to load. My PlayScene on the other hand, has all of my game images to load.

Here's where the AsyncLoader comes in.

You have to create your own subclass of AsyncTask as stated in the API. From here you send to it the parameters. I made life simple by creating an interface IAsyncCallback to use as a parameter:

public interface IAsyncCallback {
    // ===========================================================
    // Methods
    // ===========================================================

    public abstract void workToDo();

    public abstract void onComplete();

}

Now, here's my subclass of AsyncTask:

import android.os.AsyncTask;

public class AsyncTaskLoader extends AsyncTask<IAsyncCallback, Integer, Boolean> {

    // ===========================================================
    // Fields
    // ===========================================================

    IAsyncCallback[] _params;

    // ===========================================================
    // Inherited Methods
    // ===========================================================

    @Override
    protected Boolean doInBackground(IAsyncCallback... params) {
        this._params = params;
        int count = params.length;
        for(int i = 0; i < count; i++){
            params[i].workToDo();
        }
        return true;
    }

    @Override
    protected void onPostExecute(Boolean result) {
        int count = this._params.length;
        for(int i = 0; i < count; i++){
            this._params[i].onComplete();
        }
    }
}

This simply calls the workToDo method on each of the parameters (no idea why you would want more than one in this case but that's how AsyncTask is built) and then calls onComplete when it's done.

From here you have to simply create an IAsyncCallback in your onLoadResources with all of your asset loading in the workToDo method, and onLoadScene in the onComplete. Then execute the AsyncTask:

@Override
    protected void onLoadResources(){
        IAsyncCallback callback = new IAsyncCallback() {

            @Override
            public void workToDo() {
                TextureRegionFactory.setAssetBasePath("gfx/game/");
                FontFactory.setAssetBasePath("font/");

                PlayScene.this._backgroundTexture = new Texture(512, 1024, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
                PlayScene.this._backgroundTextureRegion = TextureRegionFactory.createFromAsset(PlayScene.this._backgroundTexture, Game.Instance, "background0.png", 0, 0);
                PlayScene.this._backgroundTextureRegion2 = TextureRegionFactory.createFromAsset(PlayScene.this._backgroundTexture, Game.Instance, "background0.png", 0, 0);
                PlayScene.this._backgroundTextureRegion2.setFlippedVertical(true);

                PlayScene.this._faceTexture = new Texture(32,32,TextureOptions.BILINEAR_PREMULTIPLYALPHA);
                PlayScene.this._faceTextureRegion = TextureRegionFactory.createTiledFromAsset(PlayScene.this._faceTexture,Game.Instance,"face_box.png", 0,0,1,1);

                PlayScene.this._iffishTexture = new Texture(512,512,TextureOptions.BILINEAR_PREMULTIPLYALPHA);
                PlayScene.this._iffishTextureTile = TextureRegionFactory.createTiledFromAsset(PlayScene.this._iffishTexture, Game.Instance, "Iffisch.png", 0, 0, 5, 3);

                PlayScene.this._enemyTextures = new Texture(512,512,TextureOptions.BILINEAR_PREMULTIPLYALPHA);
                PlayScene.this._blowFishEnemyTextureTile = TextureRegionFactory.createTiledFromAsset(PlayScene.this._enemyTextures, Game.Instance, "blowey.png", 0, 0, 1, 1);

                PlayScene.this._faerieTexture = new Texture(64,64,TextureOptions.BILINEAR_PREMULTIPLYALPHA);
                PlayScene.this._faerieTextureRegion = TextureRegionFactory.createFromAsset(PlayScene.this._faerieTexture,Game.Instance,"faerie.png",0,0);

                PlayScene.this._inGameFontTexture = new Texture(512, 512, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
                PlayScene.this._respawnFont = FontFactory.createFromAsset(PlayScene.this._inGameFontTexture, Game.Instance, "ANDYB.TTF", 32, true, Color.WHITE);

                PlayScene.this._hudFontTexture = new Texture(512, 512, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
                PlayScene.this._hudFont = FontFactory.createFromAsset(PlayScene.this._hudFontTexture, Game.Instance, "ANDYB.TTF", 24, true, Color.BLACK);

                PlayScene.this._analogControlTexture = new Texture(512,256, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
                PlayScene.this._analogBGTextureRegion = TextureRegionFactory.createFromAsset(PlayScene.this._analogControlTexture, Game.Instance, "analogControls.png", 0, 0);
                PlayScene.this._analogStickTextureRegion = TextureRegionFactory.createFromAsset(PlayScene.this._analogControlTexture, Game.Instance, "analogStick.png", 0, 75);
                PlayScene.this._scoreHudTextureRegion = TextureRegionFactory.createFromAsset(PlayScene.this._analogControlTexture, Game.Instance, "scoreHud.png", 49, 75);

                PlayScene.this._engine.getTextureManager().loadTextures(_enemyTextures, _backgroundTexture, _iffishTexture, _faceTexture, _inGameFontTexture, _faerieTexture, _analogControlTexture, _hudFontTexture);
                PlayScene.this._engine.getFontManager().loadFonts(_respawnFont, _hudFont);
            }

            @Override
            public void onComplete() {
                PlayScene.this.onLoadScene();
            }
        };

        new AsyncTaskLoader().execute(callback);

    }

Then, just make sure you have setScene in your onLoadScene:

@Override
    protected void onLoadScene() {
        // set this as the main scene after loading
        this._engine.setScene(this);
.
.
. your scene load stuff here
.
.
}

Just about done! Now you create a new GameScene (or PlayScene in this case) and pass false to the LoadResources.
This way it will create the scene, LoadResources will call onLoadResources which will execute the AsyncTask of loading the resources and then call onLoadScene which sets that scene as the main scene and loads up:

I do this from within another scene (I'm working on unloading it after the next one is loaded, so that you'll have to sort out yourself :) ) which creates a loading animation. It will play this animation until all of the next scene's resources are loaded and the new one sets itself as the main scene.

import org.anddev.andengine.engine.Engine;
import org.anddev.andengine.engine.handler.IUpdateHandler;
import org.anddev.andengine.engine.handler.timer.ITimerCallback;
import org.anddev.andengine.engine.handler.timer.TimerHandler;
import org.anddev.andengine.entity.scene.background.ColorBackground;
import org.anddev.andengine.entity.sprite.AnimatedSprite;
import org.anddev.andengine.opengl.texture.Texture;
import org.anddev.andengine.opengl.texture.TextureOptions;
import org.anddev.andengine.opengl.texture.region.TextureRegionFactory;
import org.anddev.andengine.opengl.texture.region.TiledTextureRegion;

public class LoadingScene extends GameScene {

    public LoadingScene(int pLayerCount, Engine baseEngine) {
        super(pLayerCount, baseEngine);
    }

    // ===========================================================
    // Fields
    // ===========================================================

    private Texture _loadingTexture;

    private TiledTextureRegion _loadingTextureRegion;

    // ===========================================================
    // Constants
    // ===========================================================

    // ===========================================================
    // Inherited Methods
    // ===========================================================

    @Override
    protected void onLoadResources() {
        TextureRegionFactory.setAssetBasePath("gfx/");

        _loadingTexture = new Texture(512, 128, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
        _loadingTextureRegion = TextureRegionFactory.createTiledFromAsset(_loadingTexture, Game.Instance, "loading.png", 0, 0, 2, 2);

        this._engine.getTextureManager().loadTexture(_loadingTexture);
    }

    @Override
    protected void onLoadScene() {
        this.setBackground(new ColorBackground(1,1,1));

        AnimatedSprite loader = new AnimatedSprite(0, 0, _loadingTextureRegion);
        loader.setPosition((Game.CAMERA_WIDTH / 2) - (loader.getWidthScaled() / 2), (Game.CAMERA_HEIGHT / 2) - (loader.getHeightScaled() / 2));
        loader.animate(300, true);

        this.getTopLayer().addEntity(loader);

        final PlayScene gameScene = new PlayScene(3,this._engine);
        gameScene.LoadResources(false);
    }

    @Override
    protected void unloadScene() {}

    @Override
    protected void onLoadComplete() {}

    // ===========================================================
    // Methods
    // ===========================================================

}

Whew! Extremely loooong winded and pretty specific to my game but this should give a decent idea as to how this works :)

Any questions, comments or feedback is more than welcome. I'm also putting this up in the wiki.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License