-
Notifications
You must be signed in to change notification settings - Fork 0
heads_up_display
The final piece our game needs is a User Interface (UI) to display things like score, a "game over" message, and a restart button.
Create a new class MainSceneAppState
in package e.g.dodgethecreeps.screen
.
package e.g.dodgethecreeps.screen;
public class MainSceneAppState extends AbstractScreen {
}
To reuse the code, open the AbstractScreen
(parent) class. We configure the root container for the user interface.
package e.g.dodgethecreeps.screen;
...
public abstract class AbstractScreen extends BaseAppState {
/** UI root container. */
protected Container rootContainer;
/** Label for messages. */
protected Label message;
@Override
protected void initialize(Application app) {
AppSettings settings = ((Dodgethecreeps) app).getSettings();
ControlLayout layout = new ControlLayout(ControlLayout.onCreateRootPane(new Vector3f(settings.getWidth(), settings.getHeight(), 0),
new Vector3f(settings.getWidth(), settings.getHeight(), 0)));
rootContainer = new Container(new ElementId("null"));
rootContainer.setPreferredSize(new Vector3f(layout.getRootPane().getResolution().clone()));
rootContainer.setLayout(layout);
message = layout.addChild(new Label(""), ControlLayout.Alignment.Center);
message.setTextHAlignment(HAlignment.Center);
message.setTextVAlignment(VAlignment.Center);
message.setFont(GuiGlobals.getInstance().loadFont("Interface/Fonts/Xolonium.fnt"));
message.setColor(new ColorRGBA(1.0F, 1.0F, 1.0F, 1.0F));
message.setPreferredSize(new Vector3f(rootContainer.getPreferredSize().x, 200, 0));
layout.setAttribute(ControlLayout.FONT_SIZE, message, 55.0F);
}
@Override
protected void cleanup(Application app) {
}
@Override
protected void onEnable() {
Dodgethecreeps app = (Dodgethecreeps) getApplication();
app.getGuiNode().attachChild(rootContainer);
AppSettings settings = app.getSettings();
rootContainer.setPreferredSize(new Vector3f(settings.getWidth(), settings.getHeight(), 0));
rootContainer.setLocalTranslation(0, settings.getHeight(), 0);
}
@Override
protected void onDisable() {
rootContainer.removeFromParent();
}
}
In state MainSceneAppState
we must show a button and the game title. Create a variable of type Button
and initialize it by overriding the initialize(Application app)
method.
Add a 'click' event to the button, the event is responsible for changing state.
package e.g.dodgethecreeps.screen;
...
public final class MainSceneAppState extends AbstractScreen {
private Button startButton;
public MainSceneAppState() { }
@Override
protected void initialize(Application app) {
super.initialize(app);
ControlLayout layout = (ControlLayout) rootContainer.getLayout();
startButton = layout.addChild(new Button("Start", new ElementId("StartButton")), ControlLayout.Alignment.CenterBottom);
startButton.setTextHAlignment(HAlignment.Center);
startButton.setTextVAlignment(VAlignment.Center);
startButton.setFont(GuiGlobals.getInstance().loadFont("Interface/Fonts/Xolonium.fnt"));
startButton.setPreferredSize(new Vector3f(200, 100, 0));
startButton.addClickCommands((source) -> {
setEnabled(false);
getApplication().getStateManager().getState(GameSceneAppState.class).setEnabled(true);
});
layout.setAttribute(ControlLayout.POSITION, startButton, new Vector3f(0, 100, 0));
layout.setAttribute(ControlLayout.FONT_SIZE, startButton, 50.0F);
message.setText("Dodge the\nCreeps!");
}
}
Open class GameSceneAppState
, create the following variables:
...
private Label scoreLabel;
...
initialize label scoreLabel
in overridden method initialize(Application app)
:
...
ControlLayout layout = (ControlLayout) rootContainer.getLayout();
//----------------------------------------------------------------------
// HUD
//----------------------------------------------------------------------
scoreLabel = layout.addChild(new Label("0"), true, ControlLayout.Alignment.CenterTop);
scoreLabel.setTextHAlignment(HAlignment.Center);
scoreLabel.setTextVAlignment(VAlignment.Center);
scoreLabel.setFont(GuiGlobals.getInstance().loadFont("Interface/Fonts/Xolonium.fnt"));
scoreLabel.setColor(new ColorRGBA(1.0F, 1.0F, 1.0F, 1.0F));
scoreLabel.setPreferredSize(new Vector3f(300, 45, 0));
layout.setAttribute(ControlLayout.POSITION, scoreLabel, new Vector3f(0, 10, 0));
layout.setAttribute(ControlLayout.FONT_SIZE, scoreLabel, 45.0F);
...
We now want to display a message temporarily, such as "Get Ready", so we add the following code.
In method onEnable()
:
...
message.setText("Get Ready!");
scoreLabel.setText("0");
...
We also need to process what happens when the player loses. The following code will display "Game Over" and then return to the home screen, after a short pause.
In method gameOver()
:
...
player = null;
mobTimer.stop();
scoreTimer.stop();
message.setText("Game Over");
messageTimer.start();
...
Add the code below to HUD to update the score:
private final TimerTask _on_ScoreTimer_timeout = (t) -> {
...
scoreLabel.setText(String.valueOf(score));
...
};
In task _on_StartTimer_timeout(t)
, clean the label.
...
private final TimerTask _on_StartTimer_timeout = (t) -> {
...
message.setText("");
...
};
...
In task _on_MessageTimer_timeout(t)
of timer messageTimer
add:
...
private final TimerTask _on_MessageTimer_timeout = (t) -> {
setEnabled(false);
t.stop();
};
....
It's time to prepare for the change between states.
In method onDisable()
:
@Override
protected void onDisable() {
...
getState(MainSceneAppState.class).setEnabled(true);
}
In the constructor of this class, disable.
...
public GameSceneAppState() {
setEnabled(false);
}
...
In the main class Dodgethecreeps
, register in the new state.
@Override
public void simpleInitApp() {
...
// scene states; where the game is managed
MainSceneAppState sceneAppState = new MainSceneAppState();
stateManager.attach(sceneAppState);
...
}
At the moment your code should look similar to the following:
package e.g.dodgethecreeps.screen;
...
public class GameSceneAppState extends AbstractScreen {
private Node rootNode;
private Dyn4jAppState<PhysicsBody2D> dyn4jAppState;
private TimerAppState timerAppState;
private Player player;
private Timer mobTimer, // Enemy timer; Manage the spawn time of enemies.
scoreTimer, // Score timer; Manage time to earn a score.
startTimer, // Manage some preparation time before starting the game.
messageTimer; // Timer to display a "Game Over" message
private MobSpawnLocation mobSpawnLocation;
private Label scoreLabel;
private int score = 0;
public GameSceneAppState() {
setEnabled(false);
}
@Override
protected void initialize(Application app) {
super.initialize(app);
Dodgethecreeps application = (Dodgethecreeps) app;
ControlLayout layout = (ControlLayout) rootContainer.getLayout();
//----------------------------------------------------------------------
// HUD
//----------------------------------------------------------------------
scoreLabel = layout.addChild(new Label("0"), true, ControlLayout.Alignment.CenterTop);
scoreLabel.setTextHAlignment(HAlignment.Center);
scoreLabel.setTextVAlignment(VAlignment.Center);
scoreLabel.setFont(GuiGlobals.getInstance().loadFont("Interface/Fonts/Xolonium.fnt"));
scoreLabel.setColor(new ColorRGBA(1.0F, 1.0F, 1.0F, 1.0F));
scoreLabel.setPreferredSize(new Vector3f(300, 45, 0));
layout.setAttribute(ControlLayout.POSITION, scoreLabel, new Vector3f(0, 10, 0));
layout.setAttribute(ControlLayout.FONT_SIZE, scoreLabel, 45.0F);
rootNode = new Node("MyRootNode");
application.getRootNode().attachChild(rootNode);
dyn4jAppState = getState(Dyn4jAppState.class);
timerAppState = getState(TimerAppState.class);
mobTimer = timerAppState.attachTimer("MobTimer", new Timer(0.5F).attachTask(_on_MobTimer_timeout), 0.60F);
scoreTimer = timerAppState.attachTimer("ScoreTimer", new Timer(0.75F).attachTask(_on_ScoreTimer_timeout), 0.60F);
startTimer = timerAppState.attachTimer("StartTimer", new Timer(1.05F).attachTask(_on_StartTimer_timeout), 0.60F);
messageTimer = timerAppState.attachTimer("MessageTimer", new Timer(1.0F).attachTask(_on_MessageTimer_timeout), 0.60F);
Camera cam = application.getCamera();
mobSpawnLocation = new MobSpawnLocation();
mobSpawnLocation.add(new Vector2(cam.getFrustumLeft(), cam.getFrustumTop()),
new Vector2(cam.getFrustumRight(), cam.getFrustumTop()));
mobSpawnLocation.add(new Vector2(cam.getFrustumRight(), cam.getFrustumTop()),
new Vector2(cam.getFrustumRight(), cam.getFrustumBottom()));
mobSpawnLocation.add(new Vector2(cam.getFrustumRight(), cam.getFrustumBottom()),
new Vector2(cam.getFrustumLeft(), cam.getFrustumBottom()));
mobSpawnLocation.add(new Vector2(cam.getFrustumLeft(), cam.getFrustumBottom()),
new Vector2(cam.getFrustumLeft(), cam.getFrustumTop()));
}
private final TimerTask _on_MobTimer_timeout = (t) -> {
Mob mob = Mob.getNewInstanceMob((Dodgethecreeps) getApplication());
Vector2 position = mobSpawnLocation.getRandomPath();
double direction = position.getDirection() + Math.PI;
direction += ThreadLocalRandom.current().nextDouble(-Math.PI / 4.0, Math.PI / 4.0);
mob.getTransform().rotate(direction);
mob.getTransform().setTranslation(position);
Vector2 velocity = new Vector2(ThreadLocalRandom.current().nextDouble(1.50, 2.50), 0.0);
mob.setLinearVelocity(velocity.rotate(direction));
dyn4jAppState.getPhysicsSpace().addBody(mob);
rootNode.attachChild(mob.getJmeObject());
t.reset();
};
private final TimerTask _on_ScoreTimer_timeout = (t) -> {
score += 1;
scoreLabel.setText(String.valueOf(score));
t.reset();
};
private final TimerTask _on_StartTimer_timeout = (t) -> {
player.setEnabled(true);
message.setText("");
mobTimer.start();
scoreTimer.start();
t.stop();
};
private final TimerTask _on_MessageTimer_timeout = (t) -> {
setEnabled(false);
t.stop();
};
public void gameOver() {
player = null;
mobTimer.stop();
scoreTimer.stop();
message.setText("Game Over");
messageTimer.start();
}
@Override
protected void onEnable() {
super.onEnable();
player = Player.getNewInstancePlayer((Dodgethecreeps) getApplication());
player.translate(0, -1.5);
dyn4jAppState.getPhysicsSpace().addBody(player);
rootNode.attachChild(player.getJmeObject());
score = 0;
player.setEnabled(false);
startTimer.start();
message.setText("Get Ready!");
scoreLabel.setText("0");
}
@Override
protected void onDisable() {
super.onDisable();
World<PhysicsBody2D> world = dyn4jAppState.getPhysicsSpace();
if (world != null) {
world.removeAllBodies();
}
rootNode.detachAllChildren();
getState(MainSceneAppState.class).setEnabled(true);
}
}
The game is almost over at this point. In the next and final part, we'll polish it up a bit with looped music.