Hour 15. Building an Android Game Engine


What You’ll Learn in This Hour

Designing an Android game engine

Creating an Android library project

Writing the core engine classes

• Testing the engine with a demo project


The first step of developing a game for a new hardware platform like the Android OS is to study the hardware and learn how to tap into it for the purposes of utilizing the hardware for a game. We have done that over the previous 14 hours, having learned about the development tools, display system, resource manager, touch input system, hardware sensors (such as the accelerometer), and the audio system. Understanding the hardware is important for a game developer because you want to gain as much control over a system as possible to eke out every bit of performance.

A logical next step is to encapsulate that hardware in code that abstracts it, taking the sting out of working with low-level hardware (which can be very frustrating) and simplifying access to it through encapsulation. In other words, we want to write Java classes that present us with a game engine rather than the Android SDK. At this level, we do not want to see very much Android SDK code. What we want to see instead are classes and methods in our game engine that are layered over the Android SDK. This process increases the legibility of the code, making it easier to make improvements and fix bugs (which will hopefully be few!).

The goal this hour is to learn how to create an Android library project that contains the basic Activity code and then use that library as the basis or “engine” for Android games. At this point, it is important that you understand the basics covered so far in the book before continuing. So, if you tend to skip through a book, it would be a good idea to cover the core hardware hours before continuing so you have a good understanding of the graphics system, file formats, and rendering.

Designing an Android Game Engine

The design for a game engine at this level will not be large or complex. The engine will not include level editors and scripting, as you would expect to find in a professional engine like Unity. No, we’re going for a framework that brings together all the resources in the Android SDK needed to make a variety of games more easily than would be possible with the Android SDK alone. The goal is not to write code for others, but to wrap all the code we’ve studied so far in easier-to-use containers, or classes, so that our own games will be easier to make. Most game engines are not released to the public, either for free or for sale, they are developed internally and maintained internally, not for use by the public. Many game studios license a third-party engine (such as Unity) to save time and money, and some studios create their own.


By the Way

The Unity game engine also supports Android and iPhone. Check out http://unity3d.com for details.


Design Goals

We do not need a large, complex engine chock full of features for every game genre. This will be more of a lean engine to assist with making games for Android a little easier. Hopefully, it will also make the code more reusable. You might call this library a framework rather than an engine, and that would be just as appropriate. Let’s describe some goals for this engine to help with writing the code for it later. The engine should, at minimum, meet these major design goals:

• Make certain assumptions about the minimum hardware

• Launch a game loop in a thread

• Detect multi-touch user input on the touch screen

• Provide easy access to game asset files

• Provide access to image resources

• Provide access to the audio system

• Provide a customizable animation system

• Can automatically report object collisions

• Can manage actors and other objects internally

• Use an event manager to communicate with components

In addition, these less important but interesting goals can also be implemented in the engine:

• Automatically clear the screen with a certain color

• Allow either portrait or landscape orientation

• Calculate the frame rate of the game loop

• Handle string output conversion for all data types

• Handle most data type conversions automatically

Engine Components

With the aforementioned goals in mind, let’s go over the major components of the engine at this early design stage to determine what direction to take with the source code.

Engine Core

The core of the engine will be a class that inherits from Activity, as usual, but which also implements the interface classes such as Runnable and OnTouchListener for threading and input services, among other things. The core class will also encapsulate the SurfaceView and Canvas objects that previously were made available via the DrawView class (which will no longer be needed). The core includes features such as an event manager that facilitates communication among the engine components. For instance, an event can be generated by the code responsible for detecting user input on the touch screen, and a potential future GUI system would consume that event to control the GUI for a game. Another common practice is to have a time generate events at regular intervals (such as one per second for frame-rate timing).

Startup

The startup method is called Activity.onCreate(). This is quite specific to the Android platform, and unlike any other startup method I’ve ever encountered on the many platforms I’ve used over the years (from Windows Phone to Nintendo DS to Windows to Xbox 360). It’s not a bad method; it’s just unique to Android. What we want to do with the engine is to abstract away the proprietary stuff and make more of a standardized interface to the Android OS. In other words, you want to be able to write similar code—that’s similar code, not necessarily the same code—and compile it on different platforms. This is extremely important today with so many different OSs and devices on the market. Gone are the days of releasing a game only for Windows, Mac, or Linux! Today, the video game market is broad, and you will want to take advantage of every platform that will increase sales and awareness of both your brand and your games.

We will invoke a custom method in the engine class called Engine.load(), which will be called from Activity.onCreate(), after the display surface, context, and resource manager have been initialized, to allow the game to load assets.

There is one additional startup step that the engine will need: a preload initialization event. We need to be able to configure the game with basic settings before the screen is initialized into either portrait or landscape mode. Engine.init() will take care of this need for us.

Main Thread

The main thread, or cycle update, takes place in the Runnable.run() method, which operates in its own thread. This method contains a while loop that is the game loop for the engine and any game that uses the engine. Thus, derived game projects need not include their own game loop. The loop is in the engine! By managing the loop and all updates from the engine, the derived game is freed up to focus on gameplay rather than logistics, such as timing, updates, and drawing, at a lower level. The derived game can operate at one level higher, from a coding perspective, and allow the engine to take care of the messy details, so to speak.

The engine will do some updates automatically, such as detecting user input on the touch screen, calculating frame rate, and doing cyclic timing. Some processes need to run at the maximum speed of the processor, and these are called cycle updates. But updating and drawing game entities—scenes, actors and props—takes place at timed intervals. Drawing will take place at approximately 60 frames per second (fps) in a method called Engine.draw(delta), and updating will run at the untimed, full-cycle speed in a method called Engine.update(delta). In both cases, the delta parameter is a millisecond value representing the time since the previous timed draw or untimed update, respectively. The delta value makes it possible to synchronize animations and other gameplay behaviors to a consistent runtime.

The full-cycle-speed update is also where engine-level processes take place, such as automated collision detection, entity management, and so on.

Rendering

Rendering is the most important component of a game, although it depends on other components to work correctly. The Android mechanism for drawing can work via Activity.onDraw(), but as you learned early on, this is limited to the user interface and not suitable for a real-time game. Rendering takes place via the SurfaceHolder and SurfaceView classes. But, instead of creating an inner class this time, like we did with the DrawView custom class in prior hours, the engine will define these objects and use them internally without a helper class. The SurfaceView class ends up being useful only to return its surface via the getHolder() method and need not be subclassed.

The rendering portion of the engine takes place in the main while loop, explained earlier. The Engine.draw(delta) method call allows the game project (which is a subclass of the engine) to draw entities at the gameplay level without regard for the underlying logistics. This significantly frees up the programmer or designer to focus on gameplay code. But even more important, this separation of game engine from game play code makes it possible to write updates to the engine without rewriting any game code.

Creating an Android Library Project

Our Android game engine will have its own project, separate from any game or app that uses it. The project will be a special type of project configured as a library. A library project contains reusable code that does not run on its own. To use the library project, you must create another project that uses or consumes the library’s features in order to make a game.

A simple library project might have only a single class with static methods. For instance, you could store reusable vector math functions in a class using methods, so that the methods are called directly (without instantiating the class). For instance, if you had a fictional class called VectorMath, and it contained a static method called DotProduct(),you would call this method with

VectorMath.DotProduct()

The class stored in a library project may have to be resolved with a complete namespace, such as android.engine.VectorMath, but that can be resolved with an import statement.

Converting to a Library Project

The new Android project is generated by Eclipse as a standard runnable program. Here is the base source code found in the new Engine.java file:

package game.engine;

import android.app.Activity;
import android.os.Bundle;

public class Engine extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

We will be making changes to this code. But first, the project has to be converted so that it will compile as a linkable rather than an executable library (embedded inside an .APK file—an Android Package).


Did You Know

An APK file is an Android Package, a renamed ZIP file containing the compiled binary and assets ready to run on an Android device. You can verify this by opening the APK file in the in folder of an Android project. The first two characters are PK—the initials of Phil Katz, inventor of the ZIP compression algorithm.


To convert a regular project into a library project, open the Project menu in Eclipse and choose Properties. From the list of filters on the left, choose Android. The Android project build targets and libraries will be shown. From this dialog screen, you can add additional libraries that the project depends on, if needed—for instance, a third-party library that you intend to use in a game. This is pretty common for larger, more complex games that need additional features.


By the Way

A good example of a third-party library is Box2D, a physics library. Check out http://box2d.org/links/ for details on the Box2D library for Android (JBox2D).


In Figure 15.6, note the lower panel titled Library. A single check box, Is Library, will convert this project to a library project. That’s all you need to do.

Image

Figure 15.6. Changing the project type to a library project.

Writing the Core Engine Classes

A few core engine classes are necessary to get the engine up and running with basic functionality. Over the next several hours we will add additional classes to the engine until it is fully featured and capable of handling just about any genre of game you may want to develop.


Watch Out

The “engine” discussed in this section will grow and evolve from one hour to the next and will not be a single, large, complete engine until the final hour. Some code will be revised as well, so you will see the library evolve as needed. I encourage you to develop your own engine in parallel with the concepts described here over the next several hours and add your own flair to the project. Go above and beyond the essentials suggested in the book!


Engine Class

While building the core engine class, one thing we want to ensure is that it will be easy to port any Android demo or game to the engine with a minimum of fuss. This means that, at least until the engine matures, we need to expose the basic Android rendering objects to the game class, including such basic objects as the SurfaceHolder and Canvas used for drawing. Eventually, you will want to move common features inside the engine. But until those features are added and tested, it is helpful to keep the internals publicly visible to subclasses (consumers) of the engine.

Following is the source code for the core of the engine, the Engine class, found in the Engine.java source file. This is an early version of the core engine that will see improvement during upcoming hours as new features are added. The file should be included in the game engine library project (not the game demo).

Note that additional source code files will be added after this one before it will be a completely usable library for building a game project.

/**
 * Android Game Engine Core Class
*/
package game.engine;
import java.math.BigDecimal;
import android.app.Activity;
import android.os.Bundle;
import android.renderscript.Float2;
import android.renderscript.Float3;
import android.content.pm.ActivityInfo;
import android.graphics.*;
import android.util.Log;
import android.view.*;
import android.view.View.OnTouchListener;

/**
 * Engine Core Class
 */
public abstract class Engine extends Activity implements Runnable,
OnTouchListener {
    private SurfaceView p_view;
    private Canvas p_canvas;
    private Thread p_thread;
    private boolean p_running, p_paused;
    private int p_pauseCount;
    private Paint p_paintDraw, p_paintFont;
    private Typeface p_typeface;
    private Point[] p_touchPoints;
    private int p_numPoints;
    private long p_preferredFrameRate, p_sleepTime;

    /**
     * Engine constructor
     */
    public Engine() {
        Log.d("Engine","Engine constructor");
        p_view = null;
        p_canvas = null;
        p_thread = null;
        p_running = false;
        p_paused = false;
        p_paintDraw = null;
        p_paintFont = null;
        p_numPoints = 0;
        p_typeface = null;
        p_preferredFrameRate = 40;
        p_sleepTime = 1000 / p_preferredFrameRate;
        p_pauseCount = 0;
    }

    /**
     * Abstract methods that must be implemented in the sub-class!
     */
    public abstract void init();
    public abstract void load();
    public abstract void draw();
    public abstract void update();

    /**
     * Activity.onCreate event method
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("Engine","Engine.onCreate start");

        //disable the title bar
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        //set default screen orientation
        setScreenOrientation(ScreenModes.LANDSCAPE);

        /**
         * Call abstract init method in sub-class!
         */
        init();

        //create the view object
        p_view = new SurfaceView(this);
        setContentView(p_view);

        //turn on touch listening
        p_view.setOnTouchListener(this);

        //create the points array
        p_touchPoints = new Point[5];
        for (int n=0; n<5; n++) {
            p_touchPoints[n] = new Point(0,0);
        }

        //create Paint object for drawing styles
        p_paintDraw = new Paint();
        p_paintDraw.setColor(Color.WHITE);

        //create Paint object for font settings
        p_paintFont = new Paint();
        p_paintFont.setColor(Color.WHITE);
        p_paintFont.setTextSize(24);

        /**
         * Call abstract load method in sub-class!
         */
        load();

        //launch the thread
        p_running = true;
        p_thread = new Thread(this);
        p_thread.start();

        Log.d("Engine","Engine.onCreate end");
    }

    /**
     * Runnable.run thread method (MAIN LOOP)
     */
    @Override
    public void run() {
        Log.d("Engine","Engine.run start");

        Timer frameTimer = new Timer();
        int frameCount=0;
        int frameRate=0;
        long startTime=0;
        long timeDiff=0;

        while (p_running) {

            if (p_paused) continue;

            /**
             * Calculate frame rate
             */
            frameCount++;
            startTime = frameTimer.getElapsed();
            if (frameTimer.stopwatch(1000)) {
                frameRate = frameCount;
                frameCount = 0;

                //reset touch input count
                p_numPoints = 0;
            }

            /**
             * Call abstract update method in sub-class!
             */
            update();

            /**
             * Rendering section, lock the canvas.
             * Only proceed if the SurfaceView is valid.
             */
            if (beginDrawing()) {

                p_canvas.drawColor(Color.BLUE);

                /**
                 * Call abstract draw method in sub-class!
                 */
                draw();

                int x = p_canvas.getWidth()-150;
                p_canvas.drawText("ENGINE", x, 20, p_paintFont);
                p_canvas.drawText(toString(frameRate) + " FPS", x, 40,
                    p_paintFont);
                p_canvas.drawText("Pauses: " + toString(p_pauseCount),
                    x, 60, p_paintFont);

                /**
                 * Complete the rendering process by
                 * unlocking the canvas.
                 */
                endDrawing();
            }

            /**
             * Calculate frame update time and sleep if necessary.
             */
            timeDiff = frameTimer.getElapsed() - startTime;
            long updatePeriod = p_sleepTime - timeDiff;
            if (updatePeriod > 0) {
                try {
                    Thread.sleep( updatePeriod );
                }
                catch(InterruptedException e) {}
            }

        }
        Log.d("Engine","Engine.run end");
        System.exit(RESULT_OK);
    }

    /**
     * BEGIN RENDERING
     * Verify that the surface is valid and then lock the canvas.
     */
    private boolean beginDrawing() {
        if (!p_view.getHolder().getSurface().isValid()) {
            return false;
        }
        p_canvas = p_view.getHolder().lockCanvas();
        return true;
    }

    /**
     * END RENDERING
     * Unlock the canvas to free it for future use.
     */
    private void endDrawing() {
        p_view.getHolder().unlockCanvasAndPost(p_canvas);
    }

    /**
     * Activity.onResume event method
     */
    @Override
    public void onResume() {
        Log.d("Engine","Engine.onResume");
        super.onResume();
        p_paused = false;
        /*p_running = true;
        p_thread = new Thread(this);
        p_thread.start();*/
    }

    /**
     * Activity.onPause event method
     */
    @Override
    public void onPause() {
        Log.d("Engine","Engine.onPause");
        super.onPause();
        p_paused = true;
        p_pauseCount++;
        /*p_running = false;
        while (true) {
            try {
                p_thread.join();
            }
            catch (InterruptedException e) { }
        }*/
    }

    /**
     * OnTouchListener.onTouch event method
     */
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //count the touch inputs
        p_numPoints = event.getPointerCount();
        if (p_numPoints > 5) p_numPoints = 5;

        //store the input values
        for (int n=0; n<p_numPoints; n++) {
            p_touchPoints[n].x = (int)event.getX(n);
            p_touchPoints[n].y = (int)event.getY(n);
        }
        return true;
    }

    /**
     * Shortcut methods to duplicate existing Android methods.
     */
    public void fatalError(String msg) {
        Log.e("FATAL ERROR", msg);
        System.exit(0);
    }

    /**
     * Drawing helpers
     */
    public void drawText(String text, int x, int y) {
        p_canvas.drawText(text, x, y, p_paintFont);
    }

    /**
     * Engine helper get/set methods for private properties.
     */
    public SurfaceView getView() {
        return p_view;
    }

    public Canvas getCanvas() {
        return p_canvas;
    }

    public void setFrameRate(int rate) {
        p_preferredFrameRate = rate;
        p_sleepTime = 1000 / p_preferredFrameRate;
    }

    public int getTouchInputs() {
        return p_numPoints;
    }

    public Point getTouchPoint(int index) {
        if (index > p_numPoints)
            index = p_numPoints;
        return p_touchPoints[index];
    }

    public void setDrawColor(int color) {
        p_paintDraw.setColor(color);
    }

    public void setTextColor(int color) {
        p_paintFont.setColor(color);
    }

    public void setTextSize(int size) {
        p_paintFont.setTextSize((float)size);
    }

    public void setTextSize(float size) {
        p_paintFont.setTextSize(size);
    }

    /**
     * Font style helper
     */
    public enum FontStyles {
        NORMAL (Typeface.NORMAL),
        BOLD (Typeface.BOLD),
        ITALIC (Typeface.ITALIC),
        BOLD_ITALIC (Typeface.BOLD_ITALIC);
        int value;
        FontStyles(int type) {
            this.value = type;
        }
    }

    public void setTextStyle(FontStyles style) {
        p_typeface = Typeface.create(Typeface.DEFAULT, style.value);
        p_paintFont.setTypeface(p_typeface);
    }

    /**
     * Screen mode helper
     */
    public enum ScreenModes {
        LANDSCAPE (ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE),
        PORTRAIT (ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        int value;
        ScreenModes(int mode) {
            this.value = mode;
        }
    }
    public void setScreenOrientation(ScreenModes mode) {
        setRequestedOrientation(mode.value);
    }

    /**
     * Round to a default 2 decimal places
     */
    public double round(double value) {
        return round(value,2);
    }

    /**
     * Round to any number of decimal places
     */
    public double round(double value, int precision) {
        try {
            BigDecimal bd = new BigDecimal(value);
            BigDecimal rounded = bd.setScale(precision, BigDecimal.
                    ROUND_HALF_UP);
            return rounded.doubleValue();
        }
        catch (Exception e) {
            Log.e("Engine","round: error rounding number");
        }
        return 0;
    }

    /**
     * String conversion helpers
     */
    public String toString(int value) {
        return Integer.toString(value);
    }

    public String toString(float value) {
        return Float.toString(value);
    }

    public String toString(double value) {
        return Double.toString(value);
    }

    public String toString(Float2 value) {
        String s = "X:" + round(value.x) + "," +
            "Y:" + round(value.y);
        return s;
    }

    public String toString(Float3 value) {
        String s = "X:" + round(value.x) + "," +
            "Y:" + round(value.y) + "," +
            "Z:" + round(value.z);
        return s;
    }
}

Timer Class

The Timer class is essential to the engine because it keeps track of the frame rate and helps determine when Thread.sleep() needs to be called (toward the end of the loop). This Timer class is a good, general-purpose timer that uses System.currentTimeMillis() and provides a stopwatch() method for quick and easy timing. If you need more than one timer, create another instance of the Timer class, given that it’s lightweight. The class should be added to a file called Timer.java and included in the game engine library project (not the game demo).

/**
 * Timer Class for Android Game Engine
*/
package game.engine;

public class Timer {
    private long p_start;
    private long p_stopwatchStart;

    public Timer() {
        p_start = System.currentTimeMillis();
        p_stopwatchStart = 0;
    }

    public long getElapsed() {
        return System.currentTimeMillis() - p_start;
    }

    public void rest(int ms) {
        long start = getElapsed();
        while (start + ms > getElapsed()) {
            try {
                Thread.sleep( 1 );
            } catch (InterruptedException e) {}
        }
    }

    public void resetStopwatch() {
        p_stopwatchStart = getElapsed();
    }

    public boolean stopwatch(long ms) {
        if (getElapsed() > p_stopwatchStart + ms) {
            resetStopwatch();
            return true;
        }
        else
            return false;
    }
}

TextPrinter Class

We used TextPrinter in a previous hour, and it was very useful. Every useful feature studied in prior hours will be used to get the engine up and running as quickly as possible, with excellent gameplay features from the start. Following is the source code for the class. The class should be added to a file called TextPrinter.java and included in the game engine library project (not the game demo).

/**
 * TextPrinter Class for Android Game Engine
* This class helps with printing lines of text with auto line
 * increment and reusable properties.
 */
package game.engine;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

public class TextPrinter {
    private Canvas p_canvas;
    private Paint p_paint;
    private float p_x, p_y;
    private float p_spacing;

    public TextPrinter() {
        this(null);
    }

    public TextPrinter(Canvas canvas) {
        p_canvas = canvas;
        p_paint = new Paint();
        p_x = p_y = 0;
        p_spacing = 22;
        setTextSize(18);
        setColor(Color.WHITE);
    }

    public void setCanvas(Canvas canvas) {
        p_canvas = canvas;
    }

    public void setLineSpacing(float spacing) {
        p_spacing = spacing;
    }

    public void setTextSize(float size) {
        p_paint.setTextSize(size);
    }

    public void setColor(int color) {
        p_paint.setColor(color);
    }

    public void draw(String text, float x, float y) {
        p_x = x;
        p_y = y;
        draw(text);
    }

    public void draw(String text) {
        p_canvas.drawText(text, p_x, p_y, p_paint);
        p_y += p_spacing;
    }
}

Texture Class

The Texture class encapsulates android.graphics.Bitmap, making it very easy to create a Bitmap object and load an image into memory from a file in the assets folder. This early version of the class is very basic and may see enhancements over time. It exposes a method, getBitmap(), to be used for drawing elsewhere in the program. The class should be added to a file called Texture.java and included in the game engine library project (not the game demo).

/**
 * Texture Class for Android Game Engine
*/
package game.engine;

import java.io.IOException;
import java.io.InputStream;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

public class Texture {

    private Context p_context;
    private Bitmap p_bitmap;

    public Texture(Context context) {
        p_context = context;
        p_bitmap = null;
    }

    public Bitmap getBitmap() {
        return p_bitmap;
    }

    public boolean loadFromAsset(String filename) {
        InputStream istream=null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        try {
            istream = p_context.getAssets().open(filename);
            p_bitmap = BitmapFactory.decodeStream(istream,null,options);
            istream.close();
        } catch (IOException e) {
            return false;
        }
        return true;
    }
}

Engine Test Demo Project

You will need another project to test the engine library. This part of engine development starts to become very interesting! After the grueling work trying to understand the hardware systems, all that hard work begins to pay off here! At this point, the lower-level Android code is stuffed or hidden away in the engine, and we begin to write code at a higher level. It’s still the same Java language, but we’re no longer using the Android SDK—it’s all engine from this point on. When you come upon new SDK features you need, it’s fine to use them in your game code directly, but even better to add them to the engine first.


Watch Out

If you try to “run” the engine library project, it will crash. You cannot run the engine project directly because it calls abstract methods that must be implemented in a subclass. Be sure to highlight your test project (or its source file), not the engine project. You may optionally choose the correct project from the Run icon drop-down list.


Engine Demo Source Code

Following is the source code for the engine demo test project shown in Figure 15.9. The test program demonstrates several important features for this early build of the engine, including multi-touch input, text output, bitmap loading and drawing, and a separation of engine and game code. The source code is a bit light on comments. This is intentional. I wanted you to see the basic code without any clutter to demonstrate how clean the code is with the engine functionality hidden away in the parent class. Note, for instance, the ease with which a bitmap file is loaded and how easy it is to print text and draw shapes.

Image

Figure 15.9. The first test program—the engine is off to a good start!

/**
* This project consumes the game.engine.Engine class, and so it must
 * reference the library containing this class.
 *
 * Note: The use of 'super.' is only to illustrate which methods are
 * inherited from the Engine class and not needed.
 */

package android.program;

//import java.text.DecimalFormat;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.util.Log;
import game.engine.*;

public class Game extends game.engine.Engine {
    TextPrinter tp;
    Paint paint;
    Canvas canvas;
    Timer timer;
    Texture zombie;

    public Game() {
        Log.d("Game","Game constructor");
        paint = new Paint();
        canvas = null;
        zombie = null;
        tp = new game.engine.TextPrinter();
        tp.setColor(Color.WHITE);
        tp.setTextSize(24);
        tp.setLineSpacing(28);
        timer = new Timer();
    }

    /**
     * Abstract init method called by engine.
     */
    public void init() {
        Log.d("Game","Game.init");
        //(this is the default orientation)
        super.setScreenOrientation(Engine.ScreenModes.LANDSCAPE);
    }

    /**
     * Abstract load method called by engine.
     */
    public void load() {
        Log.d("Game","Game.load");
        zombie = new Texture(this);
        if (!zombie.loadFromAsset("zombie.png")) {
            super.fatalError("Error loading zombie");
        }
    }

    /**
     * Abstract draw method called by engine.
     */
    public void draw() {
        Log.d("Game","Game.draw");
        paint.setColor(Color.WHITE);
        canvas = super.getCanvas();

        //draw zombie bitmap
        canvas.drawBitmap(zombie.getBitmap(), 10, 300, paint);

        tp.setCanvas(canvas);
        tp.draw("First Engine Demo", 10, 20);

        if (super.getTouchInputs() > 0) {
            tp.draw("Touch inputs: " + super.getTouchInputs());
            for (int n=0; n<super.getTouchInputs(); n++) {
                String s = " " + n + ": " + super.getTouchPoint(n).toString();
                tp.draw(s);
                Point p = super.getTouchPoint(n);
                if (p.x != 0 && p.y != 0)
                    canvas.drawCircle(p.x, p.y, 50, paint);
            }
        }
        if (timer.stopwatch(500)) {
            super.drawText("**TIMER**", super.getCanvas().getWidth()/2, 20);
        }
    }

    /**
     * Abstract update method called by engine.
     */
    public void update() {
        Log.d("Game","Game.update");
    }
}


By the Way

It is not necessary to use super. in front of public properties and methods inherited from a parent class, but I’ve used it in these early examples to self-document the engine code to assist with understanding how the engine works. You can also use this. in the same manner or not use any prefix pointer at all.


Logging the Engine Demo

You may have noticed a lot of Log statements in the engine and game code. That is pretty normal when developing a new engine because you need to keep an eye on how it is running from both a large-scale perspective (for the benefit of anyone who will use your engine) as well as from a close-up perspective (for your own debugging needs). Perusing the log output of the engine running reveals some very interesting truths about how an Android program works.

For instance, notice how well the engine handles the situation where the surface is being created (asynchronously) while the game loop continues to run. The first four instances of Game.update printed in the log are missing their corresponding Game.draw output lines. This tells me that the surface was not yet ready when the loop began running, but updating was still taking place. This happened for four cycles! Very interesting, indeed!

Note also the ordering of the logged events. First, the Engine constructor runs, followed immediately by the Game constructor. Then we see a block defined by Engine.onCreate start and Engine.onCreate end, and between these two lines, a call to Game.init and Game.load. If you study the source code for the test project, you’ll see that init() and load() are indeed called by onCreate(), and so on. But after these processes are understood and seem to be working reasonably well—and without bugs—some of the log output can be removed. After the engine is working well, you will want to pay more attention to critical processes such as loading assets, and the game loop is no longer of real concern.

05-25 14:11:53.030: D/Engine(28105): Engine constructor
05-25 14:11:53.030: D/Game(28105): Game constructor
05-25 14:11:53.030: D/Engine(28105): Engine.onCreate start
05-25 14:14:01.740: D/Game(28167): Game.init
05-25 14:11:53.050: D/Game(28105): Game.load
05-25 14:11:53.050: D/Engine(28105): Engine.onCreate end
05-25 14:11:53.050: D/*(28105): Engine.onResume
05-25 14:11:53.050: D/Engine(28105): Engine.run start
05-25 14:11:53.050: D/Game(28105): Game.update
05-25 14:11:53.080: D/Game(28105): Game.update
05-25 14:11:53.100: D/Game(28105): Game.update
05-25 14:11:53.120: D/Game(28105): Game.update
05-25 14:11:53.140: D/Game(28105): Game.update
05-25 14:11:53.190: D/Game(28105): Game.draw
05-25 14:11:53.220: D/Game(28105): Game.update
05-25 14:11:53.240: D/Game(28105): Game.draw
05-25 14:11:53.260: D/Game(28105): Game.update
.
.
.
05-25 14:11:59.020: D/Game(28105): Game.draw
05-25 14:11:59.040: D/Game(28105): Game.update
05-25 14:11:59.040: D/Game(28105): Game.draw
05-25 14:11:59.060: D/Game(28105): Game.update
05-25 14:11:59.070: D/Game(28105): Game.draw
05-25 14:11:59.090: D/Game(28105): Game.update
05-25 14:11:59.100: D/Game(28105): Game.draw
05-25 14:11:59.120: D/Game(28105): Game.update
05-25 14:11:59.120: D/Game(28105): Game.draw
05-25 14:11:59.130: D/*(28105): Engine.onPause
05-25 14:11:59.150: D/Engine(28105): Engine.run end

Summary

The Android game engine has been created! Admittedly, it’s in a very simple and crude state at this point, but it has huge potential because the core is working perfectly, and important support classes and methods are already available. We will want to add quite a bit more to the engine in time, including audio (which was notably missing, but not a critical component at this early stage). The very next hour adds sprite functionality to the engine and a complete game example to put the engine to the test in a real-world situation.

Q&A

Q. The game engine project could potentially grow to include virtually unlimited new features without clogging game projects that derive from it. How does this approach to engine building differ from what you might have used or learned about in how other products work (for instance, Unity)?

A. Answers may vary, but a key difference is that some engines are supplied only in binary form without source code.

Q. What are some of the pros and cons of having a separate engine library project versus just putting all the source code files in a single project?

A. Answers will vary.

Workshop

Quiz

1. What method does the Timer class use to get the number of milliseconds from the system?

2. What is the name of the engine support class used to load bitmap assets?

3. What are the four abstract classes that must be implemented by any class that derives from game.engine.Engine?

Answers

1. System.currentTimeMillis()

2. Texture

3. init(), load(), draw(), and update().

Activities

See what you can do with the engine already after this initial first version! Try modifying the program so that it draws the zombie image in place of the “finger markers” (white circles).

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

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