Phil Hassey - game dev blog
Phil Hassey as Rambo
".. I've been there,
I know what it's like,
and I'll do it again if I have to."

Archive for the 'galcon' Category

Galcon Palm update

Friday, August 13th, 2010

Wow, it’s great to see how many people have been enjoying Galcon for the Palm Pre Plus! I have gotten some really great feedback, so I’ve put together an update! All of the in-game issues are related to the integration with the WebOS features, so I’m documenting the bug report and the solution here.

Item 1: Back gesture quits the app – Resolved, 95%

This was really easy. I just capture the SDL key event for SDLK_ESCAPE and handle that as a “back” button. The nice thing is due to all my work on the Android port where users demanded back button support, I had already added this feature! So coding this only took about 5 minutes. (The Android work I’m building on took a good day, so enjoy the back features, they work throughout the whole game!)

The only shortfall of this fix is that when you use the back gesture from the main menu the app doesn’t minimize, it just sits there. I could have it quit, but I’m not sure that would be better. I’ll have to see what users say. The PDK does not have a PDL_MinimizeApp() function or anything yet, so I’ll just leave it as is now. The user can still tap the gesture area to minimize the app.

Item 2: Reduce idle CPU usage – Minor, no ideal solution

One user noticed Galcon was still using 2% cpu when minimized. This is probably due to OpenGL rendering, which I really don’t need to continue doing when the app is minimized. I considered reducing this to 0%, but if you are paused while in a multiplayer game you might want to be able to see the occasional visual update to the game. And when you are really done playing anyways, it’s best to quit the game anyways. So for now, I’m leaving this issue as-is, since I don’t think it would be an improvement to change how it works now.

Item 3: Respect the phone’s mute switch – Not a bug

I did a bit of searching in the Palm developer forums and found this answer: “No, the mute slider can’t be checked. When I asked our interface team about this, they say that the switch is a “ringer mute” switch, not a general system mute, so it doesn’t affect the volume of game or navigation programs.” So, yeah, sorry, nothing I can do about this.

Item 4: Local multiplayer not working – Minor, no solution found

I got a report from a user that local multiplayer wasn’t working, and it seems that is the case. I tried using “PDL_SetFirewallPortStatus” to enable access to the Galcon port, but that didn’t seem to improve it. From checking in the forums it might be something a bit more complicated relating to how the firewall / networking works on the phone. Fortunately, you can do 1v1 games if you just join a 1v1 server with your friend at the same time, so this is a pretty minor issue in the grand scheme of things.

Item 5: Unable to type numbers – Workaround warning added

This was a known bug from the beginning, due to how the PDK and SDL works, when a user presses the number pad and then presses a number key, a letter is sent to the SDL app. The only way to get a number is for the user to hold down the number pad key and the number key. The Palm developers have told me that my options are to hack in a translation (which wouldn’t respect international keyboards), or wait until a future release for a PDK function that could do a translation for me. Since a hack that would break it worse for some users isn’t any good, and I want to do an update soon, I’ve decided to do the next best thing and prompt the user with a tip at the top of the Settings screen telling them of the physical workaround of not letting up on the number-pad key.

So, not a perfect update, but I’m very happy that I’ve been able to develop a solution for the back gesture .  And I think the addition of the warning with the keyboard issue is a reasonable workaround.  The other items I’ve verified, but not found ideal solutions for.  I also want to add Pixi support, but I hear that 1.4.5 isn’t going to be released to the Pixi, so there isn’t much rush for this to be put out.  Once I get ahold of firmware to dev on the Pixi I’ll try and update Galcon for it though.

-Phil

P.S. I’m just about to submit this update, so it probably won’t hit the store for a couple days.

Galcon for Sixense

Thursday, August 12th, 2010

I spent the first few days this week integrating Galcon with the Sixense motion controllers! It was a nice break from the work on the Android as I waited for a G1 to get shipped to me (I’m not sure if Galcon will work on the low-end phones yet, but I’m going to find out soon!)

galconsixense

Working with the Sixense was very different, the controllers are great, and it really provides a completely unique Galcon experience. I modified the game to allow all modes to be played Co-Op with multiple users (mouse + up to 4 Sixense controllers). And I also added a special Sixense Multi-player mode where you are divided into two teams and can play against each-other.

Like with any interface design I always try and fit Galcon to the device. In this case, I had to modify a few subtle things in the user interface to make it work just right with the Sixense. I also set one of the controller’s buttons to be a “Select-all” and a “Deselect-all” button, which makes it easy to do those common actions. So I think I’ve done a pretty good job matching the Sixense controller interface with the Galcon interface. You can also see in the screenshot that each controller gets a different color for their planet selection / action choices, and the crosshairs contains the Ships % information. The mouse player still goes off the standard Ships % that’s show in the bottom right corner.

All-in-all, I think this variant of Galcon is going to be a ton of fun! I have no idea when it will be available for people to play, but I’m sure it’ll be good times when it is!

-Phil

Android Day 8: Bug fixin’

Thursday, August 5th, 2010

Step 1: Handling Back / Menu events.

Several people mentioned that it’s common to respond to pressing the BACK / MENU / HOME / SEARCH buttons on the bottom of your Android device. At least, BACK / MENU. I investigated the options and there are some methods for Activity that can be added doBackPressed, onMenuOpened, onUserLeaveHint, onSearchRequested. However, not all of these are supported in Android 1.6 (doBackPressed). And also, they all seem to work a bit differently.

What I found was a more direct approach to dealing with this was to capture the KeyUp events in onKeyUp. The keycodes are KEYCODE_BACK, KEYCODE_MENU, KEYCODE_HOME, KEYCODE_SEARCH. To capture them you just return “true” from that method and they won’t work anymore, except for HOME which works all the time, so leave that alone. If you really care, you’ll need to use onUserLeaveHint. Ideally you’d check for isCancelled, but again that’s not supported on older devices. I set up Galcon to intercept the BACK and MENU buttons to send an event to my code to fall back to pause screens. And I set it up to ignore the SEARCH button so that a user wouldn’t accidentally bring it up in-game. The HOME button I left alone.

Step 2: Getting OpenGL ES 1.0 compliance

I found that on low-end Android devices like the G1 and the Droid Eris, the visuals were not displaying properly and the log was showing a ton of “E/libEGL ( 494): called unimplemented OpenGL ES API” errors.

This was due to my use of the “glColor4ub” function which apparently isn’t supported. I changed those calls to “glColor4f” and that resolved that issue. I never found a decent way to figure out what function was causing the errors, I just figured it out by guesswork and trial and error. If someone has a tip here, that would be pretty great.

One issue I’m having is that the emulator running 1.6 is not displaying the game at all. I tried it with 2.1 on the emulator and it also does not display the game, so it looks like I might need to quickly attempt to acquire a G1 to do low-end testing on.

I also ran up against the GL stack at some point and had to reduce the number of glPushMatrix and glPopMatrix’s I was doing. A hint here is use glGetError() to find out if you’re getting any errors. This also seemed to clear up the emulator issues, but the emulator is running so slowly that I really can’t use it.

Step 3: Better keyboard support.

I found the soft keyboard input wasn’t always working quite right. This code snippet seemed to work better, maybe. The choice of SHOW_IMPLICITY or SHOW_FORCED is a lesser of two evils decision. Time will tell which decision will give me more tech support issues.

    public void toggle_keyboard(boolean show) {
        InputMethodManager mgr = (InputMethodManager)app.getSystemService(Context.INPUT_METHOD_SERVICE);
        mgr.hideSoftInputFromWindow (mGLSurfaceView.getWindowToken(),0);
        if (show) {
            mgr.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
            // On the Nexus One, SHOW_FORCED makes it impossible
            // to manually dismiss the keyboard.
            // On the Droid SHOW_IMPLICIT doesn't bring up the keyboard.
        }
    }

I wish there was a better way to do this that worked a bit more consistently, but this is the best I’ve come up with so far.

Step 4: Getting the application to quit and restart cleanly

It was requested that the “BACK” button quit the app from the main menu, which is the usual behavior of most apps. I added the call to finish() to force this to happen, but I found that whenever the app was launched again it would crash once, then on a second try it would work.

What was happening was this: the shared library “libigalcon2.so” was not being reloaded, so due to some failure on my part to smartly handle that, there were some critical values that were not being reset when the app relaunched. I had to carefully make sure to initialize those values on each start of my app. Here’s the symptom that clued me in:

D/dalvikvm(21863): Trying to load lib
    /data/data/com.galcon.igalcon/lib/libigalcon2.so 0x447a7048
D/dalvikvm(21863): Shared lib
    '/data/data/com.galcon.igalcon/lib/libigalcon2.so' already loaded in same CL 0x447a7048

Also note that the JNI_OnLoad was not being called each time my app was started up. It was only called the first time the shared library was loaded, so that was why on second launch my app would crash. (The real problem was I was saving the JNIEnv as a global variable, and it was not being reset when my app restarted.) Lesson learned: be very careful with globals!

Whew, that was a long day .. But we’re getting closer and closer to being ready!  The beta testers now report that the game is working nicely on all devices.  But they report that the framerate is awful on the G1 still, so we’re going to investigate improving frame rate yet.

-Phil

Coming VERY Soon: Android Galcon

Tuesday, August 3rd, 2010

After a long week of hard work I’m almost ready to launch the Android port of Galcon!

droidpromo

In other news, I’ve been working on updated the front page of the Galcon website to include access to the latest news and links to my development blog. You’ll definitely want to keep an eye on those as I’ve got a lot of fun stuff in store this year!

Anyway, stay tuned, I’ll be announcing the availability of the Android port of Galcon VERY SOON!

-Phil

Porting Galcon using the Android NDK

Tuesday, August 3rd, 2010

droidslimI’ve completed my port of Galcon (an iPhone game written primarily in C/C++) to the Android NDK – meaning I’ve kept the game written in C/C++ but I’ve replaced my ObjectiveC/iOS device interface with a Java/Android device interface layer.

Porting to the Android has been a thrilling experience, one that I will not soon forget. The epic journey took seven days of toil through the forests of software installation, Java integration, bug extermination, and quirk comprehension. I’ve journaled my quest in detail. This entry servers as a portal to that vast mountain of knowledge. I also include some final thoughts.

Android Day 1: SDK, Eclipse IDE, and device activation
This documents how I installed the Android SDK and NDK software, set up the IDE, and activated the devices. Through the rest of my process I did not use the Eclipse IDE, so that step can be skipped if you have the same goals as me.

Android Day 2: The NDK
Next I got the NDK installed and ran a few sample apps with it. I then found a port of STL for the Android NDK and dropped that into my codebase.

Android Day 3: Packaging, Assets, JNI, and OpenGL
With a bit of work I was able to get Java to talk to C and C to talk to Java. I also researched my options for packaging game assets such as graphics and audio, as well as got an OpenGL context setup and got Java loading my textures for me.

Android Day 4: Video cleanup, Input handling
I was able to get the visuals complete in the game, and added event handling for onTouchEvent’s. I also removed the title bar that shows at the top of the screen by default. The advice about onPause is a bit vague, but I explain a bit better in a later part.

Android Day 5: Keyboard, Multiplayer / Networking
After considerable searching I was able to find out how to get the software keyboard to display. I then worked on getting the multiplayer portion of the game working, which was rather challenging as the debugging tools did not seem to yield tracebacks, but they gave just enough that I was able to figure out what was wrong.

Android Day 6: Save games, Audio, other Details
During this day I focussed mostly on getting save / restore (or pause / resume) working better. I also got the audio features of the Android working on playing my music and sound effects. To wrap it up, I added the game’s icon.

Android Day 7: Leftovers
A lot of cleanup here – better pause / resume support, better device support, keyboard support, volume controls, copy protection, bugs, and signing. The game is running quite well now!

So that was my journey. It has been a challenging quest – I haven’t used Java in about 10 years, and doing Java-to-C integration is a fragile process! Learning to navigate through Java classes to get things done is not what I’m used to in the more simple worlds of C and python. But the port is almost ready, and I’m quite pleased with it.

-Phil

Android Day 7: Leftovers

Wednesday, July 28th, 2010

I did some basic testing of the app, and I found a number of minor things to cleanup.

Step 1 – going back to single touch for better device support:

My use of MouseMotion didn’t work on the Droid because the Droid is Android 2.1 (SDK7). So that means e.getMaskedEvent() didn’t work. And though it seems the Nexus has support for some basic MultiTouch, it didn’t work on the Droid, so I’m dropping Galcon back to single touch. Most users only use single touch anyways.

Along with this I did my build at SDK level 4, so I could be sure I wasn’t doing anything else. Recall I have it set to 8 so that the build doesn’t choke on my android:installLocation=”preferExternal” .. So I’ll have to set it back to 8 once I’m done so that feature can be used on phones that support it.

Step 2 – finding out the UDID of the device:

Getting the UDID works on some platforms not others. Here’s a snippet:

    // JNI to get the ANDROID-ID
    public static String get_udid() {
        String r = Secure.getString(app.getContentResolver(), Secure.ANDROID_ID);
        if (r == null) { r = "unknown"; }
        Log.v("get_udid",r);
        return r;
    }

Step 3 – enabling in-game volume controls:

To get the volume control to work you have to add this to your onCreate method:

setVolumeControlStream(AudioManager.STREAM_MUSIC);

And have your onKeyDown method return false.

Step 4 – keeping the Droid keyboard from closing the game:

Getting the keyboard not to close the app on the Droid required adding this to the Activity tag:

android:configChanges="keyboard|keyboardHidden|orientation"

See this page for explanation:

http://developer.android.com/reference/android/R.attr.html#configChanges

Step 5 – Using Licensing vs standard Copy Protection:

I investigated the new Licensing stuff:

http://developer.android.com/guide/publishing/licensing.html

It appears to be fairly involved to add, so I’ll be passing on it for now. The drawback is that using the built-in copy protection of the marketplace requires that my app will be installed on the system memory. On the plus side, I’ve gotten Galcon down to 4MB, which is only 2% of the system memory. From reviewing the top 20 or so Android games, 4MB appears to be an acceptable size.

“A limitation of copy protection is that applications using it can be installed only on compatible devices that provide a secure internal storage environment. For example, a copy-protected application cannot be downloaded from Market to a device that provides root access, and the application cannot be installed to a device’s SD card.”

On the plus side, if I do decide to disable the standard copy protection in a future build and add licensing, I can do that via the publishing site apparently.

“After uploading your licensed application, remember to remove copy protection from the application, if it is currently used. To check and remove copy protection, sign in to the publisher site and go the application’s upload details page. In the Publishing options section, make sure that the Copy Protection radio button selection is “Off”.”

Step 6 – bugs due to overuse of memory:

I’ve noticed the occasional crash during my crossfade. The crossfade uses glCopyTexImage2D coupled with a 1024×1024 texture. That’s a good 4MB of memory and a function which seems to cause trouble. I’m going to remove that for now. The crossfade looks nice, but it doesn’t radically enhance the user experience. (Certainly not enough to off-set a crash!) If the crash persists, I’ll re-add the crossfade.

Step 7 – Signing the App:

Last phase is getting a signed version of my app created. This is required to submit an app to the marketplace. I’m not doing this immediately, but the directions here seem fairly straightforward.

http://developer.android.com/guide/publishing/app-signing.html

Android Day 6: Save games, Audio, other Details

Tuesday, July 27th, 2010

Okay .. the gameplay is totally working, so now it’s time to add a bit of polish to the game!

Step 1 – forced orientation:

If you want to set the orientation of your app to always be the same, add this to your AndroidManifest.xml Activity tag:

android:screenOrientation="portrait"

Otherwise, the app could start up in landscape mode which would give you a rotated OpenGL window, which for most games is probably not what you want.

Step 2 – handling Save / Restore of game state:

Now I want to get save / restore working in the game. As fun as it has been re-typing in my name every time I try the MultiPlayer stuff out, the fun is over. On the plus side, it’s given me plenty of opportunity to work out a few other bugs in there.

There are a ton of options for save / loading data.

http://developer.android.com/guide/topics/data/data-storage.html

I considered using the method named “External Storage” it would allow my app to save and load data from actual REAL files with REAL paths, which was attractive in terms of making my life easier, but there was a whole huge section on checking to make sure that is even possible. I guess there are like 3 cases where it might not work.

So since I’m big on not checking if something works, I decided I’d use the “Internal Storage” method which seemed a bit more idiot proof. It appeared to also have a way to find the folder name, so that will make things pretty easy.

    // JNI used to get Save data dir
    public static String get_docdir() {
        File fdir = app.getFilesDir();
        return fdir.getAbsolutePath();
    }

However, it seems that when saving, this is causing some kind of SIGSEGV .. From what I can tell the “JNIEnv* env” that is passed to my C code isn’t the same on every call. So what I’m doing now is I’m saving the first one and only using that, not updating it as new values come in to my code. (Some of the values sent in later looked suspicious to me. Some were even null!)

Step 3 – adding music:

One of my favorite parts of game development is getting the sound working. It always makes the game feel like it’s ready to go. This is the link to read:

http://developer.android.com/guide/topics/media/index.html#playfile

Here’s the code I came up with to play music. Again, I’m storing all my game assets in the assets/ folder since that gives me the most simple direct access to it.

    // JNI to play music, etc
    public MediaPlayer _music = null;
    public static void music_play(String fname) {
        Log.v("music_play",fname);
        AssetManager am = app.getAssets();
        try {
            AssetFileDescriptor fd = am.openFd(fname);
            app._music = new MediaPlayer();
            app._music.setDataSource(fd.getFileDescriptor(),fd.getStartOffset(),fd.getLength());
            fd.close();
            app._music.setLooping(true);
            app._music.prepare();
            app._music.start();
        } catch(IOException e) { }
    }
    public static void music_stop() {
        if (app._music == null) { return; }
        app._music.stop();
    }
    public static void music_volume(float v) {
        if (app._music == null) { return; }
        app._music.setVolume(v,v);
    }

Step 4 – adding sound effects:

Time to add in sound effects. This is done with the SoundPool class. It might actually be easier to use this class for music as well.

    // JNI to play sounds
    public SoundPool _sounds = new SoundPool(8,AudioManager.STREAM_MUSIC,0);
    public static int sound_load(String fname) {
        AssetManager am = app.getAssets();
        try {
            AssetFileDescriptor fd = am.openFd(fname);
            int sid = app._sounds.load(fd.getFileDescriptor(),fd.getStartOffset(),fd.getLength(),1);
            return sid;
        } catch(IOException e) { }
        return 0;
    }
    public static void sound_play(int sid) {
        app._sounds.play(sid,(float)1.0,(float)1.0,0,0,(float)1.0);
    }

Step 5 – opening up the web browser to a URL:

In my game I’ve got a handful of spots where I let the user open up a webpage (more games, forums, etc…) Here’s the code:

    public static void open_url(String url) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse(url));
        app.startActivity(intent);
    }

Step 6 – adding the app icon:

It’s about time I got an Icon ready for this app. I’m going to use my Galcon Fusion style icon since that one works nicely with transparency. You can get really fancy with providing a bunch of different icon sizes:

http://developer.android.com/guide/practices/ui_guidelines/icon_design.html

But I just provided a single 72×72 icon.png which I put into res/drawable. And then I added this into the Application tag of my AndroidManifest.xml:

android:icon="@drawable/icon"

That seemed to be sufficient.

Step 7 – further thoughts on pause / resume:

Another cleanup item I found was getting pause/resume working more “the Android way” .. Or at least, working at all. Again, I reviewed this and made some changes to how my app works.

http://developer.android.com/reference/android/app/Activity.html

Working on this sent me into a spiral of weird crashes for no particular reason. I sprinkled around the “synchronized” keyword with no luck. I did some code cleanup and fixed some memory leaks and now it seems better. A bit tricky though, I can’t always tell why things happen.

In general, I found that during onStop you should free up all your audio assets, so that on the next onStart you could reload them. I also found that the GL assets automatically get freed, so on any onSurfaceChanged you need to reload your GL assets.

But the game is seeming pretty stable now. Time to set it in for some testing for a while 🙂

-Phil

Android Day 5: Keyboard, Multiplayer / Networking

Monday, July 26th, 2010

After a weekend of recouping from last week’s barrage of code .. I’m back!

Step 1 – Displaying the Software Keyboard:

Okay, the keyboard took forever to figure out. In fact half of Day 4 was burned on this one. I’m not going to go into the horrifying details, but here’s the best hack I could come up with to show and hide the android keyboard on demand. I’ll leave the JNI integration as an exercise to the reader. (I also have included the event handler, this is pretty straightforward, but clearly demonstrates the obtuseness of Java APIs. In python, this would certainly be simpler 😉 Anyway, this code can be pasted into your main Activity where “app” is a static reference to your Activity object.

    boolean _keyboard_state = false;
    public void toggle_keyboard() {
        InputMethodManager mgr = (InputMethodManager)app.getSystemService(Context.INPUT_METHOD_SERVICE);
        mgr.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT,0);
        app._keyboard_state = !app._keyboard_state;
    }

    // JNI used to call this from C/C++
    public static void show_keyboard() {
        Log.v("keyboard","show");
        if (!app._keyboard_state) { app.toggle_keyboard(); }
    }

    // JNI used to call this from C/C++
    public static void hide_keyboard() {
        Log.v("keyboard","hide");
        if (app._keyboard_state) { app.toggle_keyboard(); }
    }

    public boolean onKeyDown(int keycode, KeyEvent e) {
        Log.v("keydown",Character.toString(Character.toChars(e.getUnicodeChar())[0]));
        // native call to send Event to C/C++ code
        return true;
    }

And now we’re onto ..

Step 2 – Debugging Multiplayer:

Testing multiplayer. Well, it appears we might be entering some trouble. First of all, it seems I have to enable permission for my app to access the internet. I add this into my AndroidManifest.xml:

    <ses-permission android:name="android.permission.INTERNET" />

This seemed to get it to do something, but usually that something is SIGSEGV. Time to fire up GDB. Earlier I covered basic debugging, but not how to use GDB with the Android. Basically, in the NDK, you’ll want to read “docs/NDK-GDB.TXT” .. Unfortunately this seems to crash the event loop in Java somehow everytime. So it isn’t doing me any good.

– When I first start gdb it gives me: “0xafd0eb68 in ?? ()” which blocks all event handling.
– I tell it to “continue” which seems to get things going again.
– But when it hits the MP code that crashes, it says

Child terminated with signal = b 

Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb)
Child terminated with signal = 0xb (SIGSEGV)
GDBserver exiting
(gdb) backtrace
No stack.

So my attempt at getting a backtrace is futile. Huh. This made me think maybe I’m coming into some kind of multi-threading issue. (While the events were blocked, the logic loop and graphics continued, indicating some two separate threads.) ndk-gdb does not support multi-threaded apps. The Galcon code is also single threaded, so it can’t cope with multi-threaded stuff either. I went to all my native methods and added the synchronized keyword:

 public synchronized native void event(....);

This cleared up some of my crashes.  However, when joining a server I get a SIGBUS, which I’m not entirely sure what it is, but it sounds like a SIGSEG except worse.

This could be an alignment error. I’ll download enet 1.2.2 as it includes a changelog item: “now uses packed attribute for protocol structures on platforms with strange alignment rules” .. IIRC, I fixed that in my local copy of Enet to get it working on the iPad, but I’ll check this version out. And thanks to the general awesomeness of ENet the packing bug is fixed, so now I’ve got full-mulitplayer working. Embarrassingly enough I got noob-stomped pretty bad.

Android Day 4: Video cleanup, Input handling

Friday, July 23rd, 2010

Whew, day 3 was epic! I’m glad I got through all that! Day 4 (I hope) will be a bit easier. Now that I’ve got things “basically” working, it should be mostly a matter of getting a few more API calls doing what they need to do.

Step 1 – Basic handling of onPause:

First of all, right now when I restart the App without reinstalling it, I get a white screen. I think this is due to some multitasking feature on the Android where it shoves the App into the background and tells it to pause. When unpaused, I need to reload the graphics. Fixing this was pretty trivial, I just added a few lines to the onPause() method to save state and basically shutdown.

Step 2 – Removing the title bar:

When I start my game, there is a title that says “Galcon” in text on the top. I think that is part of the default app generation. I’ll want to remove that. First of all, in “res/layout/main.xml” I commented out the TextView. This had no effect, but at least I cleared out some junk. This page appears to explain how to hide the title bar (I found by adding .Fullscreen it hides the Status Bar as well.)

http://developer.android.com/guide/appendix/faq/commontasks.html – modify AndroidManifest.xml:

<application android:icon=”@drawable/icon” android:theme=”@android:style/Theme.NoTitleBar.Fullscreen”>

Step 3 – Adding onTouchEvent handling:

I really want to start playing the game – so I’m going to add some input event handling. I’ll do this from the top level in my Activity by adding a onTouchEvent handler. The documentation for MotionEvent was a bit opaque, but I worked it out and here’s how to handle events and deal with MultiTouch. It seems like something is severely broken about the API, but this was the only way I could get MultiTouch to work in Galcon. It appears to only work with 2 fingers.

    public boolean onTouchEvent(final MotionEvent e) {
        for (int i = 0; i<e.getPointerCount(); i++) {
            boolean masked = false;
            switch(e.getActionMasked()) {
                case MotionEvent.ACTION_POINTER_DOWN:
                case MotionEvent.ACTION_POINTER_UP:
                    masked = true;
                break;
            }
            if (masked && i != e.getActionIndex()) { continue; }
            float x = e.getX(i); float y = e.getY(i);
            float dx = 0; float dy = 0;
            if (e.getHistorySize()!=0) {
                dx = x - e.getHistoricalX(i,0);
                dy = y - e.getHistoricalY(i,0);
            }
            int pid = e.getPointerId(i);
            int type = 0;
            switch(e.getActionMasked()) {
                case MotionEvent.ACTION_DOWN: type=EVT_DOWN; break;
                case MotionEvent.ACTION_MOVE: type=EVT_MOTION; break;
                case MotionEvent.ACTION_OUTSIDE: type=EVT_MOTION; break;
                case MotionEvent.ACTION_POINTER_DOWN: type=EVT_DOWN; break;
                case MotionEvent.ACTION_POINTER_UP: type=EVT_UP; break;
                case MotionEvent.ACTION_UP: type=EVT_UP; break;
                case MotionEvent.ACTION_CANCEL: type=EVT_UP; break;
            }
            event(type,(int)x,(int)y,0,pid+1,(int)dx,(int)dy,0,0);
            if (masked) { break; }
        }
        return true;
    }

This works okay, except my Nexus One has a different screen resolution than my iPhone, so all my code is handling things a bit off. I added a native call in onSurfaceChanged to tell my game the size of the screen. In my event handler I translate for the game.

Step 4 – Keyboard handling:

To play the multi-player game, I need to have the pop-up keyboard work. On the Droid, of course, this isn’t needed, but for the Nexus One and possibly other future devices it is.

This is apparently easier said than done. More on it in day 5!

Android Day 3: Packaging, Assets, JNI, and OpenGL

Thursday, July 22nd, 2010

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.