The GameScreen Class

Once the game transitions to the GameScreen class, the player can immediately start playing without having to state that he or she is ready. The only states you have to care about for are these:

  • The running state, where you render the background, the world, and the UI elements, as shown in Figure 12–4.
  • The paused state, where you render the background, the world, and the paused menu, as shown in Figure 12–4.
  • The game-over state, where you render pretty much the same thing.

Follow the same method 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 you handle the user input to move the ship. You want your player to be able to control the ship with either on-screen buttons or the accelerometer. You can read the Settings.touchEnabled field to figure out what the user wants in regard to this. Depending on which input method is active, decide on whether to render the on-screen buttons or not, and pass the proper accelerometer values to the World.update() method to move the ship.

With the on-screen buttons, you don't need to use the accelerometer values; 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, you might arrive at a value of −5 for left movement and 5 for right movement via the on-screen buttons.

The last interesting for this class is the way you combine the rendering of the 3D game world and the 2D UI elements. 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, you have a couple of constants for 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 business as usual. You 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 you 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 you don't have to update the scoreString each time in order to reduce GC activity. Finally, there is an FPSCounter, so that later on you can figure out how well the 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.playSound(Assets.explosionSound);
            }
        };
        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, set up all the members, as you are now accustomed to doing. The WorldListener is responsible for playing the correct sound in the case of an event in the world. The rest is the same as for Super Jumper, though adapted slightly for the somewhat different UI elements.

    @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();
        int len = events.size();
        for (int i = 0; i < len; i++) {
            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, play the click sound. Nothing new here.

    private void updateRunning(float deltaTime) {
        List<TouchEvent> events = game.getInput().getTouchEvents();
        int len = events.size();
        for (int i = 0; i < len; i++) {
            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;
        }
        if (world.isGameOver()) {
            state = GAME_OVER;
        }
    }

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 take a look at the world updating mechanism. As you can see, you delegate the acceleration value calculation to a method called calculateInputAcceleration(). Once the world is updated, check whether any of the three states (lives, waves, or score) have changed and update the scoreString accordingly. Finally, check whether the game is over, in which case enter the GameOver state.

    private float calculateInputAcceleration() {
        float accelX = 0;
        if (Settings.touchEnabled) {
            for (int i = 0; i < 2; i++) {
                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;
                    }
                }
            }
        } else {
            accelX = game.getInput().getAccelY();
        }
        return accelX;
    }

The calculateInputAcceleration() is where you actually interpret the user input. If touch is enabled, 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, simply return its current value on the y-axis (remember, you are in landscape mode).

    private void updateGameOver() {
        List<TouchEvent> events = game.getInput().getTouchEvents();
        int len = events.size();
        for (int i = 0; i < len; i++) {
            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 simply checks for a touch event, in which case you 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);
        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, start off by clearing the framebuffer. Also, clear the z-buffer, since you are going to render some 3D objects for which you need z-testing. Next, set up the projection matrix so that you can render your 2D background image, just as you did in the MainMenuScreen or SettingsScreen. Once that is done, tell the WorldRenderer to render the game world. Finally, 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.font.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 you stored in the Assets, as well as the Pause menu. Note that, at this point, you 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.font.drawText(batcher, scoreString, 10, 320-20);
        if(Settings.touchEnabled) {
            batcher.drawSprite(32, 32, 64, 64, Assets.leftRegion);
            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. Render the scoreString first. If touch input is enabled, render the left and right movement buttons. Finally, render the Fire button and reset any OpenGL ES states You'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, you have the pause() method, which simply puts the GameScreen into the paused state.

    @Override
    public void resume() {

    }

    @Override
    public void dispose() {

    }
}

The rest is just empty stubs so that you can fulfill the GLGame interface definition. On to your final class: the WorldRenderer!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.37.89