Simultaneously supporting Android/OUYA and desktop game inputs using KeyActionBinder

OUYA and XBox 360 controllers

As I mentioned in my previous post, one of the advantages of abstracting game input with a class like KeyActionBinder is that supporting more than one platform is a fairly easy, one-step process: you simply specify the expected inputs during setup of the key binder.

I’m currently building a (fairly fun!) prototype and I plan on running it on the OUYA (and other Android devices) as well as the desktop (I haven’t tested iOS’ new gamepad support yet; I’m not sure Adobe AIR already supports it). The cool thing is that I’m able to use the exact same code for both versions; most of the times, I test the game on the desktop, but I run it on the OUYA once in a while to test performance (this is easier with a second monitor properly connected). This is how I do it.

First, I create the ids for the actions I’ll be using, and of course a variable to hold the KeyActionBinder instance I’ll be using.

// Constants
private static const ACTION_LEFT:String = "left";
private static const ACTION_RIGHT:String = "right";
private static const ACTION_JUMP:String = "jump";

// Instances
private var binder:KeyActionBinder;

Then, during the startup code of my game code, I set up the actions depending on the platform the application is running in, using KeyActionBinder and the accompanying GamepadControls class which contains a list of standard controls.

private function startGame(): void {
	var isAndroid:Boolean = Capabilities.manufacturer == "Android Linux";
	// This can also be : "Adobe iOS" (iOS), "Adobe Windows" (PC Windows), "Adobe Macintosh" (PC Mac)

	// Setup input
	binder = new KeyActionBinder(stage);

	if (isAndroid) {
		// Android and OUYA: use native controller
		binder.addGamepadActionBinding(ACTION_LEFT, GamepadControls.DPAD_LEFT);
		binder.addGamepadActionBinding(ACTION_RIGHT, GamepadControls.DPAD_RIGHT);
		binder.addGamepadActionBinding(ACTION_JUMP, GamepadControls.BUTTON_ACTION_DOWN);
	} else {
		// PC: use keyboard and XBox 360 controller
		binder.addKeyboardActionBinding(ACTION_LEFT, Keyboard.LEFT);
		binder.addKeyboardActionBinding(ACTION_RIGHT, Keyboard.RIGHT);
		binder.addKeyboardActionBinding(ACTION_JUMP, Keyboard.SPACE);

		binder.addGamepadActionBinding(ACTION_LEFT, GamepadControls.XBOX_DPAD_LEFT);
		binder.addGamepadActionBinding(ACTION_RIGHT, GamepadControls.XBOX_DPAD_RIGHT);
		binder.addGamepadActionBinding(ACTION_JUMP, GamepadControls.XBOX_BUTTON_ACTION_A);
	}
}

Then, during my main game loop, I set the speed of the player accordingly, and activate jump actions as needed (in this case I’m using a Box2D-based game, hence the interface being used by playerBody).

private function gameLoop():void {
	var newSpeed:Number = 0;
	var currSpeed:Number = playerBody.GetAngularVelocity();

	// Movement
	if (binder.isActionActivated(ACTION_LEFT)) {
		newSpeed = 10;
		playerBody.SetAngularVelocity(Math.max(currSpeed, currSpeed - (currSpeed - newSpeed) / 20));
	}
	if (binder.isActionActivated(ACTION_RIGHT)) {
		newSpeed = -10;
		playerBody.SetAngularVelocity(Math.min(currSpeed, currSpeed - (currSpeed - newSpeed) / 20));
	}

	// Jump
	if (binder.isActionActivated(ACTION_JUMP)) {
		// Check the number of current "hits" to the player body (meaning it's touching the ground or a wall)
		if (playerBody.GetUserData()["hits"] > 0) {
			// Jumps in any direction, using the last impact normal
			var jmpn:b2Vec2 = playerBody.GetUserData()["contactNormal"].Copy();
			jmpn.x *= 40;
			jmpn.y *= 40;
			playerBody.ApplyImpulse(jmpn, playerBody.GetWorldCenter());
		}
	}
}

With the above code, I’m able to run the project with no modifications and run both on the desktop, using an XBox controller (or the keyboard), and on the OUYA, using the native controller. The result, using a fairly ghetto “level” loaded from a R.U.B.E. file, looks like so (the circle in the middle is playerBody):

Game prototype level

The code for the player input above controls the circle’s rotation speed and ability to jump from any surface. Physics wise, it’s still a prototype, but with the simple player input code in place, it’s much easier to test across a variety of platforms.

Note: it must be said that the GamepadControls constants I used in the above code will probably change names in the future, as I add more controls and properly differentiate between desktop and Android/OUYA controls.

5 responses

  1. Hi zeh !

    I see you’re using manufacturer,
    I chose Capabilities.version.slice(0, 3); (which should be restricted to WIN/LNX/MAC/AND according to the asdocs) for my input system.

    did you consider it and if so is there anything wrong with that ?
    (you’ve been working with gamepads longer than I have so you might know if this is going to limit things a lot) .

    Now that I see you too are using the concept of “actions” identified by strings, it makes me feel less lonely, and again thanks for sharing that spreadsheet with us

    • Hey gsynuh, thanks for the comment.

      That code using manufacturer was just a quick code rewrite I did just for the example without giving too much thought. But in reality they should always work the same since it *is* always “AND*” for .version and “Android Linux” for .manufacturer according to my research (check the gamepad spreadsheet, it has a second “device” tabs with some of that data). So there’s nothing wrong with that.

      On my actual KeyActionBinder class (separate branch for now) I’m doing something a bit more involved:
      https://github.com/zeh/key-action-binder/blob/auto-controls/src/com/zehfernando/input/binding/KeyActionBinder.as

      Every platform profile can have any number of filters that are applied not only to .manufacturer, but also .os and .version. Right now the filters are super strict (both .manufacturer *and* .version *and* .os have to be the right ones), but it’s just while I’m still writing the rest of the platform filters. I’ll probably make it more lenient later; I just wanted to have the options, for easier maintenance.

      I do believe .version (and maybe sometimes .os in the future because of Windows 8) is all one will need though.

      And I agree – the thing with using abstract actions can look strange to some but it’s the best approach. It’s what I’ve been doing for a while with a few other classes and it just helps with portability. It also allows custom binding; maybe I’m too much of a PC FPS player, but custom user binding for me is something of the utmost importance, even if it’s not actually *exposed* to the player through a menu.

Comments are closed.