Chapter 3. Building a Java Game from Scratch

If you don't like C and want to stay as far away from it as possible, the next two chapters will show you how simple and fun writing a Java-only game can be. We start by taking a look at the differences between Android and the Java Micro Edition (Java ME), which is the standard for mobile development. Next, we take a look at some basic gaming techniques such as sprite rendering and sound manipulation, as an appetizer to the main course—building a real game called Space Blaster. In this fun game, you must shoot or duck incoming asteroids that threaten to destroy your space ship. A shield will also help you in your quest for a high score. Let's get started.

Android Gaming vs. Java ME Gaming

If you have written many Android applications, perhaps you have noticed how different the Android Java API is from the Java SE and Java ME APIs, the standards for Java and mobile development respectively. As a matter of fact, according to Wikipedia, this is a major source of criticism about Android (http://en.wikipedia.org/wiki/Android_(operating_system)).

  • Android does not use a conventional Linux kernel. It does not have a native X Window system, nor does it support the standard GNU libraries, making reusing existing Linux applications or libraries difficult.

  • Android does not use established Java standards (Java SE and Java ME). This prevents compatibility among Java applications. It does not provide the full set of Java SE or ME classes, libraries, and APIs.

  • Android does not officially allow applications to be installed on or run from an SD card. This is a serious mistake as many advanced 3D games use big files to store graphics and sound. Storing these files in the main file system will quickly drain precious disk space.

These shortcomings are evident just by looking at the Android library within your IDE. Consider Figure 3-1, which displays the classes for the android.jar library. On the right side, you will notice that most of the packages of Java SE are included, which is good. However, some useful classes for games, such as java.awt.Polygon and java.awt.Dimension, are still missing. Although in version 1.5 of the SDK Google has added the important Java Bean property change support (see the java.beans package). The middle of the figure shows where Android and Java SE are two different animals—none of the classes in the Android package are part of the Java SE and ME standards. Finally, in the left view, you can see that Google is reusing some neat libraries from the Apache Software Foundation (Commons HTTP and HTTP client) and the W3C XML APIs (under org.xml).

The packages of the Android API (android.jar)

Figure 3-1. The packages of the Android API (android.jar)

The Android API breaks compatibility with Java SE/ME and makes it tough, but not impossible, to reuse Java code from standard Java games or applets. You'll find out how to reuse your Java code throughout this chapter.

Creating Space Blaster, Your First Java Game

It's time for the real thing. This section presents the pure Java game Space Blaster. Even though this is an Android game, some code has been reused from a Java applet.

Figure 3-2 shows the game in action. The objective of the game is to navigate a space ship through a field of meteors, shooting them up as you go. The ship has a laser weapon and a defensive shield. You can choose to shoot the meteor, dodge it by dragging the ship with your fingertips, or let the shield protect you, but whatever you choose, the power diminishes. As time passes, the power will replenish itself, and you will get points the longer you stay in the game. You can maneuver the ship with the keypad arrows or drag it across the field with your fingers. Tapping the background will fire the laser, pressing E will end the game, and pressing Q will terminate the application.

Space Blaster running on the emulator

Figure 3-2. Space Blaster running on the emulator

Let's take a look at the class hierarchy for this application.

Understanding Game Architecture

Space Blaster is a relatively simple game. It has three main classes:

  • SpaceBlaster.java: This is the main activity that bonds the game code with the Android platform. Its job is to load the game layout and process Activity events.

  • SpaceBlasterGame.java: This is where all the meat resides. This class has all the game logic, and it processes key and touch events. SpaceBlasterGame extends ArcadeGame.

  • ArcadeGame.java: This abstract class encapsulates common functionality. It also uses a standard Java TimerTask to define a game loop, which will repeat itself infinitely until the user quits the application.

These classes interact with each other and local resources as shown in Figure 3-3.

The Space Blaster architecture

Figure 3-3. The Space Blaster architecture

The main activity class loads the XML UI from the file main.xml. The XML UI defines a basic Android LinearLayout that draws all the sprites on the game. The position of a sprite is controlled using X and Y coordinates within the LinearLayout::onDraw() method. Note that SpaceBlasterGame extends ArcadeGame, which in turn extends LinearLayout. This inheritance hierarchy allows you to define your own class to use as a LinearLayout, where you can draw by simply overloading the onDraw() method (see Listing 3-1).

Example 3-1. User-Defined LinearLayout (main.xml)

<?xml version="1.0" encoding="utf-8"?>

<!— User Defined Linear Layout —>
<ch03.game.sb.SpaceBlasterGame
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/ll_absolute"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:background="#FF000000">

</ch03.game.sb.SpaceBlasterGame>

The XML in Listing 3-1 creates a LinearLayout with the ID ll_absolute whose width and height will fill the screen (fill_parent). It has a vertical orientation, so child controls will be aligned vertically. Other utility classes in the project follow:

  • AudioClip.java: This sound handler class deals with the device's media player.

  • Tools.java: This simple class has utility methods to display messages to the user.

To get started, you will need the chapter source that comes along with this book, which contains all the resources for the next sections.

Creating the Project

Create a project to host the game. Here is how:

  1. Click New Android Project from the toolbar, and enter the following information (see Figure 3-4):

    • Name: ch03.SpaceBlaster

    • Build Target: Android 1.1 or Android 1.5

    • Application Name: Space Blaster

    • Package Name: ch03.game.sb

    • Activity: SpaceBlaster

    • Min SDK version: 3 for 1.5, 2 for 1.1

  2. Click Finish.

    The Space Blaster New Adroid Project dialog

    Figure 3-4. The Space Blaster New Adroid Project dialog

Now, let's work on the main activity and layout classes.

Creating the Game's Activity Class

Listing 3-2 shows the game activity class, ch03.game.sb.SpaceBlaster. This is the core class that will be executed when the game starts. When an instance of the game is created, the onCreate() method will be called once. This method loads the user-defined layout described in Listing 3-1.

Example 3-2. Space Blaster's Main Activity Class

package ch03.game.sb;

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;

public class SpaceBlaster extends Activity {
    private View view;

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

        LayoutInflater factory = LayoutInflater.from(this);

        // Set game layout
        view = factory.inflate(R.layout.main, null);
        setContentView(view);

        // Enable view key events
        view.setFocusable(true);
        view.setFocusableInTouchMode(true);

    }

    @Override
    protected void onStop() {
        super.onStop();
        ((ArcadeGame) view).halt();
    }

    @Override
    protected void onPause() {
        super.onPause();
        onStop();
    }

    @Override
    protected void onRestart() {
super.onRestart();
        ((ArcadeGame) view).resume();
    }
}

The class keeps a reference to the layout (using the view class variable). This variable is used to halt the game when the user exists or restart it, as shown in the onStop and onRestart methods. To manually load the user-defined layout, we use the LayoutInfalter class:

LayoutInflater factory = LayoutInflater.from(this);

 // Set game layout
 view = factory.inflate(R.layout.main, null);
setContentView(view);

Two important lines are used to cascade key and touch events to the inner layout:

view.setFocusable(true);
view.setFocusableInTouchMode(true);

setFocusable tells the view (the user-defined layout) that it can receive focus and thus events in key or touch modes. These two lines are critical; if they are commented out, the game will not receive events.

Creating the Game Layout

As mentioned before, the class SpaceBlasterGame extends ArcadeGame, which in turn extends the Android layout LinearLayout. In this way, we can define a game thread to update the state of the game and simply draw sprites in the onDraw method of LinearLayout, thus gaining a finer control over the drawing process. SpaceBlasterGame performs the drawing, and it has all the game logic. This class is relatively complex compared to the others, but before we take a look at it, let's see how the game thread is handled by ArcadeGame.

ArcadeGame is the abstract base class for SpaceBlasterGame, and it deals with the game loop. This loop is infinite and invalidates the view to force a redraw of the display (see Listing 3-3).

Example 3-3. Abstract Class ArcadeGame

package ch03.game.sb;

import java.util.Timer;
import java.util.TimerTask;

import ch03.common.AudioClip;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.AttributeSet;
import android.widget.LinearLayout;

/**
* Base class for all games. Extends {@link LinearLayout} and uses a
 * {@link TimerTask} to invalidate the view
 *
 * @author V. Silva
 *
 */
public abstract class ArcadeGame extends LinearLayout {
    // App context
    private Context mContex;

    // Update timer used to invalidate the view
    private Timer mUpdateTimer;

    // Timer period
    private long mPeriod = 1000;

    /**
     * C
     *
     * @param context
     */
    public ArcadeGame(Context context) {
        super(context);
        mContex = context;
    }

    public ArcadeGame(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContex = context;
    }

    /**
     * Fires on layout
     */
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        try {
            // Init game
            initialize();

            /**
             * start update task. Which will fire onDraw in the future
             */
            startUpdateTimer();
        } catch (Exception e) {
            // bug
            e.printStackTrace();
        }
    }

    /**
* Set the update period
     *
     * @param period
     */
    public void setUpdatePeriod(long period) {
        mPeriod = period;
    }

    /**
     * A timer is used to move the sprite around
     */
    protected void startUpdateTimer() {
        mUpdateTimer = new Timer();
        mUpdateTimer.schedule(new UpdateTask(), 0, mPeriod);
    }

    protected void stopUpdateTimer() {
        if (mUpdateTimer != null) {
            mUpdateTimer.cancel();
        }
    }

    public Context getContex() {
        return mContex;
    }

    /**
     * Load an image
     *
     * @param id
     * @return
     */
    protected Bitmap getImage(int id) {
        return BitmapFactory.decodeResource(mContex.getResources(), id);
    }

    /**
     * Get AudioClip
     *
     * @param id
     * @return
     */
    protected AudioClip getAudioClip(int id) {
        return new AudioClip(mContex, id);
    }

    /**
     * Overload this to update the sprites on the game
     */
    abstract protected void updatePhysics();
/**
     * Overload to initialize the game
     */
    abstract protected void initialize();

    abstract protected boolean gameOver();

    abstract protected long getScore();

    /**
     * Canvas update task
     *
     * @author vsilva
     *
     */
    private class UpdateTask extends TimerTask {

        @Override
        public void run() {
            updatePhysics();

            /**
             * Cause an invalidate to happen on a subsequent cycle
             * through the event loop. Use this to invalidate the View
             * from a non-UI thread. onDraw will be called sometime
             * in the future.
             */
            postInvalidate();
        }

    }

    /**
     * Halt game. Stops the update task. Called by a parent activity to halt
     *
     */
    public void halt() {
        stopUpdateTimer();
    }

    /**
     * Resume Game
     */
    public void resume() {
        initialize();
        startUpdateTimer();
    }
}

Any user-defined layout must define constructors with an Android Context and set of attributes for being able to be processed properly by the system:

public ArcadeGame(Context context) {
    super(context);
    mContex = context;
}

public ArcadeGame(Context context, AttributeSet attrs) {
    super(context, attrs);
    mContex = context;
}

Note that ArcadeGame extends LinearLayout and sends its arguments to the parent via super(context) and super(context, attrs). This class also overloads the parent's method onLayout(), which fires when the view should assign a size and position to each of its children:

/**
     * Fires on layout
     */
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        try {
            // Init game
            initialize();

            /**
             * start update task. Which will fire onDraw in the future
             */
            startUpdateTimer();
        } catch (Exception e) {
            // bug
            e.printStackTrace();
        }
    }

onLayout is in charge of initializing the game and starting the update timer. Note that initialize() is an abstract method that must be overloaded by the child class (SpaceBlasterGame) to perform initialization. The update timer, on the other hand, will be defined within AbstractGame. To define a game loop, we can use a simple TimerTask that will update itself using a period, as shown in Listing 3-4.

Example 3-4. Defining a Game Loop Using a Timer Task

// Timer period
    private long mPeriod = 1000;

    /**
     * Canvas update task
     *
     * @author vsilva
     *
     */
    private class UpdateTask extends TimerTask {
        @Override
public void run() {
            updatePhysics();

            /**
             * Cause an invalidate to happen on a subsequent cycle
             * through the event loop. Use this to invalidate the View
             * from a non-UI thread. onDraw will be called sometime
             * in the future.
             */
            postInvalidate();
        }

    }

    /**
     * A timer is used to move the sprite around
     */
    protected void startUpdateTimer() {
        mUpdateTimer = new Timer();
        mUpdateTimer.schedule(new UpdateTask(), 0, mPeriod);
    }

    protected void stopUpdateTimer() {
        if (mUpdateTimer != null) {
            mUpdateTimer.cancel();
        }
    }

    /**
     * Set the update period
     *
     * @param period
     */
    public void setUpdatePeriod(long period) {
        mPeriod = period;
    }

When the layout initializes, the onLayout() method will fire and call initialize() and startUpdateTimer(). These methods will start the Timertask:run() method, which updates the physics of the child class and invalidates the view by calling postInvalidate(). Invalidating the view will, in turn, tell the system UI thread to refresh the display. This cycle will repeat itself until the value of the update period (mPeriod) is reached, and this value can be set by the main class (SpaceBlaster) using the setUpdatePeriod method. In this way, we have created a simple refresh loop for the game.

Implementing the Game

The previous sections showed you the foundation for the main class, SpaceBlasterGame. Here is where all the meat resides. Let's take a closer look at the most important sections of SpaceBlaster.java. Note that the class has been stripped for simplicity in Listing 3-5, but the chapter source contains the full implementation.

Example 3-5. The Main Game Class

package ch03.game.sb;

import ch03.common.AudioClip;
import ch03.common.Tools;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;

public class SpaceBlasterGame extends ArcadeGame
{
    // Game name
    public static final String NAME = "SpaceBlaster";

    // Refresh rate (ms)
    private static final long UPDATE_DELAY = 40;

    private Context mContext;

    // For text
    private Paint mTextPaint = new Paint();

    // For Bitmaps
    private Paint mBitmapPaint = new Paint();

    private Paint mLaserBarPaint = new Paint();
    private Paint mShieldBarPaint = new Paint();
    private Paint mShieldPaint = new Paint();

    /**
     * Constructor
     *
     * @param context
     */
    public SpaceBlasterGame(Context context) {
        super(context);
        mContext = context;
        super.setUpdatePeriod(UPDATE_DELAY);
    }

    public SpaceBlasterGame(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        super.setUpdatePeriod(UPDATE_DELAY);
    }
// Code removed for simplicity
    // See chapter source
    // ...
}

The class constructor SpaceBlasterGame starts by initializing the Android context by calling super(context) and setting the game update delay to 40 milliseconds. Note that the context is critical for all the UI thread operations. A series of Paint objects is also defined to hold the style and color information about how to draw geometries, text, and bitmaps:

  • mTextPaint holds style and color information for the text. It will be initialized as white.

  • mBitmapPaint holds style and color information for all bitmaps.

  • mLaserBarPaint holds style and color information for the laser bar at the bottom of the screen.

  • mShieldBarPaint holds style and color information for the shield bar at the bottom of the screen.

  • mShieldPaint holds style and color information for the ship's shield. It will be used to draw an oval filled with a semitransparent light blue color around the ship.

Initializing Sprites and Sounds

Game initialization occurs in the initialize method overloaded from the parent abstract class ArcadeGame (see Listing 3-6). The call sequence goes as follows: LinearLayout.onLayout calls ArcadeGame.initilize, which calls SpaceBlasterGame.initilize. This last method performs the following tasks:

  • Set the screen size.

  • Set the style and color attributes for the Paint objects: text, laser bar, and shield bar.

  • Load the game bitmap sprites: ship, meteor, laser bullet, and explosion sequence.

  • Load the audio clips.

Example 3-6. Game Initialization

public void initialize() {
    int n;

    // Screen size
    int width = getWidth();
    int height = getHeight();

    // Text Paints
    mTextPaint.setARGB(255, 255, 255, 255);
    mShieldPaint.setARGB(125, 0, 255, 255);
// Laser Bar Energy
   mLaserBarPaint.setARGB(255, 0, 255, 96);
   mLaserBarPaint.setStyle(Paint.Style.FILL);

   // Shield Bar Energy
   mShieldBarPaint.setARGB(255, 0, 255, 255);
   mShieldBarPaint.setStyle(Paint.Style.FILL);

   ship = getImage(R.drawable.sb_ship);
   bullet = getImage(R.drawable.sb_bullet);
   fire = new Bitmap[fireframe];

   // Load fire image sprites
   int[] ids = new int[] { R.drawable.sb_fire0, R.drawable.sb_fire1 };

   for (n = 0; n < fireframe; n++) {
       fire[n] = getImage(ids[n]);
   }

   // Load meteor explosion sequence sprites
   ids = new int[] { R.drawable.sb_boom0, R.drawable.sb_boom1,
           R.drawable.sb_boom2, R.drawable.sb_boom3,
           R.drawable.sb_boom4 };

   boom = new Bitmap[bframes + 1];

   for (n = 0; n <= bframes; n++) {
       boom[n] = getImage(ids[n]);
   }

   // ...

   // Meteor initialize
   meteor = getImage(R.drawable.sb_meteor);

   // ...

   // Load Audio clips
   try {
       blast = getAudioClip(R.raw.sb_blast);
       crash = getAudioClip(R.raw.sb_collisn);
       kill = getAudioClip(R.raw.sb_mdestr);
   } catch (Exception e) {
       Tools.MessageBox(mContext, "Audio Error: " + e.toString());
   }

   initStars();
   // ...

}

Loading Bitmap Sprites

Bitmap sprites are loaded by SpaceBlasterGame.initialize from the drawables section of the project using the getImage(RESOURCEID) method. getImage is defined in the base class ArcadeGame, so it'll be easy for child classes to reuse. For example, the game's explosion is simply an array of bitmaps loaded from the drawables folder (shown in Figure 3-5). To create an explosion, the game manipulates the explosion frame number when the display is drawn. The tricky part is keeping track of many explosions and their X and Y coordinates. Consider the next fragment, which loads bitmaps for the ship, laser bullet and explosion frames:

// Ship
        ship = getImage(R.drawable.sb_ship);

        // Laser Bullet
        bullet = getImage(R.drawable.sb_bullet);

        // Load meteor explosion sequence sprites
        ids = new int[] { R.drawable.sb_boom0, R.drawable.sb_boom1,
                R.drawable.sb_boom2, R.drawable.sb_boom3,
                R.drawable.sb_boom4 };

        boom = new Bitmap[bframes + 1];

        for (n = 0; n <= bframes; n++) {
            boom[n] = getImage(ids[n]);
        }
Game sprites in the drawables folder

Figure 3-5. Game sprites in the drawables folder

Creating the Star Field

Another neat trick is to create a random star field in the background (see Figure 3-6). We can do this by using arrays of X and Y coordinates for each star. But we must also set style and colors using Paint objects. Listing 3-7 demonstrates how to create the random star field and styles for SpaceBlaster.

Example 3-7. A Random Star Field

/**
     * create the star field in the background
     */
    public void initStars() {
        starsX = new int[numStars];
        starsY = new int[numStars];
        starsC = new Paint[numStars];
        for (int i = 0; i < numStars; i++) {
            starsX[i] = (int) ((Math.random() * xSize − 1) + 1);
            starsY[i] = (int) ((Math.random() * ySize − 1) + 1);
            starsC[i] = newColor();
        }
    }

    public Paint newColor() {
int r = Math.random() * 255;
        int g = Math.random() * 255;
        int b = Math.random() * 255;
        Paint p = new Paint();
        p.setARGB(255, r, g, b);
        return p;
    }
The star field with game sprites

Figure 3-6. The star field with game sprites

Playing Audio Clips

A good sound implementation is critical for any successful game. Android gives you two choices when dealing with game sound:

  • Implement the sound logic in Java using Android's MediaPlayer API.

  • Implement the sound natively (in C or C++). This could be an option if you have native code and wish to reuse it. This can be though as, by the time of this writing, Google provides no support for native development.

Java sound must be implemented using the MediaPlayer API. This is a powerful media tool, but it has some drawbacks when dealing with game sound:

  • The MediaPlayer can only open raw resources (sound files within your project) or files located in the file system. This is a serious caveat if the game packs sprites and sounds in a single file. 3D games like Doom and Wolf 3D use a single file for sprites and sounds, and they use a mixer device to combine streams of bytes simultaneously. This solution is not possible with the MediaPlayer.

  • The MediaPlayer API has some annoying idiosyncrasies when developing. For example, playing a resource sound is easy:

    MediaPlayer mPlayer = MediaPlayer.create(Context, RESOURCEID);
    mPlayer.start();
  • However if you attempt to pause a sound before it plays or issue two simultaneous play commands, the MediaPlayer throws an IllegalState exception. The same thing happens if you try to replay a sound before preparing or making sure the player has been paused. These limitations make dealing with the player annoying and difficult for a game where multiple sounds must be replayed continuously.

  • The MediaPlayer consumes significant resources and can slow things down quite a bit, especially if you have lots of sounds.

Listing 3-8 shows the AudioClip sound class. It is designed to mirror the Java SE class of the same name by wrapping an instance of the MediaPlayer and dealing with the media events behind the scenes.

Example 3-8. An Audio Helper Class

package ch03.common;

import android.content.Context;
import android.media.MediaPlayer;

public class AudioClip {
    private MediaPlayer mPlayer;
    private String name;

    private boolean mPlaying = false;
    private boolean mLoop = false;

    public AudioClip(Context ctx, int resID)
    {
        // clip name
        name = ctx.getResources().getResourceName(resID);

        // Create a media player
        mPlayer = MediaPlayer.create(ctx, resID);

        // Listen for  completion events
        mPlayer.setOnCompletionListener(
             new MediaPlayer.OnCompletionListener() {
@Override
                    public void onCompletion(MediaPlayer mp) {
                        mPlaying = false;

                        if (mLoop) {
                            mp.start();
                        }
                    }

             });
    }


    public synchronized void play() {
        if (mPlaying)
            return;

        if (mPlayer != null) {
            mPlaying = true;
            mPlayer.start();
        }
    }

    public synchronized void stop() {
        try {
            mLoop = false;
            if (mPlaying) {
                mPlaying = false;
                mPlayer.pause();
            }

        } catch (Exception e) {
            System.err.println("AduioClip::stop " + name + " "
                    + e.toString());
        }
    }

    public synchronized void loop() {
        mLoop = true;
        mPlaying = true;
        mPlayer.start();

    }

    public void release() {
        if (mPlayer != null) {
            mPlayer.release();
            mPlayer = null;
        }
    }
}

AudioClip wraps and instance of MediaPlayer and is aware of the state of the sound: paying, paused, or looping. This awareness is necessary for dealing with IllegalState exceptions explained previously. AudioClip also listens for completion events via setOnCompletionListener(OnCompletionListener), and if the loop flag is set, it will replay the sound. Space Blaster has three sounds that can be loaded with the code:

blast = getAudioClip(R.raw.sb_blast);      // Laser shot
crash = getAudioClip(R.raw.sb_collisn);  // Ship collision
kill = getAudioClip(R.raw.sb_mdestr);     // meteor destruction

getAudioClip takes a sound resource ID as argument, and it is defined in the abstract class ArcadeGame as follows:

protected AudioClip getAudioClip(int id) {
return new AudioClip(mContex, id);
}

Tip

Audio resources must be saved in the res/raw folder within the project.

About Native Sound

Native games like Doom and Wolf 3D, shown in later chapters, pack sound resources in their own formats and manipulate the sound device at the operating system level. Even though Android runs a Linux kernel behind the scenes, it is not a standard Linux distribution, especially with dealing with low-level devices, for example:

  • The standard Linux sound device (/dev/dsp) and mixer (/dev/mixer) are not supported, making reusing sound logic very difficult for native games. For some reason, Google decided to use the Sonivox Enhanced Audio System (EAS) via the /dev/eac device. This caveat is compounded by the fact that Google provides no support for native development. Nevertheless, a solution is to cascade the sound name back to Java via JNI and play it using AudioClip. This technique is explained in detail in Chapters 6 and 7.

  • If you have native code that uses OpenAL/OpenGL for accelerated sound and graphics, you are in trouble. Google wants all development done in its Java-based version of OpenGL and doesn't support OpenAL. This leaves you with the painful task of translating OpenGL logic line by line from C to Java. Nevertheless, all is not lost. I found a neat trick to leave your C-based OpenGL code intact and still be able to run it from Java, and I explain my trick in 21.330 Chapter 5.

Handling Key and Touch Events

Key and touch events can be handled by overloading the following methods (as shown in Listing 3-9):

  • onKeyDown: This event fires when a key is pressed. The event receives two arguments: an integer representing the key code and a KeyEvent. Note that the key code is an integer value defined by Android not related to the standard ASCII keys. To test for a specific key, simply compare the value with the static key constants in KeyEvent.

  • onKeyUp: This event fires when a key is released, and it receives the same arguments as onKeUp. Furthermore, you can also check the status of the Shift or Alt keys using event.isShiftPressed() or event.isAltPressed() respectively.

  • onTouchEvent: This event fires when you tap the screen, and it receives a MotionEvent. Tap events have two states: DOWN and UP just like key events. You can check the tap state by comparing event.getAction() with MotionEvent.ACTION_DOWN or MotionEvent.ACTION_UP. You can also get the X and Y coordinates relative to the upper-left corner of the screen.

Example 3-9. Handling Touch and Key Events

/**
 * Android Key events
 */
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    keyUp(keyCode);
    return true;
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    keyDown(keyCode);
    return true;
}

public boolean onTouchEvent(MotionEvent event) {
    int tx = (int)event.getX();
    int ty = (int)event.getY();

    // Has the ship been touched. if so move it
    if ( tx >= x && tx <= x + ship.getWidth()
         && ty >= y && ty <= y + ship.getHeight()){
        x = tx - (ship.getWidth()/2);
        y = ty - (ship.getHeight()/2);

    }
    // else handle tap
    else if ( event.getAction() == MotionEvent.ACTION_UP)
    {
// if not in game, start
        if ( !ingame ) {
            ingame = true;
            GameStart();
        }
        else {
            // fire gun
            keyDown(KeyEvent.KEYCODE_SPACE);
        }
    }
    return true;
}

/**
 * Process key down event
 * @param key Android Key code
 * @return
 */
public boolean keyDown( int key) {
    if (ingame) {
        mousex = −1;
        if (key == KeyEvent.KEYCODE_DPAD_LEFT
                || key == KeyEvent.KEYCODE_Q)
            dx = −1;
        if (key == KeyEvent.KEYCODE_DPAD_RIGHT
                || key == KeyEvent.KEYCODE_W)
            dx = 1;
        if (key == KeyEvent.KEYCODE_DPAD_UP
                || key == KeyEvent.KEYCODE_O)
            dy = −1;
        if (key == KeyEvent.KEYCODE_DPAD_DOWN
                || key == KeyEvent.KEYCODE_L)
            dy = 1;
        if ( (key ==  KeyEvent.KEYCODE_SPACE) ) {
            if (bcur > 0) {
                fireGun();
            }
        }
    } else {
        if (key == KeyEvent.KEYCODE_S) {
            ingame = true;
            GameStart();
        }
    }
    if (key == KeyEvent.KEYCODE_E){
        ingame = false;
    }

    if (key == KeyEvent.KEYCODE_Q){
        // Arggg!! There should be a better wayt to quit!
        System.exit(0);
}
    return true;
}

/**
 * Process key up event
 * @param e Key event
 * @param key key code
 * @return
 */
public boolean keyUp(int key) {
    if (key == KeyEvent.KEYCODE_DPAD_LEFT
            || key == KeyEvent.KEYCODE_DPAD_RIGHT
            || key == KeyEvent.KEYCODE_Q
            || key == KeyEvent.KEYCODE_W)
        dx = 0;
    if (key == KeyEvent.KEYCODE_DPAD_UP
            || key == KeyEvent.KEYCODE_DPAD_DOWN
            || key == KeyEvent.KEYCODE_O
            || key == KeyEvent.KEYCODE_L)
        dy = 0;
    return true;
}

In SpaceBlaster, when the pad arrows are pressed, the position of the ship is shifted by 1 unit in the corresponding direction. S stars the game; E ends it, and Q terminates the application. The onTouchEvent allows the ship to be dragged around the device screen using your fingertips. When the game is running, tapping anywhere on the device screen (other than the ship) will start the game if it's not already started or fire the laser gun if the game is already running.

At this point, the project, including all files and resources, should look as shown 24.251in Figure 3-7.

Resources for Space Blaster

Figure 3-7. Resources for Space Blaster

Testing on the Emulator

We can finally start gaming! Start your emulator, and run the game like so:

  1. From the Eclipse main menu, click Run Run Configurations.

  2. On the left side, right-click Android Application New.

  3. Enter a name (SpaceBlaster), and select the project ch03.SpaceBlaster (see Figure 3-8).

  4. Click Run.

Figure 3-6 shows how the game should look on the emulator. To play, tap the screen and use the arrows to move the ship around. Press the space bar to fire the gun.

The Run Configurations dialog for SpaceBlaster

Figure 3-8. The Run Configurations dialog for SpaceBlaster

What's Next?

In this chapter, you have taken the first steps in writing a simple Java game and looked at the different issues that affect Java game development for Android, such as the significant differences between the Android API and the Java ME standard and how that influences code reuse and multiplatform support. You also learned gaming tricks, including creating an XML user-defined linear layout, using a timer task to simulate a game loop, invalidating a view layout within a user-defined (non-UI) thread, loading sprites and sounds, and dealing with the idiosyncrasies of the media player. You also saw some drawing techniques for creating sprite animations and simple objects and setting text styles and colors using the Paint object. Finally, I showed you how to deal with key and touch events.

In the next chapter, we take things further to create an arcade classic—Asteroids, a game that presents a new set of challenges when dealing with drawing techniques.

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

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