Adding iCade Support to Your Game

Jake Gundersen Jake Gundersen

This is a blog post by iOS Tutorial Team member Jacob Gundersen, an indie game developer who runs the Indie Ambitions blog. Check out his latest app – Factor Samurai!

Learn how to make a game compatible with the iCade!

Learn how to make a game compatible with the iCade!

The iCade is a miniature arcade cabinet for your iPad. It communicates with the iPad over Bluetooth, and allows you to play iCade-compatible games with button-mashing fun!

The iCade may be the most widely-supported external game controller for iOS devices today. Over 70 games are iCade-compatible, and more coming all the time!

Recently the creators of the iCade announced several new types of iCade devices. I’m excited to see these new devices, and I hope that more developers will integrate iCade support into their games.

And that got me thinking… I should write a quick tutorial showing you guys how to do exactly that! :]

Making your game iCade-compatible has the following benefits:

  • Widespread support. Besides the iCade (and now the new devices) the iControlPad and even this retro NES controller can emulate the iCade interface. So if you’re compatible with one, you’re compatible with all!
  • Good for indies. As indie developers trying to get our games noticed, adding iCade support isn’t a bad strategy, as the list of games that do support it is still relatively small. Only about 70 games are currently listed, so your game could be this small list for iCade owners hungry for compatible games!
  • Joysticks rule! Perhaps the biggest reason to support the iCade is that touchscreen joysticks are a miserable way to play a game that’s designed to use a joystick interface. I’ve been itching to create a game that uses the iCade since I got one a few months ago, so as part of the Platformer Starter Kit I’m working on (and announcing this very second!), I’m integrating support for the iCade.

To run through this tutorial, you will need an iCade. You can get one on Amazon.com for about $80. But if you’re a retro gamer, it’s worth it ;]

Convinced yet? Bring out your iCade and let’s get started! :]

Getting Started

The game we will be making iCade compatible.

The game we will be making iCade compatible.

In this project, you’re going to modify the Raycast game from this tutorial.

However, don’t download the project from that tutorial! Instead, download this slightly modified version.

Here I’ve added some extra files we need for the tutorial – some button images, and the iCade interface library (which we’ll discuss in a minute).

In this tutorial, first you’re going to refactor the controls to use an on-screen joystick instead of the current simple touch interface. Then you’ll integrate iCade support as well!

Note that your game will still work with the on-screen joystick, so you won’t absolutely need iCade to play it. It will just be a nice bonus for those who have it :]

Note: According to the iCade support document, you can’t mention iCade in any way in the iTunes description of your app. Does anyone know if this is true (or can you get away with it)?

By the end of this tutorial, users will be able to play your game with the onscreen controls or the iCade, and the interface will function similarly in both cases.

iCade Overview

Before we begin, let’s discuss the basics of how iCade and its interface library work.

The iCade emulates a Bluetooth keyboard. There are four joystick directions (up, down, left, right) and there are eight arcade buttons. Each press or release of a button sends a character as if it had been pressed on a Bluetooth keyboard.

So, for example, pressing the joystick up sends ‘W’ and releasing the joystick back to center sends ‘E’, as you can see in this diagram from the iCade Developer’s PDF:

The characters are only sent once, not continuously. So it is up to your code to keep track of which buttons are currently pressed or released. You can see all the key mappings here.

In order to receive button-presses from the iCade (again, which acts as a Bluetooth keyboard), you have to create a view that receives this text input. Luckily, you don’t have to write this code yourself though, because there’s a great library you can use!

iCade Interface Library Overview

Stuart Carnie has written a great library that makes it much easier to work with iCade input, and he deserves the thanks of all who are reading this tutorial!

I’ve already integrated his library into the starter project, but you should download the project from Github anyway, because you’ll need the test project that comes with it in a second.

The main class you’ll be using is the iCadeReaderView. This is a UIView subclass that implements the UIKeyInput delegate protocol. You will create this view and add it as a subclass to your glView. It will receive these keystrokes (invisibly) and keep track of the state information.

There are two ways of accessing the state of the iCadeReaderView class:

  1. The iCadeState property. This property is a bitmask that keeps track of all the buttons that are currently pressed.
  2. The iCadeEventDelegate protocol. You can also implement the iCadeEventDelegate protocol and set yourself as the delegate. The delegate will receive messages every time the state changes (both when buttons are pressed and when they are released).

I find that this second method is easier to use. If we were to query the iCadeState property, we’d need do so within each update method. If we use the delegate, we can push the information from the iCadeReaderView class to whatever class we want (like the HUD) and keep track of the state there.

To get started, let’s go through the process of testing the iCade Bluetooth pairing and looking at the properties you can use in the class.

We’ll go over pairing the iCade as a Bluetooth device and using the iCadeReaderView test project to take a look at how it works. You can safely skip this section if you’re comfortable with Bluetooth pairing and the basic communication with the iCadeReaderView class.

Pairing the iCade with Your iOS Device

On your iPad (or iPhone – this all works there as well), go into your Settings app and choose General, then Bluetooth. Switch Bluetooth on if it’s not already. Your iPad will say Searching… under the devices list. Now we’ll go to the iCade.

Press the bottom four buttons and the top white button (far right) and hold them down at the same time. This puts the iCade into pairing mode. The coin slot will start to blink if this is done right.

On your iPad, an alertview will pop up and give you a four-digit pin to enter into the iCade. This is done with the buttons and the joystick. There’s a graphic that illustrates the mapping between the numbers and the buttons.

Once you’ve put in the pin and pressed enter (either white button), the iPad should have a device in the list named ‘ION iCade Game Controller,’ and the status should be connected. If it’s not connected, choose the device from the devices list on the iPad, and iPad will search for it.

Also, the iCade may have turned itself off. Press any button on the iCade to turn it back on. You’ll know it’s on because the coin slot will be lit. That should do it – you are now paired with your iCade.

All of these pairing instructions are written on the underside of the top of the iCade cabinet, along with the graphic that shows the numbers mapping for the pin entry.

Keep in mind that this is a Bluetooth keyboard, so any text input will no longer bring up the onscreen keyboard. In order to get it back, you’ll have to disconnect from the iCade, so download any games or anything that requires you type before you do all of this. The easiest way (that I’ve found) to disconnect the iCade is to turn Bluetooth off.

Testing the iCade Library

Next, open up the iCadeTest project you downloaded from Github and run it on your iPad in debug mode.

You should see a UI representation of the iCade controller on the screen like this:

iCadeTest program

As you move your joystick controller, you should see the UI update appropriately. For example, in the screenshot above I have the upper right white button pressed down.

If this works, you’re finally ready to integrate iCade into our simple game!

Adding a Touchscreen Controller

If you haven’t already, download the starter project and open it up in Xcode.

The first thing you’re going to do is remove the touch responder methods from ActionLayer.mm. Go ahead and comment out touchesBegan, touchesMoved, touchesEnded, and touchesCancelled. You can also remove self.isTouchEnabled = YES; from – (id)initWithHUD:(HUDLayer *).

Later we’ll be adding new methods that move and jump the player, but for now we’re moving the touch responder to the HUD layer.

You’re now going to add the buttons to the HUD layer. Open HUDLayer.h and add the following instance variables so your code looks like this:

@interface HUDLayer : CCLayer {
    CCLabelBMFont * _statusLabel;
    CCSprite *leftButton;
    CCSprite *rightButton;
    CCSprite *jumpButton;
 
    NSArray *buttons;
}
 
- (void)showRestartMenu:(BOOL)won;
- (void)setStatusString:(NSString *)string;
 
@end

Then change init in HUDLayer.mm to the following:

- (id)init {
 	if ((self = [super init])) {
		self.isTouchEnabled = YES;
 
        leftButton = [CCSprite spriteWithFile:@"leftButton.png"];
        rightButton = [CCSprite spriteWithFile:@"rightButton.png"];
        jumpButton = [CCSprite spriteWithFile:@"jumpButton.png"];
 
        buttons = [[NSArray alloc ] initWithObjects:leftButton, rightButton, jumpButton, nil];
 
        for (CCSprite *s in buttons) {
            s.opacity = 127;
        }
 
        CGSize winSize = [CCDirector sharedDirector].winSize;
 
        _statusLabel = [CCLabelBMFont labelWithString:@"" fntFile:@"Arial.fnt"];
        leftButton.position = ccp(50, 50);
        rightButton.position = ccp(150, 50);
        jumpButton.position = ccp(440, 50);
 
        leftButton.scale = 0.5;
        rightButton.scale = 0.5;
        jumpButton.scale = 0.5;
 
 
        [self addChild:jumpButton];
        [self addChild:leftButton];
        [self addChild:rightButton];
 
        _statusLabel.position = ccp(winSize.width* 0.85, winSize.height * 0.9);
        [self addChild:_statusLabel]; 
	 }
    return self;
}

Nothing earth shattering here: just adding the buttons. You’re setting the opacity to half (127) and you’ll set it back to full (255) when a button is pressed. Also, you’re adding the buttons to an array, just to make it easier to process touches later on.

If you look at the images in a photo editor, you’ll see that they’re surrounded by a bunch of transparent space. This is to make it easier to press the buttons.

Build and run now. You should have a screen that looks like this:

If you try to touch buttons, nothing happens. To fix that, add the following touch code methods, starting with the two simpler ones, touchesBegan and touchesEnded:

- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    
    for (UITouch *t in touches) {
 
        CGPoint touchLocation = [self convertTouchToNodeSpace:t];
 
        for (CCSprite *s in buttons) {
            if (CGRectContainsPoint(s.boundingBox, touchLocation)) {
                s.opacity = 255;
                int buttIndex = [buttons indexOfObject:s];
                if (buttIndex == 2) {
                    [delegate heroJump];
                } else if (buttIndex == 1) {
                    [delegate heroMove:kDirectionRight];
                } else if (buttIndex == 0) {
                    [delegate heroMove:kDirectionLeft];
                }
 
            } 
        }
    }
}
 
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
 
    for (UITouch *t in touches) {
 
        CGPoint touchLocation = [self convertTouchToNodeSpace:t];
 
        for (CCSprite *s in buttons) {
            if (CGRectContainsPoint(s.boundingBox, touchLocation)) {
                s.opacity = 127;
                int buttIndex = [buttons indexOfObject:s];
 
                if (buttIndex == 1 || buttIndex == 0) {
                    [delegate heroMove:kDirectionNone];
                } 
            }
        }
    }
}

These two methods are mirror images of each other. You are using multitouch, iterating through the whole set of touches. You’ll need to be able to hit a direction and jump at the same time. You are also iterating through your buttons and looking at whether your touch begins or ends inside of a button sprite.

If you get a hit, you first change the opacity of the button. Next, you test which button you are currently working with by its index in the array, and then you send the appropriate message to your delegate based on whether you’re hitting left, right, jump, or releasing any of these buttons.

We’ll get to your delegate protocol in a minute.

These methods take care of touches that start and end on a single button. To deal with the case where a touch starts on one button and ends on another, we need to add a ccTouchMoved callback.

This callback needs to deal with two situations:

  1. What if the player touches the right button, and then slides onto the left button? In this case, we want the right button to turn off, and the left button to turn on.
  2. What if the player slides onto the jump button? In this case (and this is a design decision – you could choose to do otherwise), there shouldn’t be another jump. The player needs to release the screen and start a new tap to trigger a second jump.

This behavior better mirrors the interface of a physical controller: sliding between directional buttons changes direction, but a new jump action requires a release and press.

So to accomplish this, add the ccTouchesMoved callback like so:

- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    for (UITouch *t in touches) {
 
        CGPoint touchLocation = [self convertTouchToNodeSpace:t];
 
        //get previous touch and convert it to node space
        CGPoint previousTouchLocation = [t previousLocationInView:[t view]];
        CGSize screenSize = [[CCDirector sharedDirector] winSize];
        previousTouchLocation = ccp(previousTouchLocation.x, screenSize.height - previousTouchLocation.y);
 
        for (CCSprite *s in buttons) {
            if (CGRectContainsPoint(s.boundingBox, previousTouchLocation) && 
                !CGRectContainsPoint(s.boundingBox, touchLocation)) {
 
                s.opacity = 127;
                int buttIndex = [buttons indexOfObject:s];
 
                if (buttIndex == 1 || buttIndex == 0) {
                    [delegate heroMove:kDirectionNone];
                } 
            }
        }
 
        for (CCSprite *s in buttons) {
            if (!CGRectContainsPoint(s.boundingBox, previousTouchLocation) && 
                CGRectContainsPoint(s.boundingBox, touchLocation)) {
 
                s.opacity = 255;
                int buttIndex = [buttons indexOfObject:s];
 
                //We don't get another jump on a slide on, we want the player to let go of the button for another jump
                if (buttIndex == 1) {
                    [delegate heroMove:kDirectionRight];
                } else if (buttIndex == 0) {
                    [delegate heroMove:kDirectionLeft];
                }
 
            } 
        } 
    }
}

This method is a hybrid of the two previous ones. One difference is that there are two touch locations. You’re getting the previous touch location along with the current one.

The first part tests when the player slides off a button they were previously touching. If that’s the case, we want to turn that button back to half opacity and send a message to the delegate that the player is no longer touching a direction button.

There’s no need to know which direction button has been released, so the one message will do. (We’re assuming that pressing the new direction happens after releasing the old one. If this isn’t true, we might accidentally turn off the new direction press).

You don’t need to send a message that the player is no longer pressing the jump button, because you’re applying an impulse on the first touch of the jump button, and not sending another until the button has been released and touched again.

The second block of code tests whether the player has pressed a new button. In this case, you do need to know which button is being pressed, so you find the position in the array and send the appropriate message.

For the touch methods to work properly, you need to turn on multitouch. If you don’t, the player can only touch one button at a time.

Turning on multitouch is done in AppDelegate.mm. Find the line that runs the first scene, [[CCDirector sharedDirector] runWithScene: [ActionLayer scene]]; and add the following line before it:

[glView setMultipleTouchEnabled:YES];

One last thing you need to do in order to run this code (otherwise you’ll get an error) is add your delegate protocol. Change HUDLayer.h to the following:

#import "cocos2d.h"
 
typedef enum {
    kDirectionLeft,
    kDirectionRight,
    kDirectionNone
} ControlDirection;
 
@protocol ControlsDelegate
 
-(void)heroMove:(ControlDirection)direction;
-(void)heroJump;
 
@end
 
@interface HUDLayer : CCLayer {
    CCLabelBMFont * _statusLabel;
 
    CCSprite *leftButton;
    CCSprite *rightButton;
    CCSprite *jumpButton;
 
    NSArray *buttons;
 
    id <ControlsDelegate> delegate;
}
 
- (void)showRestartMenu:(BOOL)won;
- (void)setStatusString:(NSString *)string;
 
@property (assign) id <ControlsDelegate> delegate;
 
@end

In the above code, first you add an enum that will be used to send the direction of the button to the delegate. Then you create the protocol.

You add two methods to the protocol, one for jump and one for direction. Then you add an instance variable for your delegate, and a property so it can be set by its parent.

The only other thing you must do is add @synthesize to the HUDLayer.mm:

@synthesize delegate;

You can now build and run. Your buttons won’t yet make the hero move, because you still need to implement the logic on the delegate end, but they should now respond to touch by changing opacity.

Implementing the ControlsDelegate Protocol

Now you’re going to set up the delegate on the ActionLayer. First you need to set the delegate in initWithHud. Change that method to the following:

- (id)initWithHUD:(HUDLayer *)hud
{
    if ((self = [super init])) {
        _hud = hud;
        _hud.delegate = self;  //here's our new line
        // Rest of method...
        [self setupWorld];
        [self setupLevelHelper];
        [self setupDebugDraw];
        [self setupAudio];
        [self scheduleUpdate];
        //self.isTouchEnabled = YES;
        _lives = 3;
        [self updateLives];
    }
    return self;
}

When you do this, you’ll immediately get an error telling you that the ActionLayer class doesn’t implement the ControlsDelegate. Switch to the header and change the interface line:

@interface ActionLayer : CCLayer <ControlsDelegate> {

That takes care of the error. Now implement the methods. There are two:

#pragma mark ControlsDelegate methods
 
-(void)heroMove:(ControlDirection)direction {
    if (direction == kDirectionLeft) {
        _playerVelX = -MOVE_POINTS_PER_SECOND;
        _hero.flipX = YES;
    } else if (direction == kDirectionRight) {
        _playerVelX = MOVE_POINTS_PER_SECOND;
        _hero.flipX = NO;
    } else {
        _playerVelX = 0;
    }
}
 
-(void)heroJump {
    _heroBody->ApplyLinearImpulse(b2Vec2(_playerVelX/[_lhelper pixelsToMeterRatio], 1.25), _heroBody->GetWorldCenter());
    [[SimpleAudioEngine sharedEngine] playEffect:@"wing.wav"];
    [_lhelper startAnimationWithUniqueName:@"Flap" onSprite:_hero];
}

This code should look familiar if you did the HUDLayer tutorial that it comes from, but if you didn’t, I’ll explain.

The first method sets the _playerVelX property. In updateHero: this property is used to set the hero’s x velocity. He’ll move that distance each frame. This code also changes the hero’s sprite so that he’s facing the direction he’s moving. If a button is released, then the hero’s x velocity is set to 0 (don’t move).

The jump method applies a linear impulse, pushing the hero up. It plays a wing-flapping sound and turns on a wing-flapping animation. The player can release and tap this button to fly around.

Build and run now – you should be able to fly around the level!

Can We Do iCade Now!!?

Now, finally on to the iCade, the hardest part. Just kidding – this is actually the easiest part (again, thanks to Stuart Carnie). You’re going to use the iCadeEventDelegate methods to interact with the iCadeReaderView.

In HUDLayer.h, import iCadeReaderView.h, and set the HUDLayer as implementing the iCadeEventDelegate protocol:

#import "iCadeReaderView.h"
 
@interface HUDLayer : CCLayer <iCadeEventDelegate> {

Then implement these two methods from the delegate protocol in HUDLayer.mm:

-(void)buttonDown:(iCadeState)button {
    if (button == iCadeJoystickLeft) {
        [delegate heroMove:kDirectionLeft];
        leftButton.opacity = 255;
    } else if (button == iCadeJoystickRight) {
        [delegate heroMove:kDirectionRight];
        rightButton.opacity = 255;
    } else if (button == iCadeButtonA || 
               button == iCadeButtonB || 
               button == iCadeButtonC || 
               button == iCadeButtonD || 
               button == iCadeButtonE || 
               button == iCadeButtonF || 
               button == iCadeButtonG || 
               button == iCadeButtonH) {
        [delegate heroJump];
        jumpButton.opacity = 255;
    } 
}
 
-(void)buttonUp:(iCadeState)button {
    if (button == iCadeJoystickLeft) {
        [delegate heroMove:kDirectionNone];
        leftButton.opacity = 127;
    } else if (button == iCadeJoystickRight) {
        [delegate heroMove:kDirectionNone];
        rightButton.opacity = 127;
    } else if (button == iCadeButtonA || 
               button == iCadeButtonB || 
               button == iCadeButtonC || 
               button == iCadeButtonD || 
               button == iCadeButtonE || 
               button == iCadeButtonF || 
               button == iCadeButtonG || 
               button == iCadeButtonH) {
        jumpButton.opacity = 127;
    } 
}

Each time a button is pressed, the buttonDown method is called and passes the button (a bitmask) as a parameter. Each time a button is released, buttonUp is called.

You’re just testing to see which button has been pressed or released, and then implementing very similar logic to what you did with your onscreen buttons. You change the opacity of the button sprites and send the appropriate method to the delegate.

As you can see, any pressed button will send the jump message.

Now you need to create the iCadeReaderView object, set the delegate, and add it to the view hierarchy. Add the following code to the end of init, still within the main if statement:

	iCadeReaderView *icrv = [[iCadeReaderView alloc] initWithFrame:CGRectZero];
        EAGLView *root = [[CCDirector sharedDirector] openGLView];
        [root addSubview:icrv];
        icrv.active = YES;
        icrv.delegate = self;

Here you’re setting up the view with no frame, so it’s invisible. You then send a reference to EAGLView so you can insert your iCadeReaderView into the view hierarchy. Then set it to active – this is required for it to work. Finally, you set your HUDLayer as the delegate of the iCadeEventDelegate protocol methods.

Guess what? That’s it! Really, really easy. Fire up your iCade now, make sure it’s paired as you did before and that the Bluetooth is connected, and you can control your flying cupid kid in retro miniature arcade bliss!

Where to Go From Here?

Our game running on the iCade!

Our game running on the iCade!

Here is the finished iCade game we developed in this tutorial.

If you don’t have an iCade, you should get one. Look at it this way: if you develop a game that supports it, it will be on a relatively short list of games that do. That means more visibility and more sales for your game.

There, I’ve given you a sound business reason to go out and buy an iCade. You’re welcome.

Once you have your iCade you must, must play Super Mega Worm and Mos Speedrun. You will thank me. Also, if you were lucky enough to score iMAME before it was pulled, that also supports the iCade.

It will be fun to see if the iCade and related devices can make more inroads and gain greater support among developers. With a wireless Bluetooth controller that supported iCade and a HDMI connection, you could use your phone as a home gaming console! Hey, it could happen.

Hope to hear from you in the forums!

Note from Ray: Want more iCade game recommendations? I asked you guys via Twitter a while back, and here were the top recommendations: Velocispider, League of Evil, Temple Run, and Caverns of Minos. Enjoy! :]


This is a blog post by iOS Tutorial Team member Jacob Gundersen, an indie game developer and co-founder of Third Rail Games. Check out his latest app – Factor Samurai!

Jake Gundersen
Jake Gundersen

Jacob is an indie game developer and runs the Indie Ambitions blog. Check out his latest app - Factor Samurai! You can find him on Twitter.

User Comments

2 Comments

  • Hi Jacob,

    Thank you for another great tut. This looks like a great oppuntity to get games noticed. Reading through the docs it seems like ion are not refusing us the right to mention them in our App descriptions so much as a warning for the Apple submission guidelines. Anyway like you said getting your App mentioned as compatible on their website should surely do it for any iCade owners. I think I might go ahead and buy the iCade mini for iPhone and give your tut a crack! Thanks again.

    Elliott
    Elliott
  • Thanks for this tutorial! Supporting this from the start of a game project is really a good advice. As for not being allowed to mention iCade in App description: from various messages on the internet, i also understand that you're not allowed to use the word "iCade" in your Application..?!? But there's some good suggestions also, to just use the word "Arcade". And perhaps add something like "Arcade hardware support" in your App description is a good alternative?

    If anyone can confirm any of this, as of nov 2012, i'll be glad to hear about it.

    Also, there are more hardware extensions out there by now, like iCade Mobile and iCade Core. If your game supports iCade then it'll support these as well. The only problem i hear is that sometimes the button configurations between all of them may not be what you intended as developer. Which means you should probably add button configuration options in your game. If anyone can confirm this please do :P

    Thanks again and cheers!
    RonnyD1978

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in May: Procedural Level Generation in Games with Kim Pedersen.

Sign Up - May

Coming up in June: WWDC Keynote - Podcasters React! with the podcasting team.

Sign Up - June

Vote For Our Next Book!

Help us choose the topic for our next book we write! (Choose up to three topics.)

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

... 55 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 1 total!

Translation Team

  • Sonic Zhao

... 38 total!

Subject Matter Experts

  • Richard Casey

... 4 total!