The Game Screen Class

Once the game transitions to the GameScreen class the player can immediately start playing without having to state that she is ready. The only states we have to care for are tthese:

■ The running state where we render the background, the world, and the UI elements as in Figure 12-4.

■ The paused state where we render the background, the world, and the paused menu, again as in Figure 12-4.

■ The game-over state, where we render pretty much the same thing.

We'll again follow the same method we used in Super Jumper and have different update() and present() methods for each of the three states.

The only interesting part of this class is how we handle the user input to move the ship. We want our player to be able to control the ship with either on-screen buttons or the accelerometer. We can read the Settings.touchEnabled field to figure out what the user wants in that regard. Depending on which input method is active, we either render the on-screen buttons or not and also have to pass the proper accelerometer values to the World.update() method for the ship to move.

With the on-screen buttons we of course don't use the accelerometer values but instead just pass a constant artificial acceleration value to the World.update() method. It has to be in the range -10 (left) to 10 (right). After a little experimentation I arrived at a value of -5 for left movement and 5 for right movement via the on-screen buttons.

The last interesting bit of this class is the way we combine rendering the 3D game world and the 2D UI elements. Let's take a look at the code of the GameScreen class in Listing 12-11.

Listing 12-11. GameScreen.java, the Game Screen package com.badlogic.androidgames.droidinvaders;

import java.util.List;

import javax.microedition.khronos.opengles.GL10;

import com.badlogic.androidgames.droidinvaders.World.WorldListener; import com.badlogic.androidgames.framework.Game; import com.badlogic.androidgames.framework.Input.TouchEvent; import com.badlogic.androidgames.framework.gl.Camera2D; import com.badlogic.androidgames.framework.gl.FPSCounter; import com.badlogic.androidgames.framework.gl.SpriteBatcher; import com.badlogic.androidgames.framework.impl.GLScreen; import com.badlogic.androidgames.framework.math.OverlapTester; import com.badlogic.androidgames.framework.math.Rectangle; import com.badlogic.androidgames.framework.math.Vector2;

public class GameScreen extends GLScreen { static final int GAME_RUNNING = 0; static final int GAME_PAUSED = 1; static final int GAME_OVER = 2;

As usual we have a couple of constants encoding the screen's current state.

int state; Camera2D guiCam; Vector2 touchPoint; SpriteBatcher batcher; World world;

WorldListener worldListener; WorldRenderer renderer; Rectangle pauseBounds; Rectangle resumeBounds; Rectangle quitBounds; Rectangle leftBounds; Rectangle rightBounds; Rectangle shotBounds; int lastScore; int lastLives; int lastWaves; String scoreString; FPSCounter fpsCounter;

The members of the GameScreen are also business as usual. We have a member keeping track of the state, a camera, a vector for the touch point, a SpriteBatcher for rendering the 2D UI elements, the World instance along with the WorldListener, the WorldRenderer (which we are going to write in a minute), and a couple of Rectangles for checking whether a UI element was touched. In addition, three integers keep track of the last number of lives, waves, and score, so that we don't have to update the scoreString each time to reduce GC activity. Finally, there is an FPSCounter so we can later figure out how well our game performs.

public GameScreen(Game game) { super(game);

state = GAME_RUNNING;

guiCam = new Camera2D(glGraphics, 480, 320); touchPoint = new Vector2(); batcher = new SpriteBatcher(glGraphics, 100); world = new World(); worldListener = new WorldListener() { ^Override public void shot() {

Assets.playSound(Assets.shotSound);

^Override public void explosion() {

Assets.pIaySound(Assets.expIosionSound);

world.setWorldListener(worldListener); renderer = new WorldRenderer(glGraphics); pauseBounds = new Rectangle(480 - 64, 320 - 64, 64, 64); resumeBounds = new Rectangle(240 - 80, 160, 160, 32); quitBounds = new Rectangle(240 - 80, 160 - 32, 160, 32); shotBounds = new Rectangle(480 - 64, 0, 64, 64); leftBounds = new Rectangle(0, 0, 64, 64); rightBounds = new Rectangle(64, 0, 64, 64); lastScore = 0;

lastLives = world.ship.lives; lastWaves = world.waves;

scoreString = "lives:" + lastLives + " waves:" + lastWaves + " score:"

+ lastScore; fpsCounter = new FPSCounter();

In the constructor we just set up all those members as we are accustomed to doing. The WorldListener is responsible for playing the correct sound in case of an event in our world. The rest is exactly the same as in Super Jumper, just slightly adapted to the somewhat different UI elements of course.

^Override public void update(float deltaTime) { switch (state) { case GAME_PAUSED: updatePaused(); break; case GAME_RUNNING:

updateRunning(deltaTime); break; case GAME_OVER:

updateGameOver(); break;

The update() method delegates the real updating to one of the other three update methods, depending on the current state of the screen.

private void updatePaused() {

List<TouchEvent> events = game.getInput().getTouchEvents();

TouchEvent event = events.get(i); if (event.type != TouchEvent.TOUCH_UP) continue;

guiCam.touchToWorld(touchPoint.set(event.x, event.y)); if (OverlapTester.pointInRectangle(resumeBounds, touchPoint)) { Assets.playSound(Assets.clickSound); state = GAME_RUNNING;

if (OverlapTester.pointInRectangle(quitBounds, touchPoint)) { Assets.playSound(Assets.clickSound); game.setScreen(new MainMenuScreen(game));

The updatePaused() method loops through any available touch events and checks whether one of the two menu entries was pressed (Resume or Quit). In each case we play the click sound. Nothing new here.

private void updateRunning(float deltaTime) {

List<TouchEvent> events = game.getInput().getTouchEvents();

TouchEvent event = events.get(i); if (event.type != TouchEvent.TOUCH_DOWN) continue;

guiCam.touchToWorld(touchPoint.set(event.x, event.y));

if (OverlapTester.pointInRectangle(pauseBounds, touchPoint)) { Assets.playSound(Assets.clickSound); state = GAME_PAUSED;

if (OverlapTester.pointInRectangle(shotBounds, touchPoint)) { world.shot();

world.update(deltaTime, calculateInputAcceleration()); if (world.ship.lives != lastLives || world.score != lastScore || world.waves != lastWaves) { lastLives = world.ship.lives; lastScore = world.score; lastWaves = world.waves;

scoreString = "lives:" + lastLives + " waves:" + lastWaves + " score:" + lastScore;

The updateRunning() method is responsible for two things: to check whether the pause button was pressed and react accordingly, and to update the world based on the user input. The first piece of the puzzle is trivial, so let's look at the world updating mechanism. As you can see we delegate the acceleration value calculation to a method called calculateInputAcceleration(). Once the world is updated we check whether any of the three states (lives, waves, or score) have changed and update the scoreString accordingly. Finally we check whether the game is over, in which case we enter the GameOver state.

private float calculateInputAcceleration() { float accelX = 0; if (Settings.touchEnabled) {

if (game.getInput().isTouchDown(i)) {

guiCam.touchToWorld(touchPoint.set(game.getInput()

.getTouchX(i), game.getInput().getTouchY(i))); if (OverlapTester.pointInRectangle(leftBounds, touchPoint)) { accelX = -Ship.SHIP_VELOCITY / 5;

if (OverlapTester.pointInRectangle(rightBounds, touchPoint)) { accelX = Ship.SHIP_VELOCITY / 5;

accelX = game.getInput().getAccelY();

return accelX;

The calculateInputAcceleration() is where we actually interpret the user input. If touch is enabled we check whether the left or right on-screen movement buttons were pressed and set the acceleration value accordingly to either -5 (left) or 5. If the accelerometer is used we simply return its current value on the y-axis (remember, we are in landscape mode).

private void updateGameOver() {

List<TouchEvent> events = game.getInput().getTouchEvents();

TouchEvent event = events.get(i); if (event.type == TouchEvent.TOUCH_UP) { Assets.playSound(Assets.clickSound); game.setScreen(new MainMenuScreen(game));

The updateGameOver() method is again trivial and just checks for a touch event, in which case we transition to the MainMenuScreen.

^Override public void present(float deltaTime) { GL10 gl = glGraphics.getGL();

gl.glClear(GL10. GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

gl.glEnable(GL10. GL_TEXTURE_2D); batcher.beginBatch(Assets.background);

batcher.drawSprite(240, 160, 480, 320, Assets.backgroundRegion);

batcher.endBatch();

gl.glDisable(GL10.GL_TEXTURE_2D);

guiCam.setViewportAndMatrices();

gl.glEnable(GL10. GL_TEXTURE_2D); batcher.beginBatch(Assets.background);

batcher.drawSprite(240, 160, 480, 320, Assets.backgroundRegion);

batcher.endBatch();

gl.glDisable(GL10.GL_TEXTURE_2D);

renderer.render(world, deltaTime);

switch (state) { case GAME_RUNNING: presentRunning(); break; case GAME_PAUSED: presentPaused(); break; case GAME_OVER:

presentGameOver();

fpsCounter.logFrame();

The present() method is actually pretty simple as well. As always we start off by clearing the framebuffer. We also clear the z-buffer since we are going to render some 3D objects for which we need z-testing. Next we set up the projection matrix so that we can render our 2D background image, just as we did in the MainMenuScreen or SettingsScreen. Once that is done, we tell the WorldRenderer to render our game world. Finally we delegate the rendering of the UI elements depending on the current state. Note that the WorldRenderer.render() method is responsible for setting up all things needed to render the 3D world!

private void presentPaused() { GL10 gl = glGraphics.getGL(); guiCam.setViewportAndMatrices(); gl.glEnable(GL10.GL_BLEND);

gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); gl.glEnable(GL10. GL_TEXTURE_2D);

batcher.beginBatch(Assets.items);

Assets./ont.drawText(batcher, scoreString, 10, 320-20); batcher.drawSprite(240, 160, 160, 64, Assets.pauseRegion); batcher.endBatch();

gl.glDisable(GL10.GL_TEXTURE_2D); gl.glDisable(GL10.GL_BLEND);

The presentPaused() method just renders the scoreString via the Font instance we store in the Assets as well as the Pause menu. Note that at this point we have already rendered the background image as well as the 3D world. All the UI elements will thus overlay the 3D world.

private void presentRunning() { GL10 gl = glGraphics.getGL();

guiCam.setViewportAndMatrices(); gl.glEnable(GL10.GL_BLEND);

gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); gl.glEnable(GL10. GL_TEXTURE_2D);

batcher.beginBatch(Assets.items);

batcher.drawSprite(480- 32, 320 - 32, 64, 64, Assets.pauseButtonRegion); Assets./ont.drawText(batcher, scoreString, 10, 320-20); if(Settings.touchEnabled) {

batcher.drawSprite(32, 32, 64, 64, Assets.IeftRegion); batcher.drawSprite(96, 32, 64, 64, Assets.rightRegion);

batcher.drawSprite(480 - 40, 32, 64, 64, Assets.fireRegion); batcher.endBatch();

gl.glDisable(GL10. GL_TEXTURE_2D); gl.glDisable(GL10.GL_BLEND);

The presentRunning() method is also pretty straightforward. We render the scoreString first. If touch input is enabled we then render the left and right movement buttons. Finally we render the Fire button and reset any OpenGL ES states we've changed (texturing and blending).

private void presentGameOver() { GL10 gl = glGraphics.getGL(); guiCam.setViewportAndMatrices(); gl.glEnable(GL10.GL_BLEND);

gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); gl.glEnable(GL10. GL_TEXTURE_2D);

batcher.beginBatch(Assets.items);

batcher.drawSprite(240, 160, 128, 64, Assets.gameOverRegion); Assets.font.drawText(batcher, scoreString, 10, 320-20); batcher.endBatch();

gl.glDisable(GL10.GL_TEXTURE_2D); gl.glDisable(GL10.GL_BLEND);

The presentGameOver() method is more of the same. Just some string and UI rendering. ^Override public void pause() { state = GAME_PAUSED;

Finally we have the pause() method, which simply puts the GameScreen into the paused state.

^Override public void resume() { }

^Override public void dispose() {

The rest is again just some empty stubs so that we fulfill the GLGame interface definition. On to our final class: the WorldRenderer!

0 0

Post a comment