game101

Adding Scene and Scene Manager

Goals

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 !

An hypothetical implementation of a game with scenes

Proposed implementation

The following diagram propose an implementation layering on a Scene interface, an AbstractScene implementation to provide internal mechanism, and a SceneManager.

Implementation class diagram

Scene interface

The scene interface describe the scene lifecycle and all its methods:

Then the lifecycle operation:

And finally the ending operation :

The Scene manager

The SceneManager class implementation with its dependencies

loading configuration

The SceneManager#initialize(Game) is loading from the configuration file the list of available scene implementation and the default activated one:

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=demo

Add a scene

A new scene from the configuration scene is added to the list a Scene class implementation.

Activate a scene

At activation time, the scene is instantiated and stored in a scenes instance list. At anytime, the current scene can be retrieved.

Using an abstract implementation for scene

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 Demo

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.

App class update

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;
    }
    //...
}

Scene delegation

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 !

Displaying the scene name in debug mode