Add a scene manager to easily switch from one game play to another. A gameplay can be the Title screen, the menu screen where the player select to start a new game, the Map for this famous RPG game, the Inventory screen where the player manage all its items.
It will also be the main play screen where everything happen !
The following diagram propose an implementation layering on a Scene
interface, an AbstractScene
implementation to
provide
internal mechanism, and a SceneManager
.
The scene interface describe the scene lifecycle and all its methods:
initialize(Game)
is the first called by the Game implementation, juste after configuration has been loaded (
see SceneManager#initialize(Game)
for details.prepare(Game)
is called to initialize specific services that won’t be already initialized by the AbstractScene
implementation,create(Game)
will create/instantiate all required GameObject
for the scene description,Then the lifecycle operation:
input(Game)
here is where the Scene will manage all the device input: mouse and keyboard,update(Game, double)
this is where specific update mechanics for a {@link Scene} must be implemented,draw(Game, Renderer)
specific drawing operation (after the GameObject rendering, then the specific effects for this
scene can be rendered,And finally the ending operation :
dispose()
release all the loaded / instantiated resources objects.The SceneManager#initialize(Game)
is loading from the configuration file the list of available scene implementation
and the default activated one:
app.scene.list
contains the comma separated list of Scene class implementation with their scene id separated by
a ‘:’ :app.scene.list=demo:com.merckgroup.demo.scenes.MyOwnSceneClass, title:com.merckgroup.demo.scenes.MyTitleSceneClass" and the list of instances
is store in the availableScenes list.
app.scene.default
the default scene id to be activated at start:app.scene.default=demo
A new scene from the configuration scene is added to the list a Scene class implementation.
At activation time, the scene is instantiated and stored in a scenes instance list. At anytime, the current scene can be retrieved.
The default mechanism to retrieve dependencies to other services to be used by any Scene implementation
are set into the AsbtractScene
:
abstract class AbstractScene implements Scene {
protected Configuration config;
protected Renderer renderer;
protected EntityManager entityMgr;
protected PhysicEngine physicEngine;
protected InputHandler inputHandler;
//...
public void initialize(Game g) {
config = g.getConfiguration();
renderer = g.getRenderer();
entityMgr = g.getEntityManager();
physicEngine = g.getPhysicEngine();
inputHandler = g.getInputHandler();
prepare(g);
}
//...
}
The default current scene camera is also delegated to the AbstractScene:
abstract class AbstractScene implements Scene {
//...
protected Camera camera;
//...
public Camera getCamera() {
return camera;
}
public void setCamera(Camera camera) {
this.camera = camera;
this.renderer.setCurrentCamera(camera);
}
}
And finally, the instantiation of any Scene is partially delegated to the AbstractScene class:
abstract class AbstractScene implements Scene {
protected Game game;
protected String name;
//...
public AbstractScene(Game g, String name) {
logger.log(Level.INFO, "Instantiate scene {}", name);
this.game = g;
this.name = name;
}
//...
}
The DemoScene
is now our previous App game mechanism to be slipped into a new Scene interface.
The main things are the create()
method where GameObject are created, and the input
method where the GameObject
“player” interaction are managed with the device input:
class DemoScene extends AbstractScene {
//...
@Override
public void create(Game g) {
// Create the main player entity.
var player = (GameObject) new GameObject("player")
.setType(Rectangle2D.class)
.setFillColor(Color.RED)
.setBorderColor(new Color(0.3f, 0.0f, 0.0f))
.setSize(16.0, 16.0)
.setPosition((screenWidth - 32) * 0.5, (screenHeight - 32) * 0.5)
.setSpeed(0.0, 0.0)
.setAcceleration(0.0, 0.0)
.setMass(100.0)
.setDebug(1)
.setMaterial(Material.STEEL)
.setLayer(1)
.setAttribute(MOVE_STEP_SPEED, 50.0)
.setAttribute(PLAYER_SCORE, (int) 0)
.setAttribute(PLAYER_LIVE, (int) 5);
entityMgr.add(player);
//...
}
//...
}
And the input management are transferred:
class DemoScene extends AbstractScene {
//...
@Override
public void input(Game g) {
EntityManager emgr = g.getEntityManager();
boolean move = false;
GameObject player = (GameObject) emgr.get("player");
double moveStep = (double) player.getAttribute(MOVE_STEP_SPEED, 200.0);
double jumpFactor = moveStep * 5.0;
//...
if (inputHandler.getKey(KeyEvent.VK_UP)) {
player.addForce(new Point2D.Double(0.0, -jumpFactor));
move = true;
}
if (inputHandler.getKey(KeyEvent.VK_DOWN)) {
player.addForce(new Point2D.Double(0.0, moveStep));
move = true;
}
if (inputHandler.getKey(KeyEvent.VK_LEFT)) {
player.addForce(new Point2D.Double(-moveStep, 0.0));
move = true;
}
if (inputHandler.getKey(KeyEvent.VK_RIGHT)) {
player.addForce(new Point2D.Double(moveStep, 0.0));
move = true;
}
//...
}
//...
}
And we finally need to adapt App class.
The new Service implementation SceneManager
must be initialized:
public class App implements Game {
//...
@Override
public int initialize(String[] args) {
//...
// add the SceneManager (scene list loaded at initialization)
sceneMgr = new SceneManager(this);
logger.log(Level.INFO, "initialization done.");
return initStatus;
}
//...
}
And we need to create the scene:
public class App implements Game {
//...
@Override
public void create() {
logger.log(Level.INFO, "- create scene content for {0}", getAppName());
sceneMgr.getCurrent().create(this);
}
}
Then delegates the input management for the active scene:
public class App implements Game {
//...
@Override
public void input(Game g) {
logger.log(Level.INFO, "- Loop {0}:", updateTestCounter);
logger.log(Level.INFO, " - handle input");
sceneMgr.getCurrent().input(this);
}
}
Also update operation for object and scene :
public class App implements Game {
//...
@Override
public void update(Game g, double elapsed) {
logger.log(Level.INFO, " - update thing {0}", elapsed);
updateTestCounter += 1;
physicEngine.update(elapsed);
if (Optional.ofNullable(sceneMgr.getCurrent().getCamera()).isPresent()) {
sceneMgr.getCurrent().getCamera().update(elapsed);
}
sceneMgr.getCurrent().update(this, elapsed);
}
}
And if required, offers the opportunity for the Scene implementation to create its own rendering operation if required.
public class App implements Game {
//...
@Override
public void render(Game g, int fps) {
logger.log(Level.INFO, " - render thing at {0} FPS", fps);
renderer.draw();
sceneMgr.getCurrent().draw(this, renderer);
window.drawToWindow(renderer.getImageBuffer());
}
}
Here we are !