AndroidGame: Tying Everything Together

Our game development framework is nearly complete. All we need to do is tie the loose ends together by implementing the Game interface we designed in Chapter 3. To do this, we will use the classes we created in the previous sections of this chapter. The following is a list of responsibilities.

  • Perform window management. In our context, this means setting up an activity and an AndroidFastRenderView, and handling the activity life cycle in a clean way.
  • Use and manage a WakeLock so that the screen does not dim.
  • Instantiate and hand out references to Graphics, Audio, FileIO, and Input to interested parties.
  • Manage Screens and integrate them with the activity life cycle.
  • Our general goal is it to have a single class called AndroidGame from which we can derive. We want to implement the Game.getStartScreen() method later on to start our game in the following way.
public class MrNom extends AndroidGame {
    @Override
    public Screen getStartScreen() {
        return new MainMenu(this);
    }
}

We hope you can see why it is beneficial to design a workable framework before diving headfirst into programming the actual game. We can reuse this framework for all future games that are not too graphically intensive. Now, let's discuss Listing 5–16, which shows the AndroidGame class.

Listing 5–16. AndroidGame.java; Tying Everything Together

package com.badlogic.androidgames.framework.impl;

import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.view.Window;
import android.view.WindowManager;

import com.badlogic.androidgames.framework.Audio;
import com.badlogic.androidgames.framework.FileIO;
import com.badlogic.androidgames.framework.Game;
import com.badlogic.androidgames.framework.Graphics;
import com.badlogic.androidgames.framework.Input;
import com.badlogic.androidgames.framework.Screen;

public abstract class AndroidGame extends Activity implements Game {
    AndroidFastRenderView renderView;
    Graphics graphics;
    Audio audio;
    Input input;
    FileIO fileIO;
    Screen screen;
    WakeLock wakeLock;

The class definition starts by letting AndroidGame extend the Activity class and implement the Game interface. Next, we define a couple of members that should already be familiar. The first member is the AndroidFastRenderView, to which we'll draw, and will manage our main loop thread for us. Of course, we set the Graphics, Audio, Input, and FileIO members to instances of AndroidGraphics, AndroidAudio, AndroidInput, and AndroidFileIO. The next member holds the currently active Screen. Finally, there's a member that holds a WakeLock that we use to keep the screen from dimming.

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        boolean isLandscape = getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
        int frameBufferWidth = isLandscape ? 480 : 320;
        int frameBufferHeight = isLandscape ? 320 : 480;
        Bitmap frameBuffer = Bitmap.createBitmap(frameBufferWidth,
                frameBufferHeight, Config.RGB_565);

        float scaleX = (float) frameBufferWidth
                / getWindowManager().getDefaultDisplay().getWidth();
        float scaleY = (float) frameBufferHeight
                / getWindowManager().getDefaultDisplay().getHeight();

        renderView = new AndroidFastRenderView(this, frameBuffer);
        graphics = new AndroidGraphics(getAssets(), frameBuffer);
        fileIO = new AndroidFileIO(getAssets());
        audio = new AndroidAudio(this);
        input = new AndroidInput(this, renderView, scaleX, scaleY);
        screen = getStartScreen();
        setContentView(renderView);

        PowerManager powerManager = (PowerManager)
getSystemService(Context.POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "GLGame");
    }

The onCreate() method, which is the familiar startup method of the Activity class, starts by calling the base class's onCreate() method, as required. Next, we make the Activity full-screen, as we did in a couple of other tests in the previous chapter. In the next few lines, we set up our artificial framebuffer. Depending on the orientation of the activity, we want to use a 320×480 framebuffer (portrait mode) or a 480×320 framebuffer (landscape mode). To determine the Activity's screen orientations, we fetch the orientation member from a class called Configuration, which we obtain via a call to getResources().getConfiguration(). Based on the value of that member, we then set the framebuffer size and instantiate a Bitmap, which we'll hand to the AndroidFastRenderView and the AndroidGraphics instances in the following chapters.

NOTE: The Bitmap instance has an RGB565 color format. This way, we don't waste memory, and our drawing is completed a little faster.

We also calculate the scaleX and scaleY values that the SingleTouchHandler and the MultiTouchHandler classes will use to transform the touch event coordinates in our fixed-coordinate system.

Next, we instantiate the AndroidFastRenderView, AndroidGraphics, AndroidAudio, AndroidInput, and AndroidFileIO with the necessary constructor arguments. Finally, we call the getStartScreen() method, which our game will implement, and set the AndroidFastRenderView as the content view of the Activity. Of course, all the previously instantiated helper classes will do some more work in the background. For example, the AndroidInput class tells the selected touch handler to communicate with the AndroidFastRenderView.

    @Override
    public void onResume() {
        super.onResume();
        wakeLock.acquire();
        screen.resume();
        renderView.resume();
    }

Next is the onResume() method of the Activity class, which we override. As usual, the first thing we do is call the superclass method. Next, we acquire the WakeLock and make sure the current Screen is informed that the game, and thereby, the activity, has been resumed. Finally, we tell the AndroidFastRenderView to resume the rendering thread, which will also kick off our game's main loop, where we tell the current Screen to update and present itself in each iteration.

    @Override
    public void onPause() {
        super.onPause();
        wakeLock.release();
        renderView.pause();
        screen.pause();

        if (isFinishing())
            screen.dispose();
    }

First, the onPause() method calls the superclass method again. Next, it releases the WakeLock and makes sure that the rendering thread is terminated. If we don't terminate the thread before calling the current Screen's onPause(), we may run into concurrency issues since the UI thread and the main loop thread will both access the Screen at the same time. Once we are sure the main loop thread is no longer alive, we tell the current Screen that it should pause itself. In case the Activity is going to be destroyed, we also inform the Screen so that it can do any necessary cleanup work.

    @Override
    public Input getInput() {
        return input;
    }

    @Override
    public FileIO getFileIO() {
        return fileIO;
    }

    @Override
    public Graphics getGraphics() {
        return graphics;
    }

    @Override
    public Audio getAudio() {
        return audio;
    }

The getInput(), getFileIO(), getGraphics(), and getAudio() methods need no explanation. We simply return the respective instances to the caller. Later, the caller will always be one of our Screen implementations of our game.

    @Override
    public void setScreen(Screen screen) {
        if (screen == null)
            throw new IllegalArgumentException("Screen must not be null");

        this.screen.pause();
        this.screen.dispose();
        screen.resume();
        screen.update(0);
        this.screen = screen;
    }

At first, the setScreen() method we inherit from the Game interface looks simple. We start with some traditional null-checking, since we can't allow a null Screen. Next, we tell the current Screen to pause and dispose of itself so that it can make room for the new Screen. The new Screen is asked to resume itself and update itself once with a delta time of zero. Finally, we set the Screen member to the new Screen.

Let's think about who will call this method and when. When we designed Mr. Nom, we identified all the transitions between various Screen instances. We'll usually call the AndroidGame.setScreen() method in the update() method of one of these Screen instances.

For example, let's assume we have a main menu Screen where we check to see if the Play button is pressed in the update() method. If that is the case, we will transition to the next Screen by calling the AndroidGame.setScreen() method from within the MainMenu.update() method with a brand-new instance of that next Screen. The MainMenu screen will regain control after the call to AndroidGame.setScreen(), and should immediately return to the caller as it is no longer the active Screen. In this case, the caller is the AndroidFastRenderView in the main loop thread. If you check the portion of the main loop responsible for updating and rendering the active Screen, you'll see that the update() method will be called on the MainMenu class, but the present() method will be called on the new current Screen. This would be problematic, as we defined the Screen interface in a way that guarantees that the resume() and update() methods will be called at least once before the Screen is asked to present itself. That's why we call these two methods in the AndroidGame.setScreen() method on the new Screen. The AndroidGame class takes care of everything.

public Screen getCurrentScreen() {
        return screen;
    }
}

The last method is the getCurrentScreen() method, which simply returns the currently active Screen.

Finally, remember that AndroidGame derives from Game, which has another method called getStartScreen(). This is the method we have to implement to get things going for our game!

Now, we've created an easy-to-use Android game development framework. All we need to do is implement our game's Screens. We can also reuse the framework for any future games, as long as they do not need immense graphics power. If that is necessary, we have to use OpenGL ES. However, to do this, we only need to replace the graphics part of our framework. All the other classes for audio, input, and file I/O can be reused.

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

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