I considered using SDL for this port, however, I became concerned that none of the ports were quite ready for prime time. None of them have been really released as complete and stable packages, and there are enough weird things about Android that I think I’ll be better off doing the work directly myself. Galcon is an “OpenGLES” app in essence, so much of what SDL provides is unnecessary anyways. But if you are interested:
There is a summer of code project relating to this:
http://socghop.appspot.com/gsoc/student_project/show/google/gsoc2010/sdl/t127230762779
http://hg.libsdl.org/SDL-gsoc2010_android/summary
And there’s also another SDL port here:
http://www.anddev.org/code-snippets-for-android-f33/sdl-port-for-android-sdk-ndk-1-6-t9218.html
Step 1 – Using command line tools instead of the Eclipse IDE:
I also decided that avoiding Eclipse might be a good move. I’m sure there are those of you who would argue otherwise. But, err, I’m a CLI kind of guy, so we’ll see how it goes.
First I added ~/android/tools to my path, so that I wouldn’t have to keep typing in the whole path 🙂
In my Galcon android folder, I ran this command:
$ android create project -t “android-4” -k com.igalcon.galcon -a Galcon -n Galcon -p .
Which created my other project files.
Then to create a debug build of Galcon:
$ ant debug
Then to install it onto my device:
$ adb devices
List of devices attached
emulator-5554 device
deviceid device
$ adb -s install bin/Galcon-debug.apk
And the app is installed, and I can run it. However, at this point I’ve just got a Hello World thing showing up, so I’ve got a long way to go until I’ve got Galcon appearing 🙂
Step 2 – Including resources (graphics, audio) with your game:
There are a number of options as to how to bring data long for your game. A few are explained on this page:
http://developer.android.com/guide/topics/resources/providing-resources.html
I made a folder called “assets” and placed all my game assets in it. Instead of mp3s, I include ogg files. And instead of .wav, I include ogg files. On the Android handhelds, there is only 256 MB of internal storage. Many games just pack the binary and then download the data separately onto the users flash card. I deem that to be totally unawesome and will ship Galcon with all its data. But I will do my best to pack as little data into my binary as possible. By using oggs instead of mp3s and wavs, I’ve trimmed a couple MB off my data size.
UPDATE: App can request to be stored on the SD card! http://developer.android.com/guide/appendix/install-location.html
To do this, add android:installLocation=”preferExternal” to the manifest tag in AndroidManifest.xml .. And while you’re at it, add in <uses-sdk android:minSdkVersion=”4″ /> to require OpenGLES 1.1 support. Also change target=android-8 in default.properties. This will give a warning since your minSdkVersion and your target aren’t the same. Just read that link above for more info.
If you save your files in “res” folders, some kind of Android magic kicks in and it seems you can’t reference anything by a String. You have to use Java IDs, which makes it really hard for C code to say “get me file X” .
Files saved in the assets/ directory are not given a resource ID, so you can’t reference them through theR class or from XML resources. Instead, you can query files in the assets/ directory like a normal file system and read raw data using AssetManager.
At this point, I’ve got a basic project building, and I’ve got assets included in my package. (I can tell this because my package is 3MB, not 200k like a empty Hello World example.) I think 3MB is an acceptable size.
Step 3 – Debugging the game:
Next I need to figure out how to get debugging working. I’m pretty sure I’m going to have bugs, so .. doing on-device debugging will be ideal. Again, if I were to take the wimp’s way out, I’d use Eclipse (I guess? for me actually, Eclipse seems like a greater learning curve..) but I’ll be using CLI methods to debug. I’ve edited Galcon.java and added a Divide by Zero bug, so I can see how it appears. And upon installing it, it crashes.
As a side note, to replace an app you’ve already installed, this command:
$ adb -s install -r bin/Galcon-debug.apk
Error that appears on my device: “Sorry! The application Galcon (process com.galcon.igalcon) has supped unexpectedly. Please try again.”
To debug:
$ adb -s logcat
And I get to see a lovely dump of errors as I have them:
E/AndroidRuntime( 1960): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.galcon.igalcon/com.galcon.igalcon.Galcon}: java.lang.ArithmeticException: divide by zero
Here’s a link to a page that talks more about adb, getting a shell on the device, and other fun goodies:
http://developer.android.com/guide/developing/tools/adb.html
Step 4 – Getting Java to call C functions:
Now I’m going to try and get Java to call out to the C / C++ code I built yesterday. At this point I’m going to send a thanks out to James Brown (Ancient Frog) who lent me a few lines of his attempt at an Android port. There are lots of tutorials and documentation on-line for JNI stuff, but seeing real code that is in the ballpark of what I want to do is a huge help. I’ll be posting a few snippets as I go in this blog, but if you do want to really learn about JNI, there is a ton of information.
First of all, in Galcon.java, in my Galcon class, I’ve sprinkled a handful of references to native functions:
public native void init();
public native void loop();
public native void paint();
In onCreate() I’ve added a call to init().
And then I created a myjni.cpp file which I filled with templates like this. I love all the ludicrous namespacing. I’m not sure who it is helping, but I’m pretty sure it isn’t me.
JNIEXPORT void JNICALL Java_com_galcon_igalcon_Galcon_loop(JNIEnv* env) { /* do stuff here */ }
After building and attempting to run I get this error from logcat:
W/dalvikvm( 2168): No implementation found for native Lcom/galcon/igalcon/Galcon;.init ()
I definitely need to add myjni.cpp to my jni/Android.mk and re-run ndk-build.
Tip: if you want to do some logging to the Android message log:
#include
#define MYLOG(msg) __android_log_write(ANDROID_LOG_ERROR,"MYLOG()",msg);
Seems to do the trick.
I’m still getting the error, so I think my .so isn’t being loaded. I add this before I call init to load the shared library:
System.loadLibrary("igalcon2");
I get a NEW error:
E/AndroidRuntime( 2255): java.lang.UnsatisfiedLinkError: Library igalcon2 not found
I guess libigalcon2.so isn’t being packaged. Gotta check into this. An unzip -v of the package reveals that libigalcon2.so is being included in the package. But I found this error in logcat:
W/dalvikvm( 2338): JNI_OnLoad returned bad version (0) in /data/data/com.galcon.igalcon/lib/libigalcon2.so 0x449e1260
I guess I need to make sure I’m returning the right version in my JNI_OnLoad function. This is my function:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
MYLOG("JNI_OnLoad(): GetEnv failed");
return -1;
}
return JNI_VERSION_1_4;
}
That seemed to work fine. So I’ve got JNI up and running and talking to my C code. I was able to get my various JNI functions calling my Galcon functions with no trouble.
Step 5 – Setting up an OpenGL context in Java:
logcat reveals that I’m getting a TON of OpenGLES errors similar to: “E/libEGL ( 2463): call to OpenGL ES API with no current context (logged once per thread)” .. This is pretty self explanatory. Time to figure this one out.
This page seems to reveal how to set up an OpenGL context:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/GLSurfaceViewActivity.html
And this seems to be how to set up and OpenGL renderer:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/CubeRenderer.html
Sort of bodging those together got me pretty far. At least, I know my GLES code is being called and I’m not seeing errors.
Step 6 – Getting C to call a Java method (to load a texture):
I gotta get my texture images loading up so that my paint code is rendering something a bit more interesting to the screen. In this case instead of Java calling C, I need my C code calling Java code. So it’s like JNI in reverse. Or something. Here’s the documentation on doing just that:
http://java.sun.com/developer/onlineTraining/Programming/JDCBook/jniref.html#call
You’ll note that no matter how we do this, we gotta have access to an “env” or something. I’m going to set up some kind of awful global variable that will be available to all my code so that I can use “env” to do stuff like this. I put this at the top of my JNI calls:
JNIEnv *_my_jnienv = 0;
void set_jnienv(JNIEnv *env) { _my_jnienv = env; }
JNIEnv *env *get_jnienv() { return _my_jnienv; }
And added set_jnienv(env); to the top of each call. Now in my other “driver” code I can call get_jnienv() to get it back.
Here’s my dummy load_texture method on the Java side:
public static int load_texture(String fname) {
Log.v("load_texture",fname);
return 0;
}
Here’s my C function that calls it:
int jni_load_texture(const char *s) {
JNIEnv *env = get_jnienv();
jclass cls = env->FindClass("com/galcon/igalcon/Galcon");
jmethodID mid = env->GetStaticMethodID(cls,
"load_texture",
"(Ljava/lang/String;)I");
// there could be some exception handling happening here, but there isn't
jint ret;
jstring mystr = env->NewStringUTF(s);
ret = env->CallStaticIntMethod(cls, mid, mystr);
return ret;
}
Pretty gruesome, but it seems to get the job done. Now if I can fill in my java load_texture method with something that works, I think I’ll have visuals! Here it is:
import android.graphics.BitmapFactory;
import android.graphics.Bitmap;
import android.content.res.AssetManager;
import android.opengl.GLUtils;
import android.opengl.GLES10;
import java.io.InputStream;
import java.io.IOException;
// gl is a GL10 instance, grabbed from within my renderer. app is my Galcon Activity.
public static int load_texture(String fname) {
Log.v("load_texture",fname);
AssetManager am = app.getAssets(); //new android.content.res.getAssets();
try {
InputStream stream = am.open(fname);
Bitmap bitmap = BitmapFactory.decodeStream(stream);
int[] textures = new int[1];
gl.glGenTextures(1, textures, 0);
int textureID = textures[0];
gl.glBindTexture(GL10.GL_TEXTURE_2D, textureID);
// no mipmaps
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
return textureID;
} catch(IOException e) {
// Log.v("load_texture:open() fail");
return 0;
}
}
Not trivial, but this saves me from having to decompress the files in C land. I currently don’t have any image decompression code that I use regularly so this works for me.