DinoMage on Twitter RSS Feed

Posts Tagged ‘android’

This is part 3 of my HowTo: SDL on Android series.
(prev)

Now let’s get some more native libraries linked into our app.  I probably don’t have to tell you how important this is, since you can’t make a whole lot of games or apps without text, sound, or music.

SDL_ttf

Adding a satellite library like SDL_ttf leads us through several steps.  These are roughly the same for each library, so I’ll only go over this one in specifics.

1) Download the source. Get the latest source from the Mercurial repository or grab a snapshot from the official page.

2) Place source in project. Put the SDL_ttf source directory alongside your SDL directory in your project (in jni/ so you get jni/SDL_ttf).

3) Gather dependencies. The way everything is set up, you have to be particular about the directory placement and naming.  The SDL extension libraries mostly expect their dependency directories to be in jni/ and to be named in their simple form (e.g. libDependency-0.3.0/ would be dependency/).  For SDL_ttf, we need to get the FreeType2 library.  Go to the FreeType official downloads.  You might end up in a list of files to download.  The latest version should be good.  As of this writing, that is “freetype-2.4.11.tar.gz” (you don’t need the doc or demo archives).  Yeah, you’ll probably need a good unzip program that can handle tar.gz or tar.bz2 archives.  Now, SDL_ttf strangely expects FreeType to live within the SDL_ttf directory.  Copy or move it over into the jni/SDL_ttf directory and rename it to “freetype”.

4) Add include and linker flags for the library. Open up your jni/src/Android.mk file.  Add $(LOCAL_PATH)/../SDL_ttf to the LOCAL_C_INCLUDES line.  That will give all of our files access to the SDL_ttf.h header. SDL_ttf knows how to build itself (it has an Android.mk), so it will create a shared library that we can link to.  Add SDL2_ttf (note the “2”) to the LOCAL_SHARED_LIBRARIES line.  The library is linked as SDL2_ttf so you can install it alongside SDL_ttf built for SDL1.2 as necessary.

5) Tell SDL to load the library at runtime. The last thing is to get the C library loaded from the Java code that really drives the app.  Open up src/org/libsdl/app/SDLActivity.java.  In the SDLActivity class, there’s a static block (line 58 in my copy) that loads the .so files.  Uncomment or add the line:

System.loadLibrary("SDL2_ttf");

Now when ndk-build runs, it should pull in SDL_ttf and pump out the .so.  Then when the .apk is installed and run, the Java code will load the C library for you.  Now you can work with the library just as usual.

If you have anything to add, please let me know in the comments!

Tags: , ,

This is part 2 of my HowTo: SDL on Android series.
(prev) (next)

Working with the Android platform is not so different from desktop platforms.  The biggest design difference is that the main input device is a touch screen.  Android devices can also be rotated to change the screen orientation via a built-in accelerometer.  There are also a few details that arise because we’re running native C or C++ code behind a Java front.  So we’ll have to handle touch events and orientation changes in addition to logging, file I/O, and interfacing with Java.

Touch Input Events

SDL 2.0 has a new member in its event structure for touch events.  We should at this point ignore mouse events (SDL_MOUSEBUTTONDOWN, etc.) and start responding to SDL_FINGERDOWN, SDL_FINGERUP, and SDL_FINGERMOTION event types.  event.tfinger holds the data for all types of touch events.

It works mostly like mouse data, except that the positions (x, y, dx, dy) are normalized to [0.0, 1.0].  You have to multiply them by the window’s width or height to get window coordinates.  This is so the concept of a touch device can be abstracted from the window.  To fully support multitouch, you also have to pay attention to tfinger.touchId (the device ID, not as important) and tfinger.fingerId (an ID for this particular touch).

Orientation Events

In Part 1 of this guide, I briefly mentioned enabling orientation events by making sure to specify in AndroidManifest.xml that your app can handle that kind of configuration change.  Once you have that, SDL pretty much just works how you’d hope.  When your Android device is rotated, your app receives an SDL_WINDOWEVENT_SIZE_CHANGED window event.  Note that window events in SDL2 are special events.  They all have the same event type SDL_WINDOWEVENT, then you have to check the event.window.event member to see which kind of window event it is.  The SDL_WINDOWEVENT_SIZE_CHANGED window event will have the new dimensions of the screen in the event.window.data1 and event.window.data2 members.  You can check what kind of orientation this results in (landscape is wide vs. portrait is tall) by comparing the values of the width and height.

Often, though, you have an app for which it doesn’t make sense to switch between landscape and portrait orientations.  You can lock the orientation with a specification in the AndroidManifest.xml.  In the same ‘activity’ element that has the configChanges attribute, add another attribute:

android:screenOrientation="sensorLandscape"

or

android:screenOrientation="sensorPortait"

These will let the screen flip upside-down if the device is held upside-down, but still lock to the aspect that you choose.  Notice that I use “sensorPortait” here, because for an older version of Android, someone made a typo.

Logging

Android uses a program called logcat to check the contents of the device log.  You can use logcat directly through adb or you can use Eclipse’s logging window.  Normal native apps typically use __android_log_print() or similar functions to print to the log.  This support is pulled from a library, so you would need to include <android/log.h> and link with -llog.  But…  don’t bother.  SDL has already done this for you.  SDL_Log() is a direct replacement for ‘printf’ or ‘cout’.  There are some other functions to help you filter your messages in logcat (check that wiki page).  I also like to wrap SDL_LogMessageV() for more control.

Assets with SDL_RWops

If you haven’t used them before, SDL_RWops are awesome.  They are an abstraction over reading and writing memory and can cover things like file access, in-memory loading, and even network data reading if you’re crazy.  They bring low-level data into your program and if an interface supports SDL_RWops (e.g. SDL_LoadBMP_RW), you can turn that data stream into something immediately useful.

What this means for us in Android may not be obvious right away.  The SDL_RWops creation functions (like SDL_RWFromFile) will first check *within* your APK, in its assets/ directory.  Since thing like SDL_image’s IMG_Load() are built atop of SDL_RWops, this makes them work automatically even though your assets are technically inside a zip file.  Just make sure to keep updated asset files in your project’s assets/ directory.

And if you’re using SDL_RWops directly, use SDL_RWread, SDL_RWwrite, and SDL_RWclose (which frees the SDL_RWops too).  For reference, here’s an example of how to read in all the bytes from an SDL_RWops:

SDL_RWops* rwops = SDL_RWFromFile(filename, "rb");
if(rwops == NULL)
{
    SDL_Log("Could not open file \"%s\".\n", filename);
    return -1;
}
long data_max_size = 100;
unsigned char* data = (unsigned char*)malloc(data_max_size);
long total = 0;
long len = 0;
while((len = SDL_RWread(rwops, data, 1, data_max_size)) > 0)
{
    // Do stuff with your 'len' bytes of data
    total += len;
}
free(data);
SDL_RWclose(rwops);

User Data

If you need to read or write data on the Android device, you can’t (can, but shouldn’t) do it directly in the APK.  It gets messy reading and writing inside a zip file (your APK).  Instead, SDL gives you some native access to the user directory with SDL_AndroidGetInternalStoragePath().  This returns the path to the user data directory for your app.  To specify a file there, append a ‘/’ and the file name.  You can use an SDL_RWops to open the file and you’re set.

Interfacing with Java

Typically, getting two different languages to communicate is verbose, messy, and error-prone.  Java and C are reasonably similar on the surface… but no, this will still be messy.

Java uses JNI (Java Native Interface) to communicate with C or C++.  JNI works both directions.  You can make native calls from Java (Java -> C) and you can make Java calls from the native side (C -> Java).  I, for one, wish it weren’t necessary, but there are some APIs that do not have a native implementation.

The gist of Java -> C is that Java needs to know exactly how to call the desired C function.  That means writing a declaration in Java and writing the called function in a special way in your native code.  The nice thing is that we don’t have to modify SDLActivity.java at all to customize our app’s Java-side behavior.  SDL gives us an “in” by way of inheriting from SDLActivity in our own Java source.  We just have to make sure that we let SDL continue to do what it needs to do.

Here’s my example for my Test1 class if I wanted key down events from the OUYA controller API:

import tv.ouya.console.api.OuyaController;
public class Test1 extends SDLActivity
{
public static native void OuyaControllerKeyDown(int player, int keyCode);  // Our native function declaration
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Let SDL do its thing
    OuyaController.init(this);  // My turn
}
// I want to catch key events and send them to the native code
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    int keyCode = event.getKeyCode();
    if(event.getAction() == KeyEvent.ACTION_UP && onKeyUp(keyCode, event)) // I'm not including onKeyUp() for brevity
        return true;
    else if(event.getAction() == KeyEvent.ACTION_DOWN && onKeyDown(keyCode, event))
        return true;
    return super.dispatchKeyEvent(event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    switch(keyCode){
        case OuyaController.BUTTON_O:
        case OuyaController.BUTTON_U:
        case OuyaController.BUTTON_Y:
        case OuyaController.BUTTON_A:
        case OuyaController.BUTTON_L1:
        case OuyaController.BUTTON_R1:
        case OuyaController.BUTTON_L3:
        case OuyaController.BUTTON_R3:
        case OuyaController.BUTTON_MENU:
        case OuyaController.BUTTON_DPAD_UP:
        case OuyaController.BUTTON_DPAD_RIGHT:
        case OuyaController.BUTTON_DPAD_DOWN:
        case OuyaController.BUTTON_DPAD_LEFT:
            if(event.getRepeatCount() == 0)
                OuyaControllerKeyDown(OuyaController.getPlayerNumByDeviceId(event.getDeviceId()), keyCode);  // The native call
            return true;
    }
    return super.onKeyDown(keyCode, event);
}
}

So, I declare my native function as public static native (and don’t use underscores!  You’ll see why soon.) and I make sure to pass control to the superclass by calling its corresponding super.* call whenever I override a method.

Now, the native side:

#ifdef __ANDROID__
#include <jni.h>
extern "C" void Java_com_dinomage_test1_Test1_OuyaControllerKeyDown(JNIEnv* env, jclass cls, jint player, jint keyCode)
{
    __android_log_print(ANDROID_LOG_VERBOSE, "Test1", "nativeOuyaControllerKeyDown(): player=%d, keyCode=%d", player, keyCode);
    // We got data!
}
#endif

C++ is a little naughty with its name-mangling, so declaring a function with C linkage (extern “C”) is pretty common.  Java wants us to do that here so it can find the function to call in our binary (a shared object on Android – that’s like a DLL in Windows).  In C, of course, you don’t need to do that.  Java also wants us to name the function very specifically.  You’ll notice that the underscores separate a uniquely identifying set of tokens.  “com.dinomage.test1” is the package/module name.  “Test1” is the Java class that has the native function declaration.  “OuyaControllerKeyDown” is the function name, finally.

JNI uses some special types in the native code so we can access the data.  Some are simple and obvious, like “jint”.  We can use it just like an integer, because it is one.  Some aren’t so simple.  With “jstring”, we would need to call a special function to extract the C string and then another to free it later.  Java does not store data the same way that either C or C++ do.  See the JNI reference for details.

That’s that for Java -> C.

C -> Java: TODO!

If you have anything to add, please let me know in the comments!

Tags: , ,

Meet our first Android app, Board Game Apprentice!  You can get it now at Google Play or the Amazon Appstore.

Board Game Apprentice promo image

It’s a simple thing, conceptually.  Board Game Apprentice is a set of 7 tools for helping you play board games.  If you’re missing a timer, dice, or a spinner, you can use a digital one instead (just don’t lose your phone!).  They’re even fun just to mess around with and a great thing to have handy when you need it.

Available for Android 2.3+, Kindle Fire, and Kindle Fire HD

App Includes:
Timer – Digital, hourglass, analog, and chess timers, each can run in the background when other tools are in use
Dice – Traditional pip dice and numbered dice from 4 to 20 sides, custom text and colors, hold tray, roll history
Spinner – Realistic feel with colored or numbered wedges, freespin mode for pointing at a player
Buzzer – Up to 4 buzzers, sound and glow indicate who rings in first
Doodle Pad – Quick sketching and erasing, supports multitouch (kids love it!)
Scoreboard – Auto sums, enter player names, no limit on number of players, undo and redo
Tokens – 4 colors of your choice, divided table, stackable, supports multitouch

Go get it!

en_generic_rgb_wo_60    amazon-apps-store-us-white

We don’t approach mobile apps in the all-too-typical way.  Our apps are more than just microgames.  Board Game Apprentice doesn’t skimp on any of the tools provided, offering way more value than other apps which contain just one or two of these tools.  We also can’t stand advertisements and devious business models.  We hope you do too.

Tags: , ,