Android Game Tying Everything Together

Our little game development framework is nearly complete. All we need to do is tie together the loose ends by implementating the Game interface we designed in Chapter 3, using the classes we created in the previous sections of this chapter. Here's a list of responsibilities:

■ Perform window management. In our context, that 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 get dimmed.

■ Instantiate and hand out references to Graphics, Audio, FilelO, 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. All we want to do is implement the Game.getStartScreen() method later on to start off our game, like this:

public class MrNom extends AndroidGame { ^Override public Screen getStartScreen() { return new MainMenu(this);

I hope you can see why it pays off to design a nice little framework before diving headfirst into programming the actual game. We can reuse this framework for all future games that are not to graphically intensive. So let's discuss Listing 5-14, which shows the AndroidGame class.

Listing 5-14. 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.FilelO; 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 off by letting AndroidGame extend the Activity class and implement the Game interface. Next we define a couple of members that should be familiar. The first member is the AndroidFastRenderView, which we'll draw to, and which will manage our main loop thread for us. The Graphics, Audio, Input, and FileIO members will be set to instances of AndroidGraphics, AndroidAudio, AndroidInput, and AndroidFileIO—no big surprise there. The next member holds the currently active Screen. Finally there's a member that holds a WakeLock, which we'll 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 off by calling the base class's onCreate() method, as it is required. Next we make the Activity full-screen, as we did in a couple of tests in the previous chapter already. In the next few lines we set up our artificial framebuffer. Depending on the orientation of the activity, we either want to use a 320! 480 framebuffer (portrait mode) or a 480! 320 framebuffer (landscape mode). To determine what screen orientation the Activity uses, we fetch the orientation member from a class called Configuration, which we get 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 AndroidGraphics instances a little later.

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

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

Next we instantiate the AndroidFastRenderView, AndroidGraphics, AndroidAudio, Androidlnput, and AndroidFilelO with the necessary constructor arguments. Finally we call the getStartScreen() method, which our actual game will implement, and set the AndroidFastRenderView as the content view of the Activity. All these helper classes we just instantiated will do some more work in the background, of course. The Androidlnput class will tell the touch handler it selected to hook up with the AndroidFastRenderView, for example.

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

Next up is the onResume() method of the Activity class, which we override. As usual, the first thing we do is call the superclass method because we are good citizens in Android land. Next we acquire the WakeLock and make sure the current Screen gets informed of the fact that the game (and thereby the activity) has just been resumed. Finally we tell the AndroidFastRenderView to resume the rendering thread, which will also kick off our game's main loop, in which 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();

The onPause() method first calls the superclass method again. Next it releases the WakeLock and makes sure that the rendering thread is terminated. If we didn't terminate the thread before calling the current Screen's onPause(), we could run into concurrency issues since the UI thread and the main loop thread would 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 of that event so it can do any cleanup work necessary.

@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 should need no explanation. We simply return the respective instances to the caller. The caller will always be one of our Screen implementations of our game later on.

@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;

The setScreen() method we inherit from the Game interface looks simple at first glance. We start off with some old-school null-checking, as we can't allow a null Screen. Next we tell the current Screen to pause and dispose of itself so 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.

Say we have a main menu Screen where we check if the Play button is pressed in the update() method. If that is the case, we'll want to transition to the next Screen, and we can do so 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 get back 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 would be called on the MainMenu class, but the present() method would be called on the new current Screen. This would be bad, 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. And that's why we call these two methods in the AndroidGame.setScreen() method on the new Screen. All is taken care of for us by the mighty AndroidGame class.

public Screen getCurrentScreen() { return screen;

The last method is called getCurrentScreen(). It simply returns the currently active Screen.

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

0 0

Post a comment