Hi everyone, I wanted to share with you a simple architecture to dynamically load and unload scenes at Runtime.
Let me know if it has been useful to you so that I can understand what topics you are interested in and if you have ideas on how to improve the features shown
I designed the classes using Unity engine as an example.
- Unity - Scripting API: SceneManager
- Unity - Scripting API: Scene
- Unity - Scripting API: AsyncOperation
The Scene class contains the name of the scene and a list of appStates that compose it.
public class Scene {
public String name;
public List<Class<? extends AppState>> systemPrefabs;
}
The SceneManager class is used to load and unload scenes in synchronous or asynchronous mode.
The methods:
- public AsyncOperation unloadSceneAsync(Scene newScene) {}
- public AsyncOperation loadSceneAsync(Scene newScene) {}
return an object of type AsyncOperation that you can use to check if the asynchronous operation is done or its progress. Inside it contains a CompletableFuture to which you can chain an action that is invoked upon operation completion.
The GameAppState class is used to manage the states of your application. You can customize it as needed, adding a background image if you want during asynchronous operations.
public class GameAppState extends SimpleAppState implements ActionListener {
public enum GameState { PREGAME, LOADING, RUNNING, PAUSED }
private GameState _currGameState = GameState.PREGAME;
private SceneManager sceneManager;
Scene[] scenes = new Scene[] { Boot.scene_01, Boot.scene_02 };
int sceneIndex = 0;
Scene currScene = scenes[sceneIndex];
AsyncOperation asyncOperation;
@Override
public void simpleInit() {
// TODO Auto-generated method stub
sceneManager = stateManager.getState(SceneManager.class);
}
@Override
public void update(float tpf) {
//To change body of generated methods, choose Tools | Templates.
if (this._currGameState == GameState.LOADING) {
// do something...
if (asyncOperation.isDone()) {
System.out.println("Press the space bar to continue.";
}
//Output the current progress
float progress = ((float) asyncOperation.getProgress() / 100);
System.out.println("Loading progress: " + progress + "%");
// bitmap.setText(...);
}
}
private void onLoadSceneComplete(boolean sceneLoaded) {
if (!sceneLoaded) {
System.out.println("An error occurred while loading scene: " + currScene.name);
}
this._currGameState = GameState.RUNNING;
System.out.println("loadLevel completed");
}
private void loadSceneAsync() {
asyncOperation = sceneManager.loadSceneAsync(currScene);
asyncOperation.onCompleted(b -> onLoadSceneComplete((boolean) b));
System.out.println("loading scene: " + currScene.name);
}
private void onUnloadSceneComplete(boolean sceneUnloaded) {
if (!sceneUnloaded) {
System.out.println("An error occurred while unloading scene: " + currScene.name);
}
this._currGameState = GameState.PREGAME;
System.out.println("unloadLevel completed");
}
private void unloadSceneAsync() {
asyncOperation = sceneManager.unloadSceneAsync(currScene);
asyncOperation.onCompleted(b -> onUnloadSceneComplete((boolean) b));
System.out.println("unloading scene: " + currScene.name);
}
@Override
public void onAction(String action, boolean isPressed, float tpf) {
//To change body of generated methods, choose Tools | Templates.
if (action.equals(KeyMapping.START_GAME) && isPressed) {
if (this._currGameState == GameState.PREGAME)
startGame();
} else if (action.equals(KeyMapping.RESTART_GAME) && isPressed) {
if (this._currGameState == GameState.RUNNING)
stopGame();
}
}
public void stopGame() {
this._currGameState = GameState.LOADING;
unloadSceneAsync();
}
public void startGame() {
this._currGameState = GameState.LOADING;
loadSceneAsync();
}
}
The Boot class contains the definition of the elements that make up the various scenes of the game. For convenience I used a java class but it could also be a json.
public class Boot {
public static final Scene scene_01 = new Scene();
public static final Scene scene_02 = new Scene();
static {
scene_01.name = "Scene 1";
scene_01.systemPrefabs = Arrays.asList(
PostProcessingAppState.class,
LevelManager.class,
ParticleManager.class,
PlayerManager.class,
PlayerMovement.class,
PlayerShooting.class,
InventoryAppState.class
);
scene_02.name = "Scene 2";
scene_02.systemPrefabs = Arrays.asList(...);
}
}
Limitations of Future
- It cannot be manually completed
- You cannot perform further action on a Futureâs result without blocking
- Multiple Futures cannot be chained together
- You can not combine multiple Futures together
- No Exception Handling
CompletableFuture implements Future and CompletionStage interfaces and provides a huge set of convenience methods for creating, chaining and combining multiple Futures. It also has a very comprehensive exception handling support.
To find out more about how CompletableFuture works, here is an interesting explanation.