DinoMage on Twitter RSS Feed

Archive for the ‘Dev’ Category

Pixel Proof is our newest app for desktop computer platforms.  It’s a great new editor that approaches pixel art in a special way. The interface is simple, the tools are fresh, and much of it is customizable. Set up your palette and toolset and the layered file format will save them along with the image. With Twitter integration, you can even tweet your art directly from the app!

pixel_proof_banner

The Pirate Edition is free and fully-featured. Give it a try and let me know what you think! If you like it, buy it!
https://dinomage.itch.io/pixel-proof

With all of the exciting features packed into Pixel Proof and the future plans, Pixel Proof is lined up to be the cleanest, most flexible pixel editor available.  Help us get there by trying it out and providing feedback.  We appreciate all input!

frog

Tags: ,

2
Apr

C string explode

   Posted by: in Dev, Libraries

I was working on porting some code from C++ to C and I couldn’t really find a simple explode() function for C online.  So, of course, I had to make it myself.  I’m just putting it here for general reference.  This implementation uses malloc(), so it’s going to be a little less performant than a static one, though easier to use.  At least it can’t be any worse than my C++ implementation!

No guarantees, you’re granted the code under the MIT license.

typedef struct StringList
{
    char* value;
    struct StringList* next;
} StringList;
void StringListFree(StringList* node)
{
    // Delete the nodes in order
    while(node != NULL)
    {
        StringList* last = node;
        node = node->next;
        free(last->value);
        free(last);
    }
}
// Returns a linked list of malloc'ed NULL-terminated strings.  The delimiter is erased.
static StringList* explode(const char* text, char delimiter)
{
    StringList* head;
    StringList* new_node;
    StringList** node;
    const char* start;
    const char* end;
    unsigned int size;
    if(text == NULL)
        return NULL;
    head = NULL;
    node = &head;
    // Doesn't technically support UTF-8, but it's probably fine, right?
    size = 0;
    start = end = text;
    while(1)
    {
        if(*end == delimiter || *end == '\0')
        {
            *node = (StringList*)malloc(sizeof(StringList));
            new_node = *node;
            new_node->value = (char*)malloc(size + 1);
            memcpy(new_node->value, start, size);
            new_node->value[size] = '\0';
            new_node->next = NULL;
            if(*end == '\0')
                break;
            node = &((*node)->next);
            start = end+1;
            size = 0;
        }
        else
            ++size;
        ++end;
    }
    return head;
}

Hey there!  This is a tutorial on some of the basics of using SDL_gpu.  If you are comfortable with C or C++ and SDL, then you’re ready for this.  I’ll post installation instructions for SDL_gpu some other time, though if you use SDL then you probably already have that down.

To start things off, here’s a listing of the functions we’ll be using:

GPU_SetDebugLevel
GPU_Init
GPU_LoadImage
GPU_Clear
GPU_Blit
GPU_Flip
GPU_Quit

These will be everything we need to put together a simple simulation to play with.

If you’ve used SDL’s built-in rendering before, then SDL_gpu shouldn’t feel too different.  They do the same basic things, but SDL_gpu has a lot more functionality and a few different conventions.  The most blatant convention difference you’ll see is that GPU_Blit() and the other blitting functions draw sprites at their center instead of from the upper-left corner.  This doesn’t actually change much in your code, but makes scaling and rotating sprites work without hassle.

Initialization

void GPU_SetDebugLevel(GPU_DebugLevelEnum level);
GPU_Target* GPU_Init(Uint16 w, Uint16 h, GPU_WindowFlagEnum SDL_flags);

There are a couple of ways that SDL_gpu lets you know when something goes wrong.  Some functions can have an obvious return value when there’s an error.  E.g. GPU_Init() will return a NULL render target on error.  SDL_gpu also maintains an error stack that you can check manually (sometimes a silent program is bliss). But we’re just starting off! We need to know right when errors occur. With GPU_SetDebugLevel(), we can tell SDL_gpu to print out the errors just as they happen.  That will help us catch some mistakes that we might make along the way.

GPU_Init() is the easy way to get a window and render target created.  All you have to do is pass in the width and height of the window you want.  You can also pass in some SDL window flags if you want to.  This will set everything up and choose the best renderer that is available.

So here’s a snippet of those two functions. Once you have #include “SDL_gpu.h” and set up your main() function:

// Tell us whenever something bad happens
GPU_SetDebugLevel(GPU_DEBUG_LEVEL_MAX);
// Get a 800x600 window to render to
GPU_Target* screen = GPU_Init(800, 600, GPU_DEFAULT_INIT_FLAGS);

Rendering an image

Now we would like to draw something on the screen. To render to our new window, we need either an image or we can draw shapes.  Here’s what we need for drawing images:

GPU_Image* GPU_LoadImage(const char* filename);
void GPU_Clear(GPU_Target* target);
void GPU_Blit(GPU_Image* image, GPU_Rect* src_rect, GPU_Target* target, float x, float y);
void GPU_Flip(GPU_Target* target);

GPU_LoadImage() of course loads images. It currently supports .png, .jpg, .tga, .gif, and .bmp image files. Before each frame is drawn, we might want to use GPU_Clear() to erase the contents of the screen target. In some cases you don’t need to, especially when you are drawing over the entire screen every frame (e.g. with a background image).  When we pass our new image and the screen target to GPU_Blit(), we get the image rendered right where we want it.  To actually see what we rendered, we need to GPU_Flip() the screen buffer so it is shown in the window.

// Load the image...
// (Make sure that you have some image named test_image.png in the executable's directory)
GPU_Image* image = GPU_LoadImage("test_image.png");
// Clear the screen
GPU_Clear(screen);
// Remember that GPU_Blit() draws the image *centered* at the given position
GPU_Blit(image, NULL, screen, image->w/2, image->h/2);
// Show the result in the window
GPU_Flip(screen);

With that, we can technically see something drawn…

Shutting down

void GPU_Quit(void);

Let’s not forget to shut down SDL_gpu at the end. GPU_Quit() will free up the resources that SDL_gpu was using.

Source

Here’s what your complete program might look like now with some usual SDL stuff thrown in:

#include "SDL_gpu.h"
int main(int argc, char** argv)
{
    // Tell us whenever something bad happens
    GPU_SetDebugLevel(GPU_DEBUG_LEVEL_MAX);
    // Get a 800x600 window to render to
    GPU_Target* screen = GPU_Init(800, 600, GPU_DEFAULT_INIT_FLAGS);
    // Did initialization fail?
    if(screen == NULL)
        return 1;
    // Load the image...
    // (Make sure that you have some image named test_image.png in the executable's directory)
    GPU_Image* image = GPU_LoadImage("test_image.png");
    if(image == NULL)
        return 2;
    SDL_Event event;
    Uint8 done = 0;
    while(!done)
    {
        while(SDL_PollEvent(&event))
        {
            if(event.type == SDL_QUIT)
                done = 1;
        }
        // Clear the screen
        GPU_Clear(screen);
        // Remember that GPU_Blit() draws the image *centered* at the given position
        GPU_Blit(image, NULL, screen, image->w/2, image->h/2);
        // Show the result in the window
        GPU_Flip(screen);
    }
    GPU_Quit();
    return 0;
}

Tags: ,

SDL_gpu has its first binary release!   If you make games or applications in C or C++, you should give it a spin.

In case you aren’t familiar with it yet, SDL_gpu is an alternative rendering system for SDL 1.2 and SDL 2.0.   It includes everything you need to make amazing, high-performance 2D graphics:

  • Fast buffered blits
  • Shader support
  • Sprite rotation and scaling
  • Plenty of 2D primitives (e.g. lines, arcs, circles, rounded rectangles, annulus sectors)
  • Render-to-Texture
  • An enjoyable basic abstraction (targets and images)
  • Virtual resolution and viewports
  • 2D camera control
  • Built-in support for some popular image formats
  • Blend, filter, and wrap modes
  • Arbitrary 2D geometry (batched textured triangles)
  • Can be used with native OpenGL calls

You can get started at the Google Code page.  There’s some introductory info, documentation, and downloads there.

Tags: , ,

9
Dec

Multiple windows with SDL2

   Posted by: in Dev

Just messing around getting ready to put multiple window support into SDL_gpu…  I couldn’t find a good reference for how to use multiple windows, even in SDL2’s demos.  So here’s what I put together for a functioning demo (warning: no error-checking).  This uses SDL2’s built-in rendering system (SDL_Renderer) so I could explore the limitations that I need to work with.

#include "SDL.h"
typedef struct Sprite
{
    SDL_Texture* texture;
    float x, y;
    float velx, vely;
} Sprite;
typedef struct Group
{
    Uint32 windowID;
    SDL_Renderer* renderer;
    Sprite sprite;
} Group;
#define screen_w 300
#define screen_h 300
#define sprite_w 100
#define sprite_h 100
Group create_group()
{
    Group g;
    SDL_Window* window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_w, screen_h, 0);
    g.windowID = SDL_GetWindowID(window);
    SDL_Log("New windowID: %u\n", g.windowID);
    g.renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    SDL_Surface* surface = SDL_LoadBMP("data/test.bmp");
    g.sprite.texture = SDL_CreateTextureFromSurface(g.renderer, surface);
    g.sprite.x = rand()%screen_w;
    g.sprite.y = rand()%screen_h;
    g.sprite.velx = 10 + rand()%screen_w/10;
    g.sprite.vely = 10 + rand()%screen_h/10;
    SDL_FreeSurface(surface);
    return g;
}
int main(int argc, char* argv[])
{
    int max_groups = 30;
    Group groups[max_groups];
    memset(groups, 0, sizeof(Group)*max_groups);
	int num_groups = 0;
	groups[num_groups] = create_group();
	num_groups++;
	int i = 0;
	float dt = 0.010f;
	Uint32 startTime = SDL_GetTicks();
	long frameCount = 0;
    Uint8 done = 0;
	SDL_Event event;
	while(!done)
	{
		while(SDL_PollEvent(&event))
		{
			if(event.type == SDL_QUIT)
				done = 1;
			else if(event.type == SDL_KEYDOWN)
			{
				if(event.key.keysym.sym == SDLK_ESCAPE)
					done = 1;
				else if(event.key.keysym.sym == SDLK_EQUALS || event.key.keysym.sym == SDLK_PLUS)
				{
					if(num_groups < max_groups)
                    {
                        groups[num_groups] = create_group();
                        num_groups++;
                        SDL_Log("num_groups: %d\n", num_groups);
                    }
				}
				else if(event.key.keysym.sym == SDLK_MINUS)
				{
					if(num_groups > 0)
                    {
                        for(i = max_groups-1; i >= 0; i--)
                        {
                            if(groups[i].windowID != 0)
                            {
                                SDL_DestroyTexture(groups[i].sprite.texture);
                                SDL_DestroyRenderer(groups[i].renderer);
                                SDL_DestroyWindow(SDL_GetWindowFromID(groups[i].windowID));
                                groups[i].windowID = 0;
                                num_groups--;
                                SDL_Log("num_groups: %d\n", num_groups);
                                break;
                            }
                        }
                        if(num_groups == 0)
                            done = 1;
                    }
				}
			}
			else if(event.type == SDL_WINDOWEVENT)
            {
                if(event.window.event == SDL_WINDOWEVENT_CLOSE)
                {
                    Uint8 closed = 0;
                    for(i = 0; i < max_groups; i++)
                    {
                        if(groups[i].windowID != 0 && groups[i].windowID == event.window.windowID)
                        {
                            SDL_DestroyTexture(groups[i].sprite.texture);
                            SDL_DestroyRenderer(groups[i].renderer);
                            SDL_DestroyWindow(SDL_GetWindowFromID(groups[i].windowID));
                            groups[i].windowID = 0;
                            closed = 1;
                            num_groups--;
                            SDL_Log("num_groups: %d\n", num_groups);
                            break;
                        }
                    }
                    // The main window was closed, then.
                    if(!closed || num_groups == 0)
                        done = 1;
                }
            }
		}
		for(i = 0; i < max_groups; i++)
		{
			groups[i].sprite.x += groups[i].sprite.velx*dt;
			groups[i].sprite.y += groups[i].sprite.vely*dt;
			if(groups[i].sprite.x < 0)
			{
				groups[i].sprite.x = 0;
				groups[i].sprite.velx = -groups[i].sprite.velx;
			}
			else if(groups[i].sprite.x > screen_w)
			{
				groups[i].sprite.x = screen_w;
				groups[i].sprite.velx = -groups[i].sprite.velx;
			}
			if(groups[i].sprite.y < 0)
			{
				groups[i].sprite.y = 0;
				groups[i].sprite.vely = -groups[i].sprite.vely;
			}
			else if(groups[i].sprite.y > screen_h)
			{
				groups[i].sprite.y = screen_h;
				groups[i].sprite.vely = -groups[i].sprite.vely;
			}
		}
		for(i = 0; i < max_groups; i++)
		{
		    if(groups[i].windowID == 0)
                continue;
		    SDL_RenderClear(groups[i].renderer);
		    SDL_Rect dstrect = {groups[i].sprite.x, groups[i].sprite.y, sprite_w, sprite_h};
		    SDL_RenderCopy(groups[i].renderer, groups[i].sprite.texture, NULL, &dstrect);
		    SDL_RenderPresent(groups[i].renderer);
		}
		SDL_Delay(10);
	}
    for(i = 0; i < max_groups; i++)
    {
        if(groups[i].windowID == 0)
            continue;
        SDL_DestroyTexture(groups[i].sprite.texture);
        SDL_DestroyRenderer(groups[i].renderer);
        SDL_DestroyWindow(SDL_GetWindowFromID(groups[i].windowID));
    }
    SDL_Quit();
    return 0;
}

In case that doesn’t display well in blog format… Download the source here: sdl2_multiwindow

Tomorrow is the release day for Gladiator!  Woo!  A lot has gone into the code to get it ready, but it’s finally there.

I’m looking through the changelog and it’s pretty crazy.  I spent a lot of time making a whole new level editor, but in order to do that, I had to refactor a ton of data structures to hold character, level, and campaign info.  New features came in that changed the feel of the game in several positive ways.  The player is given much more information both in and out of combat so they can control their team more effectively and track and enjoy the team’s progress.  And of course, real controller integration on OUYA is sweet.  Overall, at least a hundred bugs were squashed, and I think I only made 80% of them! 😀

So, it’s nice to bring the Openglad project to this point.  I love playing it on OUYA with my kids and I really hope it catches on with a significant audience.  I’m just waiting for another approval of this latest version to be put into the OUYA market (Discover), then we should be seeing it live tomorrow!

Lately, as I’ve gotten the time, I’ve been pounding away at Gladiator (Openglad), a cool retro action RPG that I loved as a kid.  It’s amazing that after all these years (um, 18 since the original release?), I can pick up the code and improve the game I played so much so long ago.

I released a version on Google Play, the Amazon Appstore, and the NOOK Store.  I have been working on a new level editor for a bit.  It’s incredibly improved from the old one; it feels much better to use and has more features.  Best of all, it is now integrated into the game itself.  I’ll be releasing an update to the Android version that includes the level editor.  Also, there are plans to include a sharing system so you can create and share your own levels and campaigns as well as download and rate ones that other people make.

Another bit of news is that I bought an OUYA!  Incidentally, that also means I’m working on the OUYA port of Gladiator…  Yeah, it’s pretty awesome with 4 players smashing around on a big TV.  The downside to mobile Android really is the lack of local multiplayer, so I’m really pleased to have the OUYA as an outlet for the PvP and cooperative multiplayer experiences that I love.  OUYA porting is mostly controller integration work, which can get hairy when it comes to menus, but it’s already working well.  I have a few more details to smooth out, then it’ll be go time.

Tags: , , , , , , ,

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: , ,

We just released our first app for Android (see the News post about that)!  I’m happy about that, but ugh, I feel bad for neglecting Don’t Blow It for so long.  Even so, it needs to wait a little longer.  Android has been where all my programming time has gone lately.  A big part of that work was getting my OpenGL wrapper library, SDL_gpu, further developed and working with OpenGLES.  It’s actually quite nice to use and it covers all of the features I currently need.

It was pretty exciting to get stuff running on my Kindle Fire HD.  I even ported Don’t Blow It to Android just to see how it would go.  It actually turned out great, but I won’t plan on releasing an Android version due to all the extra work it will be to get gamepads working and the game performance tuned and all that.  Maybe there will be an Ouya version eventually.  I’m really not a fan of games being brought to a touch device when the game is not designed for it (e.g. anything with a virtual joystick).  They can still be fun and worthwhile, but input works way better when it feels good and natural.

I did some fishing around for more Android devices to test our app on and I managed to get my mitts on a Samsung Vibrant (Galaxy S).  It was great fun to brick it for a while (sarcasm), then figure out how to get CyanogenMod on it.  In the end, I got a brand new version of Android on a phone that is not bad at all (relative to my sweet Nokia 1100).  It helped to fix a few bugs and now the app is out there for Kindle Fires and other Android devices.  We have already learned something concrete about the app market.  Discoverability stinks.  We will need to do quite a bit of marketing to make any sort of money on this app.

I’ll probably make a game on Android next.  I still have to see what kind of income we can manage when we do it right.