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.
AndroidFastRenderView
, and handling the activity life cycle in a clean way.WakeLock
so that the screen does not dim.Graphics
, Audio
, FileIO
, and Input
to interested parties.Screen
s and integrate them with the activity life cycle.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.
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 Screen
s. 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.
18.225.55.198