Friday, April 5, 2013

OpenGL 1.0 - Part 2 - Simple Android Games

Introduction
Here we try to do in JNI what we accomplished in java in part 1 of this howto. Then we're going to try to connect our game code to it. By this I mean show some basic functionality, specifically showing motion on the screen.

Package Name
The package name for our project is 'com.example.testapp'. We've noted this before, but at this stage we start writing JNI, so it becomes particularly important.

Organization of JNI
So far we've compiled simple JNI files. We've installed the android NDK package, written our source code files and a makefile, and compiled the code. All our game code has been in two files. One was called 'awesomeguy.h' and one was called 'awesomeguy.c'. Now we're going to add all our OpenGL code to a third file called 'androidgl1.c'. Then we're going to rename our 'h' file to 'androidgl.h'. Strictly speaking we don't need to rename anything, certianly not our header file, but at some point I made the change, and so now I'm going to stick with it for these explanations. Because we have more source code files, our makefile has changed also. In the actual game I am going to try to keep the game code in the 'awesomeguy.c' file, with almost no regard to how large the file gets. The only code that I've separated to a different file is the OpenGL code. For this howto I will change the name of the 'awesomeguy.c' file to 'testapp.c'.

Makefile - Android.mk
Here I have included the new 'Android.mk' file. It includes all the newly renamed files.

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := testapp
LOCAL_CFLAGS    := -Werror 
LOCAL_SRC_FILES := testapp.c androidgl1.c
LOCAL_LDLIBS    := -llog -lGLESv1_CM

include $(BUILD_SHARED_LIBRARY)


Notice the line that starts 'LOCAL_LDLIBS := -llog -lGLESv1_CM' . This line tells the NDK tools to add the OpenGL ES version 1 libraries to the project.

Includes
Here I have included the new list of 'include' statements.

#include < jni.h >
#include < android/log.h >
#include < stdio.h >
#include < stdlib.h >

#include < GLES/gl.h >
#include < GLES/glext.h >
Notice that the angle brackets in this listing are separated from the library that they enclose by a single space. This space should be removed for your project.

'testapp.c' file
Here is our first listing for the 'testapp.c' file. The package name for this project is 'com.example.testapp'. We are working in eclipse. The native methods in the java classes will be called JNIinit(), JNIdraw(), and JNIresize(int w, int h).

#include "androidgl.h"
// C methods go here
 
JNIEXPORT void 
JNICALL Java_com_example_testapp_Panel_JNIinit(JNIEnv * env, jobject  obj)
{
    
        init();
    
}

JNIEXPORT void 
JNICALL Java_com_example_testapp_Panel_JNIdraw(JNIEnv * env, jobject  obj)
{

        draw();
    
    
}

JNIEXPORT void 
JNICALL Java_com_example_testapp_Panel_JNIresize(JNIEnv * env, jobject  obj, jint w, jint h)
{
        resize(w,h);
    
}


This is basically a listing of the methods we use in our JNI -- Java Native Interface. As this listing shows these JNI methods just call the C methods 'draw()', 'init()', and 'resize(int w, int h)'. We will introduce these methods later in this HOWTO.

'androidgl1.h' file
Since the androidgl1.h file is included we will give a listing for it here.

#ifndef ANDROIDGL_H
#define ANDROIDGL_H


#include < jni.h >
#include < android/log.h >
#include < stdio.h >
#include < stdlib.h >
#include < math.h >
#include < pthread.h >
#include < time.h >


#include < GLES/gl.h >
#include < GLES/glext.h >

// include loge and logi definitions
 
#define  LOG_TAG              "testapp-jni"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

#define TRUE 1
#define FALSE 0 

#define TEX_WIDTH   256
#define TEX_HEIGHT  256

#define TEX_DIMENSION    256
#define BOOL int

#define MY_SCREEN_FRONT 0
#define MY_SCREEN_BACK  1


//////////////////////////////////////////////////////
// function prototype: room for testapp.c
//////////////////////////////////////////////////////

//////////////////////////////////////////////////////
// function prototype: androidgl1.c
//////////////////////////////////////////////////////

void init() ;

void draw();

void resize(int w, int h);


#endif


There is plenty of room in the 'h' file for more method prototypes. GLuint is a special kind of int defined in the OpenGL library. The other important int in this listing is the 'uint16_t' . This is shorthand for 'unsigned int with 16 bits'. All the pixels in our textures will use this type. Later on we will use screen_0 and screen_1 as our textures, so these arrays need to be defined in terms of 'uint16_t' variables.

At the very bottom of our listing you can see the part that's interesting to us right now. It's the 'init()', 'draw()', and 'resize(int w, int h)' prototypes.

androidgl1.c - init() Method
Here we have a listing of the C code in the androidgl1.c file for the init() method.

void init(void)
{
    int tex_width = TEX_WIDTH;
    int tex_height = TEX_HEIGHT;

    glShadeModel(GL_SMOOTH);
    glClearDepthf(1.0f);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    
}


In some ways this is simpler than java because we don't have to convert our floats to byte buffers.

androidgl1.c - draw() Method
Here is the draw() method.

void draw() {

    int tex_width = TEX_WIDTH;
    int tex_height = TEX_HEIGHT;

    glClearColor((float) 0.0, (float) 0.5, (float) 0.0, (float) 0.5);

//    glTexImage2D(GL_TEXTURE_2D, 0,
//            GL_RGBA,//
//            tex_width, tex_height,
//            0,
//            GL_RGBA,//
//            GL_UNSIGNED_SHORT_4_4_4_4,//
//            getScreenPointer(MY_SCREEN_FRONT));//screen


    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnableClientState(GL_VERTEX_ARRAY);

    glFrontFace(GL_CCW);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
    glEnable(GL_TEXTURE_2D);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glTexCoordPointer(2, GL_FLOAT, 0, tex_coords);

    glVertexPointer(3, GL_FLOAT, 0, vertices);
    glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    glDisable(GL_CULL_FACE);

    glLoadIdentity();
    glTranslatef(0,0, - 1.25f) ;

    glFinish();
    glFlush();
    
}


The code is much the same as the code we wrote solely in java. There is some reorganization though. Note that the 'glTexImage2D()' method is commented out. We will use this method later when we have something to use as a texture to display on our one surface. We have included the 'glClearColor()' command to show a green background. This is not strictly necessary, as a black background is usually sufficient. Green does contrast the red background we used before.

androidgl1.c - resize(w,h) Method
This is the method where we stretch the texture over the vertices.

void resize(int w, int h) {

    float w_h_ratio = (float) w/ (float) h; // specifically for vertices
    float h_w_ratio =  3.0f/  4.0f; // specifically for texture
    
    // vertices array
    vertices[0] =  - (w_h_ratio / 2.0f) ;
    vertices[1] = 0.5f; 
    vertices[2] = 0.0f;  // 0, Top Left
    
    vertices[3] =  - (w_h_ratio / 2.0f) ;
    vertices[4] = -0.5f; 
    vertices[5] = 0.0f;  // 1, Bottom Left
    
    vertices[6] = (w_h_ratio / 2.0f) ;
    vertices[7] = -0.5f; 
    vertices[8] = 0.0f;  // 2, Bottom Right
    
    vertices[9] = (w_h_ratio / 2.0f) ;
    vertices[10] = 0.5f; 
    vertices[11] = 0.0f;  // 3, Top Right
    
    // texture coordinates array
    tex_coords[0] = 0.0f;
    tex_coords[1] = 0.0f; //1
    
    tex_coords[2] = 0.0f; 
    tex_coords[3] = h_w_ratio; //2
    
    tex_coords[4] = 1.0f; 
    tex_coords[5] = h_w_ratio; //3
    
    tex_coords[6] = 1.0f; 
    tex_coords[7] = 0.0f; //4
    
    
    glViewport(0, 0, w, h);    
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    //gluPerspective( 45.0f, (float) w/ (float) h, 0.1f, 100.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glGenTextures(1, &texture_id);

    glBindTexture(GL_TEXTURE_2D, texture_id);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    screen_width = w;
    screen_height = h;
}


This method is longer. We commented out the method 'gluPerspective()'. It turns out there is no 'gluPerspective()' in the OpenGL ES library for Android phones. To get around that we will introduce our own version of 'gluPerspective()'. We will do that later though. For now we want to compile some code that will run and demonstrate that we can use openGL in C.

Compile
To compile this code we type the following.

$ cd ~/workspace/testapp
$ ndk-build
 


Then you refresh the workspace from within eclipse by pressing the F5 key.

JAVA Native Methods
Here is a partial listing from the Panel.java class. All the code that we wrote before for the java OpenGL test with the red background has been removed. Now there is only the JNI C code manipulating the OpenGL library.

public class Panel implements GLSurfaceView.Renderer {

    Context mContext;
    
    public Panel(Context context) {
        mContext = context;
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        this.JNIdraw();

    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        this.JNIresize(width, height);

    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        this.JNIinit();

    }

    public native void JNIinit();
    public native void JNIdraw();
    public native void JNIresize(int w, int h);
    
    static {
        System.loadLibrary("testapp");
    }
}


This shows the library included in the class.

Screen Shot
Here is a screen shot of the running app on the emulator.



The green section is the background color that we defined using the glClearColor() command. The white section is the object that we've defined in 3D in the 'vertices' array. This object appears as proportional to the window that we create it in. If we resize the GLSurfaceView (the java class 'PanelGLSurfaceView.java') this object will always resize itself to be proportional to the view. Below is the code for the gluPerspective() call.

static void gluPerspective(GLfloat fovy, GLfloat aspect,
               GLfloat zNear, GLfloat zFar)
{
    #define PI 3.1415926535897932f
    GLfloat xmin, xmax, ymin, ymax;

    ymax = zNear * (GLfloat)tan(fovy * PI / 360);
    ymin = -ymax;
    xmin = ymin * aspect;
    xmax = ymax * aspect;

    glFrustumx((GLfixed)(xmin * 65536), (GLfixed)(xmax * 65536),
               (GLfixed)(ymin * 65536), (GLfixed)(ymax * 65536),
               (GLfixed)(zNear * 65536), (GLfixed)(zFar * 65536));
    #undef PI
}


This code is placed in the 'androidgl1.c' file below the include command and above the other previously listed functions. This helps us display the texture as we did in the Java code.

Here is a link for the gluPerspective() command: http://stackoverflow.com/questions/6289970/failed-to-execute-this-c-code-on-android .

Now the screenshot should look like this:



New Texture
Here we're going to start using a new texture. What we've done is resized the old one to 256x256. We've also superimposed some text on the texture. We will use the JNI to display the texture, and we will cut it off at the three-quarter mark. We will be able to see that it is cut off at this point by reading the superimposed text. Here is the new image. The alpha in the image that was part of the texture we used in part 1 of this howto is replaced here with black. We do not try to display red or blue at this time.



Panel.java Constructor
We load the texture into the program in the constructor of the Panel.java class. This constructor has the application context passed to it. The code for the constructor is shown below.

    public Panel(Context context) {
        
        mContext = context;
        
        int [] a = new int [256 * 256];
        BitmapFactory.Options mOptions = new BitmapFactory.Options();
        mOptions.outHeight = 256;
        mOptions.outWidth = 256;
        mOptions.inScaled = false;
        mOptions.inPreferredConfig = Bitmap.Config.ARGB_4444;
        Bitmap mTexture;
        mTexture = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.texture, mOptions);
        mTexture.getPixels(a, 0, 256, 0, 0, 256, 256);
        this.JNIsetTexture(a);
        
    }


testapp.c - JNIsetTexture() Method
Then we change the JNI. We need a method 'JNIsetTexture()'. Before we do that we add the following lines to the top of 'testapp.c' . We could have put these lines in our androidgl.h file. The static declaration allows the c code to access the arrays anywhere from inside the .c file in which they are declared. This means any function in the testapp.c file can access these arrays. If we include these lines in the header file, there will be essentially two arrays for each name, six in all, three of which can be accessed from one file (testapp.c) and three of which can be accessed from the other file (androidgl1.c). We need to pass the address of an array -- whichever one we choose -- to the OpenGL code in the androidgl1.c file. To accomplish this we use the 'getScreenPointer()' method. We define this method in the testapp.c file, and we list it shortly.

static uint16_t screen_0 [TEX_DIMENSION][TEX_DIMENSION];
static uint16_t screen_1 [TEX_DIMENSION][TEX_DIMENSION];
static uint16_t screencounter = 0;

static uint16_t tex_array [TEX_DIMENSION * TEX_DIMENSION];


Then we can define the following JNI function. Here we've setup three ways of storing the texture data. We will load all three arrays with the texture data, then we will use the three devices separately. In the end we will try to implement a double buffering scheme to show how that can be done. For the time being we will only use one of the arrays listed above at a time. The screencounter variable will be used in the double buffering code.

JNIEXPORT void
JNICALL Java_com_example_testapp_Panel_JNIsetTexture(JNIEnv * env, jobject  obj, jintArray a_bitmap)
{

  jint *a = (*env)->GetIntArrayElements(env, a_bitmap, 0);

  copyArraysExpand_1D(a, tex_array);
  copyArraysExpand_2D(a, screen_0);
  copyArraysExpand_2D(a, screen_1);
}


testapp.c - copyArraysExpand() Method
s This code goes in the testapp.c file. We will have to add a method signature to the androidgl.h file so that the compiler can find these two methods when the time comes. The complete androidgl.h file will be shown in a later listing.

void copyArraysExpand_2D(jint from[],  uint16_t to[TEX_HEIGHT][TEX_WIDTH]) {

    int ii,jj, k;
    for (ii = 0; ii< TEX_HEIGHT; ii ++ ) {
        for (jj = 0; jj < TEX_WIDTH; jj ++ ) {
            k =( jj * TEX_WIDTH ) + ii;
            to[jj][ii] = (uint16_t) from[k] ;
        }
    }
    return;
}

void copyArraysExpand_1D(jint from[],  uint16_t to[ TEX_WIDTH * TEX_HEIGHT]) {

    int j;
    for (j = 0; j< TEX_WIDTH*TEX_HEIGHT ; j ++ ) {

        to[j] = (uint16_t)  from[j] ;
    }
    return;
}


Then we have the code to actually hold the texture in memory.

This is the 'getScreenPointer()' method in its first incarnation.

uint16_t **  getScreenPointer(int screen_enum) {
    return (uint16_t **) screen_0;
}


It just returns the value of the address of the array that we are interested in. Right now it returns the address of screen_0. To return the address of screen_1 or tex_array, simply do the replacement in this code. All three should work, even though the screen_0 and screen_1 are 2 D arrays and the tex_array is a 1 D array. The method prototype for this method must be added to the listing of prototypes in the androidgl.h file.

glTexImage2D()
This is the new 'draw()' method. The only modification is that 'glTexImage2D()' has been included. This is part of the 'androidgl1.c' file.

void draw() {

    int tex_width = TEX_WIDTH;
    int tex_height = TEX_HEIGHT;

    glClearColor((float) 0.0, (float) 0.5, (float) 0.0, (float) 0.5);

    glTexImage2D(GL_TEXTURE_2D, 0,
            GL_RGBA,//
            tex_width, tex_height,
            0,
            GL_RGBA,//
            GL_UNSIGNED_SHORT_4_4_4_4,//
            getScreenPointer(0));//screen

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnableClientState(GL_VERTEX_ARRAY);

    glFrontFace(GL_CCW);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
    glEnable(GL_TEXTURE_2D);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glTexCoordPointer(2, GL_FLOAT, 0, tex_coords);

    glVertexPointer(3, GL_FLOAT, 0, vertices);
    glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    glDisable(GL_CULL_FACE);

    glLoadIdentity();
    glTranslatef(0,0, - 1.25f) ;

    glFinish();
    glFlush();
    
}


This method uses 'getScreenPointer()'.

Screen Shot
In this screen shot the texture is used. The dimension of the shown image is not 256x256 but 256x192. This is the dimension of the game screen for this game. The image is stretched, so displaying a window on the phone's screen that has the right ratio of width-to-height is the job of the Java code, but since the GLSurfaceView is a view, setting that size shouldn't be too hard.



Movement
Next we try to move an object on the screen. In this test app we want to redraw everything in C, but we want that redraw to happen when it is called for by java code. We want, on the other hand, for all our images to be displayed on the screen by OpenGL as soon as the phone is able to do so. We have two program threads, one where we are drawing things and another where we are showing them to the user. You could say that we are 'drawing' when we show the images to the user, but for this test app the distinction is splitting hairs. The OpenGL harware does this drawing, so we won't worry about that kind of drawing.

testapp.c - JNIbuildLevel() Method
In the next code snippets we will show how to draw on the screen in C with a method we'll call 'JNIbuildLevel()'. This method takes one parameter, the x position of the object. It then calls another method to draw the object on the array that will end up being our texture.

JNIEXPORT void 
JNICALL Java_com_example_testapp_Panel_JNIbuildLevel(JNIEnv * env, jobject  obj, jint a)
{
 int ii = 0;

 uint16_t  *  screen = (void *) (getScreenPointer(MY_SCREEN_BACK));

 for (ii = 0; ii < TEX_WIDTH*TEX_HEIGHT; ii ++ ) {
  screen [ii] = tex_array[ii];
 }
 drawBox(a);

    ////////////////////////
    incrementScreenCounter();
}


testapp.c - drawBox() Method
Then we need a method called 'drawBox()'. Both these methods are placed in 'testapp.c'.

void drawBox(int x ) {
    uint16_t  *  screen = (void *) (getScreenPointer(MY_SCREEN_BACK));
    int xx = x;
    int yy = 96;
    int ii = 0;
    int jj = 0;
    uint16_t color = 0x00f0;//blue

    for ( ii = 0; ii < 10; ii ++ ) {
        for (jj = 0; jj < 10; jj ++ ) {
            screen[((yy+jj) * TEX_WIDTH ) + xx + ii] = (uint16_t) color;
        }
    }

}


Then we add a method prototype to our header file, and a listing for JNIbuildLevel() in the 'native' section of our Panel.java class.

//////////////////////////////////////////////////////
// function prototype: room for testapp.c
//////////////////////////////////////////////////////

void incrementScreenCounter() ;

uint16_t **  getScreenPointer(int screen_enum) ;

void drawBox(int a);
Here is the relevant snippet from Panel.java:
    public native void JNIinit();
    public native void JNIdraw();
    public native void JNIresize(int w, int h);
    
    public native void JNIsetTexture(int [] a);
    public native void JNIbuildLevel(int a);
    static {
        System.loadLibrary("testapp");
    }


Then we need to call the code from java from another thread.

Panel.java Constructor
Here is the new constructor for the Panel.java class:

    public Panel(Context context) {
        
        mContext = context;
        
        int [] a = new int [256 * 256];
        BitmapFactory.Options mOptions = new BitmapFactory.Options();
        mOptions.outHeight = 256;
        mOptions.outWidth = 256;
        mOptions.inScaled = false;
        
        mOptions.inPreferredConfig = Bitmap.Config.ARGB_4444;
        
        
        Bitmap mTexture;
        mTexture = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.texture, mOptions);

        mTexture.getPixels(a, 0, 256, 0, 0, 256, 256);
        this.JNIsetTexture(a);
        
        new AsyncTask < Void, Void, Void > (){
            @Override
            protected Void doInBackground(Void... params) {
                while( mLoop == true) {
                    for (int ii = 0; ii < 256 - 10 ; ii = ii + 5 ) {
                        gameSpeedRegulator();
                        JNIbuildLevel(ii);
                    }
                }
                return null;
            }
        }.execute();
        
    }


Panel.java - gameSpeedRegulator() Method
And here is a helper method that is used in the constructor. It's also part of Panel.java. This method helps time the calls to 'JNIbuildLevel()'. This way the calls to the JNI function are spaced out and we can be sure that we see all of the drawn blue squares:

    boolean mLoop = true;
    private long framesPerSec = 15;
    private long skipTicks = 1000 / framesPerSec;
    private long ticksElapsed; 
    private long sleepTime = 0;
    private long nextGameTick = 0;
   
    public boolean gameSpeedRegulator() {
        
        boolean mIsNotLate = true;
        
        Date newDate = new Date();
        ticksElapsed = newDate.getTime();
        nextGameTick += skipTicks;
        sleepTime = nextGameTick - ticksElapsed;
        
        if ( (sleepTime >= 0 && mLoop) || framesPerSec < 0 ) {
        
            //Log.e("Panel GameLoop", "---Passing time");
            try {
                if (framesPerSec > 0) Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                //
            } 
            mIsNotLate = true;
        }
        else {
            //Log.e("Panel GameLoop", "---Running behind");
            newDate = new Date();
            nextGameTick = newDate.getTime();
            mIsNotLate = false;
        }
        return mIsNotLate;
    }
   


next we redefine the getScreenPointer() method.

testapp.c - getScreenPointer() Method
Here is the new getScreenPointer() class.

uint16_t **  getScreenPointer(int screen_enum) {

    //////// BUFFER SCREEN PART I ///////////////////
    int local_index = 0;
    if (screen_enum == MY_SCREEN_FRONT) {
        local_index = screencounter;
    }
    else if (screen_enum == MY_SCREEN_BACK) {
        local_index = (screencounter + 1) &1;
    }
    ///////// BUFFER SCREEN PART II /////////////////
    if (local_index) {
        return (uint16_t **) screen_0;
    }
    else {
        return (uint16_t **) screen_1;
    }
}


This is C code, part of 'testapp.c'. It is our framebuffer. When we want to draw to the screen we call 'getScreenPointer(MY_SCREEN_BACK)'. When we want to pass a pointer as the texture address, we call 'getScreenPointer(MY_SCREEN_FRONT)'. Whenever we come to the point that we want to finish drawing on the screen and move to the next frame we call 'incrementScreenCounter()'. This way we can draw what we want and there's no tearing.

testapp.c - incrementScreenCounter() Method
This is the code for 'incrementScreenCounter()'.

void incrementScreenCounter() {
    screencounter = (screencounter + 1)& 1;

}


This is it. What we've tried to do is show movement on the screen with a small blue box that moves across. There is no screen tearing because of the frame buffering.

Here is a screen shot.



Most people won't find this outcome particularly interesting. Still there's no tearing.

Problems and Special Considerations
On the emulator this works as shown. On the device that I own the texture is repeated on the screen twice in the 'left-to-right' direction. The texture stretches as it should in the 'top-to-bottom' direction. This should not effect someone who is trying to make a game where all the backgrounds are drawn on the 'screen' variable by code. To stop the 'left-to-right' repeating set the Java line with the line 'Bitmap.Config.ARGB_4444' to 'Bitmap.Config.ARGB_8888' as noted below.

Note, my texture only uses the colors green, black, and white. If red or blue are added to the initial 'texture.png' file the outcome is unimpressive. Those colors don't show up properly from the original ping. If you want to add objects with those colors later in JNI code, this works fine, but not from the original ping.

The problem is rooted in the fact that the android phone uses a native bitmap configuration of 'ARGB_8888'. This means the bitmap word is 32 bits long and Alpha, Red, Green, and Blue data each use up 8 bits. It also means that they are ordered Alpha first, then Red, Green, and finally Blue. The OpenGL context in our example uses 'RGBA_4444'. Obviously there's a difference. You can specify, as we did in our Java, that the system use 'ARGB_4444' when it decodes the bitmap, but there is no option for 'RGBA_4444' as the OpenGL uses, and though there are other options for both OpenGL ES and Android, there's not one set of them that match. You could use C code to shift around the bits in the 'screen' array in the JNI, but this is pretty messy.

For our purposes simply constructing our own content is enough. As I have mentioned the blue square shows up in the right color because it follows the 'RGBA_4444' arrangement of the OpenGL ES code.

Still, as I want my HowTo to be iron clad, I will work on this and possibly edit this document later to show how to preserve the 'left-to-right' texture as having only one repitition and have the colors correct.




What follows is the final screenshot. In this screenshot the 'Bitmap.Config.ARGB_4444' is removed or replaced with 'Bitmap.Config.ARGB_8888'. The colors are incorrect but the size is fine. Again, if you were writing a game where the background were painted entirely by code, this would not be a problem. If you look you can see the blue box is correctly displayed.



Here is the line in the Panel.java constructor that needs to be changed or removed.

        BitmapFactory.Options mOptions = new BitmapFactory.Options();
        mOptions.outHeight = 256;
        mOptions.outWidth = 256;
        mOptions.inScaled = false;
        
        mOptions.inPreferredConfig = Bitmap.Config.ARGB_4444;
        // change or remove this line ^^^


Finally there may be a way to load textures from Java directly, but I have not explored this.

OpenGL 1.0 - Part 1 - Simple Android Games

Introduction
We want to speed up awesomeguy so that game play will be more enjoyable. We're not going to address other aspects of gameplay that might make the game less enjoyable. We are not, for example, going to change the theme of the game, or the layout of any particular level. We just want to look at the speed.

A version of the game was written without JNI. No C code was used. This version was slow since all the drawing of sprites and tiles had to be done by java. Java doesn't draw fast. Java Native Interface, the C code that we wrote, speeds up that drawing particularly. Before game play each of the sprites and all of the tiles are loaded into memory that the C code maintains and has access to. During the course of the game the JNI composes the image that is to be displayed on the screen. This happens faster than it would if solely java was responsible for the drawing. Then the C code passes the composed image back to the java code, where java methods are responsible for putting it on the screen. This method, then, became the bottle neck. This method took a lot of time.

OpenGL
What we want to do instead is to use OpenGL to show the user the composed image -- the game screen -- and avoid the two method calls that slow the game down. We want to avoid the call that transfers the array of pixels from the JNI code to the java code, and we want to avoid the call to the java method to print the array from the JNI on the screen. We feel we can do this with OpenGL. Most Android phones have some version of OpenGL, and it is implemented in hardware. It is designed to draw 3D graphics to the screen. It also is accessable to the C programmer, and it works independently of the processor that the user interacts with on the user interface thread.

We're not going to discuss how OpenGL really does the drawing that we are doing on the screen. What we want to do here is give a recipe for drawing to the screen, not really how the phone does that drawing. It is beyond the scope of this tutorial to show how depth is drawn by the OpenGL system, for example. Links on this page will show the reader more in this regard than this tutorial.

OpenGL ES Versions
There are several versions of OpenGL for the android phone, version 1.0, 1.1 and 2.0. We'll focus on version 1.0 It does everything we need and it's available on most phones, while version 2.0 is not available on as many. Version 1.0 is also good for us because it's less complicated and you can get programs written with version 1.0 to run on the Android emulator. This is something you cannot do with version 2.0, which has to have all code written for it tested on an Android device. This makes development with OpenGL version 1.0 easier to test. Our needs are simple. I believe that for complex tasks version 2.0 is faster, but we don't need its complexity.

For some background on OpenGL see this link: http://developer.android.com/guide/topics/graphics/opengl.html . Another good page for those who want to jump ahead into OpenGL in the Android in Java is here: http://www.jayway.com/2009/12/03/opengl-es-tutorial-for-android-part-i/ . This second site is used extensively.

Java Example
What we'll do here is explain how to set up OpenGL to do this. We'll even touch on setting up OpenGL in Java. This may be helpful on another project. I have also found it helpful to set up OpenGL in java, and then once it is working, then I know that my Android settings are right. Then I copy each of the methods to the JNI environment. I've found this is good for debugging the various Android settings that are required before you can 'turn on' OpenGL.

To start off lets look at the Android Manifest file. You must add this line:

 < uses-feature android:glEsVersion="0x00010000" android:required="true" /> 
This declaration tells the phone that you will be using OpenGL ES version 1.0. Of course in your actual manifest file the above text must be enclosed in angle brackets in the style of regular XML.

Now we need several classes that will be used for displaying our OpenGL content. This is fairly well documented. We need a 'GLSurfaceView' and a 'GLSurfaceView.Renderer'. A 'GLSurfaceView' is a View. To create one you 'extend' in java. A 'GLSurfaceView.Renderer' is a interface. To create one you 'implement' in java. The GLSurfaceView code is fairly simple. Most of the important stuff there is already part of the class that you are extending. The 'Renderer' code is more complex in that there are several methods that must be implemented. We use eclipse, and eclipse will create method stubs for unimplemented methods for us.

Here is an example of a GLSurfaceView. The GLSurfaceView is named 'PanelGLSurfaceView' for simplicity. You can see that the Renderer will be named 'Panel'.

import android.content.Context;
import android.opengl.GLSurfaceView;

public class PanelGLSurfaceView extends GLSurfaceView {
    Panel mPanel; // this is our renderer!!

    public PanelGLSurfaceView(Context context) {
        super(context);
        mPanel = new Panel(context);
 
        setRenderer(mPanel);

    }

}


Here is an example of what eclipse gives you for the 'GLSurfaceView.Renderer' interface. This is where the actual drawing goes on. The 'GLSurfaceView' does not now contain much. It's a view and can be included in layouts the way a view can. It's also a place to put code that responds to touching from the screen, but we're not going to detect touching from the screen in this example.

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView;

public class Panel implements GLSurfaceView.Renderer {

 public Panel(Context context) {
  // TODO Auto-generated constructor stub
 }

 @Override
 public void onDrawFrame(GL10 gl) {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void onSurfaceChanged(GL10 gl, int width, int height) {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void onSurfaceCreated(GL10 gl, EGLConfig config) {
  // TODO Auto-generated method stub
  
 }

}


Note: this code is not particularly useful at this stage because the overidden methods do not do anything. This is, though, the framework for what we do later on. The important methods are 'onDrawFrame()', 'onSurfaceChanged()', and 'onSurfaceCreated()'. Note that each of these methods has 'GL10 gl' passed to it. This is so that later when we need constants and enumerations for OpenGL, we can use the 'gl' variable to get at them in each of our methods.

Activity Class
The first thing we're going to do is get OpenGL working in Java. Then we'll proceed to implement it in JNI in C. At the start we want to make sure that the GLSurfaceView is included in another view that the user sees. For this example we'll add the GLSurfaceView to a Activity programmatically. There is a way to include the GLSurfaceView in a xml layout, but we're not going to explore that. This is an activity that will show our GLSurfaceView.

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

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        PanelGLSurfaceView mSurfaceView = new PanelGLSurfaceView(this);
        
        //setContentView(R.layout.activity_main);
        setContentView(mSurfaceView);
        
    }

}


Setting Screen Color
This is similar to how we would show our surface view with the actual game. Now, back in the renderer we make some changes. What we want to do is show that OpenGL is correctly configured for the app. Here's a modified version of the 'onDrawFrame()' method.

    @Override
    public void onDrawFrame(GL10 gl) {
        // these are two enumerations 'or'-ed together.
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT |  GL10.GL_DEPTH_BUFFER_BIT);
    }
And here we have a modified version of the 'onSurfaceCreated()' method.
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // This is the background color. RGBA
        gl.glClearColor(0.5f, 0.0f, 0.0f, 0.5f);  
        // Smooth Shading
        gl.glShadeModel(GL10.GL_SMOOTH);
        // This is a setting that effects how depth is shown
        gl.glClearDepthf(1.0f);
        
        // More cryptic settings that effect how depth is shown
        gl.glEnable(GL10.GL_DEPTH_TEST);
        gl.glDepthFunc(GL10.GL_LEQUAL);
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
    }


The 'onSurfaceCreated()' method is longer. Here the color that we want to paint all over the screen is red. The 'gl.glClearColor()' method takes 'red', 'green', 'blue', and 'alpha' values as it's parameters. With just these two methods implemented in this particular way we can tell if the app is configured properly. Here when we run the app there would be a red screen from this activity. This is does not explain what is happening in the phone to display the image. This explanation is not covered in this tutorial. If this code is compiled as shown here the activity in question will show a red screen. This is OpenGL displaying this screen and the Java that we've used to instruct the OpenGL to do this is mostly just passing information to the GL processor, and so the graphics here appear very fast, though you cannot tell at this time that they are any faster than any other means of display.

What we'll do next is set up a texture and display that texture.


Start Showing An Object
Here we start adding code that will later show up as essential to the JNI code that we are going to write. In the next code snippet we fill in part of the 'onSurfaceChanged()' method. We will be echoing this code in C later on.

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        
        // This is the viewport.
        gl.glViewport(0, 0, width, height);
        
        // these two lines clear the 'projection' matrix
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        
        // this line requires more import statements!!
        GLU.gluPerspective(gl, 45.0f, (float) width / (float) height, 0.1f, 100.0f);
        
        // these two lines clear the 'modelview' matrix
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
    }


Vertices
If you want to learn about 3D images, this might be a good place to start. Now we're going to define in the 'onDrawFrame()' method a set of vertices. These vertices form a wire-like frame which we'll later stretch a image over. This process is essentially what we'll be doing in JNI. The wire frame is in a float array called 'vertices'. Floats are floating point numbers, and they have a certian size. They also have a sign, which means they can be negative or posotive. The ones we'll use will define a square. The first float will be in the upper left hand corner and have coordinates of (-1.0, 1.0, 0.0). The second vertices is (1.0, 1.0, 0.0). The vertices are in the form (x, y, z) where z is always zero because all the points reside on the same plane at the z axis. This is not necessarily the case for all objects. The last two vertices are (-1.0, -1.0, 0.0) and (1.0, -1.0, 0.0). These four points will make the surface upon which we will display our game screen.

For Java we need to convert the array of vertices into ByteBuffers for OpenGL to use them. This is not as much of a restriction in C as it turns out. In C the vertices are sufficiently primitive to be passed to the hardware as-is. This is the modified 'onDrawFrame()' method.

    @Override
    public void onDrawFrame(GL10 gl) {
        // these are two enumerations 'or'-ed together.
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT |  GL10.GL_DEPTH_BUFFER_BIT);
        
        float vertices[] = {
                  -1.0f,  1.0f, 0.0f,  
                  -1.0f, -1.0f, 0.0f,  
                   1.0f, -1.0f, 0.0f,  
                   1.0f,  1.0f, 0.0f,  
        };

        
        ByteBuffer bb = ByteBuffer.allocateDirect(vertices.length * 4);
        bb.order(ByteOrder.nativeOrder());
        FloatBuffer vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);
    }


Each float is four bytes long.

Indices
Next we worry about indices. We use indices to define what order to view the vertices in. When we draw the vertices as a solid we use triangles made up of the vertices. These triangles are defined by the indices. You can tell the OpenGL code what order to view the triangles in by setting the winding. Winding can be clockwise or counter-clockwise. Some experimentation can be helpful here.

A plane, the basis for the 3D objects that OpenGL displays, can easily be defined using three points. Three points makes a triangle. What we do is define a whole set of points, and then define the triangles that make it up by establishing the order in which to consider them. The vertices are the mass of points and the indices allow us to show what order to follow to make triangles out of the points. You can even repeat vertices in the list of indices in order to clearly define out surfaces. What you must do, though, is to make on long list of indices that represents all the faces that you are interested in. The computer knows which side is 'out' by your choice of clockwise or counter-clockwise winding. We have four points (vertices) and they are arranged in two triangles that are side by side. We use a table of six indices to define our two triangles. Like the vertices, they need to be copied into ByteBuffers for Java to pass them to the OpenGL hardware.

For indices we'll use shorts. A short is sixteen bits. In the next code snippet we introduce enough code for a square to be drawn on the screen. We'll go over some of the code in the explanation afterword.

    @Override
    public void onDrawFrame(GL10 gl) {
        // these are two enumerations 'or'-ed together.
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT |  GL10.GL_DEPTH_BUFFER_BIT);
        
        gl.glLoadIdentity(); 
        gl.glTranslatef(0, 0, -4); 
        
        float vertices[] = {
                  -1.0f,  1.0f, 0.0f,  
                  -1.0f, -1.0f, 0.0f,  
                   1.0f, -1.0f, 0.0f,  
                   1.0f,  1.0f, 0.0f,  
        };

        
        ByteBuffer bb = ByteBuffer.allocateDirect(vertices.length * 4);
        bb.order(ByteOrder.nativeOrder());
        FloatBuffer vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);
        
        short[] indices = { 0, 1, 2, 0, 2, 3 };
        
        ShortBuffer indexBuffer;
        
        ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
        ibb.order(ByteOrder.nativeOrder());
        indexBuffer = ibb.asShortBuffer();
        indexBuffer.put(indices);
        indexBuffer.position(0);
        
        // CCW stands for 'counter-clockwise'
        gl.glFrontFace(GL10.GL_CCW); 
        
        gl.glEnable(GL10.GL_CULL_FACE); 
        gl.glCullFace(GL10.GL_BACK); 
        
        // name your vertex buffer
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0,  vertexBuffer);

        // name your index buffer
        gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_SHORT, indexBuffer);
        // disable things when you're done using them
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); 
        gl.glDisable(GL10.GL_CULL_FACE); 
        
        
    }


Showing The Square
To see the square we have to execute the two commands near the top of the listing. 'glLoadIdentity()' and 'glTranslatef()'. The 'load-identity' command puts the camera -- the imaginary camera responsible for perspective in this code -- in the starting position every time 'onDrawFrame()' is called. This method is called over and over again and is responsible for animation in 3D. The 'gl-translate' command moves the scene the camera is viewing in the negative-z direction. Without this command the square is at the exact same position that the camera is and therefore cannot be seen. You can try the code without these two lines to see how it works.



Picking A Texture
Textures can be the source of lots of problems with OpenGL. You have to use a square image, a ping, that has sides that are powers of 2. Here we used 128x128. Sometimes textures will even work on the emulator and not on the phone. You should test out your app on both ultimately. Below is our texture. To use it we made a folder called 'drawable' in the 'res' folder in our android project. We put the file, called 'texture.png', there. After refreshing the project in eclipse and building it once we can access the file.



Texture Coordinates
Texture coordinates are called 'u-v' coordinates. They are set up on a coordinate system like the x/y coordinates in an x/y plane. The only reason we don't call them 'x-y' coordinates is that the variables x,y, and z are already used. We use them in our vertices descriptions. U is like x, and refers to the horizontal component of the coordinate. V is like y, and refers to the vertical component of the coordinate. UV coordinates act as if they were xy coordinates in the first quadrant. This means that 0,0 is in the lower left. 1,1 refers to the upper right of the texture. 1,0 refers to the lower right and 0,1 rerers to the upper left. We specify one texture coordinate for each of our vertices.

Since UV coordinates are floats, you can specify values greater and less than 1. That 1 describes the edge of the texture, but you can stretch the texture into different shapes as it is applied to the vertices by specifying different values. Again, experimentation is useful here. When we write our C version of this code we will use this property to only display part of the texture on the screen. All textures are square and our game screen is not square.

Code Listing
The code for painting the texture on our square goes BEFORE the code to simply draw the shape on the screen. Our listing will have the 'onDrawFrame()' as before but the texture code will come first.

    @Override
    public void onDrawFrame(GL10 gl) {
        
        // start texture code... load bitmap
        Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.texture);
        
        // just one texture
        int[] texturesArray = new int[1];
        gl.glGenTextures(1, texturesArray, 0);
        
        // what to do if texture must be shrunk or magnified
        gl.glBindTexture(GL10.GL_TEXTURE_2D, texturesArray[0]);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
        
        float textureCoord[] = {
                0.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 1.0f,
                1.0f, 0.0f };
        
        // what to do at edge of texture (repeat in two directions)
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
        
        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
        
        FloatBuffer textureBuffer;        
        ByteBuffer byteBuf = ByteBuffer.allocateDirect(textureCoord.length * 4);
        byteBuf.order(ByteOrder.nativeOrder());
        textureBuffer = byteBuf.asFloatBuffer();
        textureBuffer.put(textureCoord);
        textureBuffer.position(0);
        
        // set texture for later drawing
        gl.glEnable(GL10.GL_TEXTURE_2D);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, texturesArray[0]);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
      
        // draw everything here that was at the beginning of the method

        gl.glClear(GL10.GL_COLOR_BUFFER_BIT |  GL10.GL_DEPTH_BUFFER_BIT);

        
        gl.glLoadIdentity(); 
        gl.glTranslatef(0, 0, -4); 
        
        float vertices[] = {
                  -1.0f,  1.0f, 0.0f,  
                  -1.0f, -1.0f, 0.0f,  
                   1.0f, -1.0f, 0.0f,  
                   1.0f,  1.0f, 0.0f,  
        };

        
        ByteBuffer bb = ByteBuffer.allocateDirect(vertices.length * 4);
        bb.order(ByteOrder.nativeOrder());
        FloatBuffer vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);
        
        short[] indices = { 0, 1, 2, 0, 2, 3 };
        
        ShortBuffer indexBuffer;
        ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
        ibb.order(ByteOrder.nativeOrder());
        indexBuffer = ibb.asShortBuffer();
        indexBuffer.put(indices);
        indexBuffer.position(0);
        
        // CCW stands for 'counter-clockwise'
        gl.glFrontFace(GL10.GL_CCW); 
        
        gl.glEnable(GL10.GL_CULL_FACE); 
        gl.glCullFace(GL10.GL_BACK); 
        
        // name your vertex buffer
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0,  vertexBuffer);

        // name your index buffer
        gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_SHORT, indexBuffer);
        
        // disable things when you're done using them
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); 
        gl.glDisable(GL10.GL_CULL_FACE); 
        gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
        gl.glDisable(GL10.GL_TEXTURE_2D);
    }


Some explanations follow.



This is a rather lengthy listing, but it shows that OpenGL ES can be used in Java for the Android phone. We have left out description of much of the code above. Some things deserve special mention. One thing is that the floats that make up the texture coordinates only have two dimensions. There are only U and V (no W). Also, these UV coordinates are converted to ByteBuffers so that Java can pass the values to the OpenGL engine.

The texture coordinate code is included before the code to actually draw the vertices. Also, most of the coordinates we see are based on the cartesian system, and so zero is in the center of the picture, or as in the case of texture coordinates, the lower left. There is only one texture coordinate for each vertices.

Java Import Listing
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
This is the list of imports for the Panel.java class.

Moving On
Next we'll try to do the same thing that we've accomplished here in C and connect it to the Android App with JNI.

Friday, January 18, 2013

New Release

RELEASE 1.0.0.20130118

This release, for Awesomeguy and Awesome-Flyer, allows the apps to report high scores to an on-line AppEngine database. This database service is free, but could be cancelled at any time by administrators if volume is too high or the service is misused. Also, note that individual scores are subject to being removed by admins if they are somehow objectionable.

The process of uploading scores from within the games is easy. First, make a game-name to use when you play the game. Then, when you are done playing, go to the 'HighScores' screen. This screen is reachable from the 'Players' screen MENU key. Then long-press the score that you want to upload and the game will ask you what you want to do. Choose 'Send Scores To Online List' and you will be asked for a account to proceed with. This is the only confusing part. You must choose a google account so that the game can use it to authenticate your message to the on-line database. Simply choose an account and the score will be sent to the Web.

You can view your scores on-line from the game by going to the 'Players' screen and choosing MENU, and then choosing 'Web Scores' from the items presented. From there you can also change which google account you are using to authenticate with. Bye the way, other players cannot see the value of your google email account.

After you choose a google email account to authenticate your scores, you will not be asked again unless you decide that you want to change which account you are using on your own.

Tuesday, May 29, 2012

Release 2012.05.28

Released new Awesomeguy today. The screen is displayed using openGL, so the game is faster, but the color scheme of the blocks on the screen has changed a little bit.

Wednesday, April 4, 2012

Release 1.0.0.20120403 and 1.0.0.20120404

On April 3 I uploaded an app to the market that was supposed to fix a bug. The bug was that when you started the game and pressed the back button immediately the app crashed. This was due to the fact that an Async task that ran the splash screen wasn't being killed on versions of Android like 2.2 . The update fixed the problem but introduced another one.

The new problem was that the external lib for Google Analytics wasn't being found in the game package when it was time for the Analytics code to run. It turned out that this was because the Eclipse android ADT was changed slightly when ADT 17 was released. I only updated my own version of the ADT a few days before I uploaded my game's apk. I was not aware of the change and in my haste I released something on the 3rd that didn't work.

This morning, April 4,  I fixed the problem (I think) and the game runs again. Sorry for the error.

Sunday, March 11, 2012

Release 2012.03.10

Released new Awesomeguy today.

Sunday, March 4, 2012

Android Update

I'm working on a new Android phone update. I don't know when it will be finished. Game play will mostly be the same. Major changes will effect the menu system and the High Score and Players lists. Hopefully the D-pad will be reworked and will operate better. There are no updates to the Nintendo DS version planned.