C++ with a pinch of Objective-C

For the last several weeks, we've been going over how C++ can be mixed with the traditional Objective-C to make your Cocoa even sweeter. But today we're going to cover a somewhat different recipe: mostly C++, with just enough Objective-C to make it work on iOS.

Why would one do such a thing, you ask? There are many possible reasons, but the most likely is that you might want your code to actually maybe run someday on a device not made by Apple. Blasephemy, I know, but the need does occasionally arise. By writing most of the app in a standard language like C++, you'll be able to reuse that code elsewhere.

We recently had exactly such a need: we wanted to make a 2D game that ran well on both iOS and Android, and wanted to keep our options open with regard to Mac and Windows as well. You could use Unity for something like that, but for various reasons we preferred to stay closer to the metal. We liked the Cocos2d framework, but because it was all built on Cocoa, it was not portable.

So we developed our own 2D game engine built on OpenGL, and kept the guts of it all portable C++. We worked out how to wrap this in the necessary Cocoa or Java glue to make it run on iOS and Android, respectively. It worked like a charm. Here's how to do the same for your iOS/C++ code.

Start with a normal XCode project, using the "Window-based application" template (which is the most bare-bones one, leaving you with less guts you'd have to rip out). While not strictly necessary, you'll probably want to rename main.m to main.mm, activating the Objective-C compiler, so that you can invoke your unit tests there (ours has a bunch of lines like "Level::runUnitTests()" and so on).

Then go to the application delegate (e.g., myGameAppDelegate.m or whatever the template called it for you). You may not need to rename this one, because all it's going to do is create a GL view, and switch the device to landscape mode if that is desired. For the sake of completeness, here's ours:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    
    // Switch the device to landscape mode
    [[UIApplication sharedApplication] setStatusBarOrientation: UIInterfaceOrientationLandscapeRight animated:NO];
    
    // Needed to rotate to landscape
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
    screenBounds = CGRectMake(0, 0, screenBounds.size.height, screenBounds.size.width);
    
    // Create the main window
    mWindow = [[UIWindow alloc] initWithFrame: screenBounds];

    // Rotate to Landscape
    CGAffineTransform tform = mWindow.transform;
    tform = CGAffineTransformRotate(tform, (M_PI / 2.0));
    mWindow.transform = tform;
    //We have to flop the width and height back to portait before doing the rotation
    mWindow.center = CGPointMake(screenBounds.size.height/2, screenBounds.size.width/2);
    
    // Create the main view of the main window
    UIView* mainView = [[UIView alloc] initWithFrame: screenBounds];

    // Create the GLView to draw the frame buffer into
    GLView* glView = [[GLView alloc] initWithFrame: screenBounds];
    [mainView addSubview: glView];
    
    // Add the main view and show the window
    [mWindow addSubview: mainView];
    [mWindow makeKeyAndVisible];
    
    //Clean up
    [mainView release];
    [glView release];
    
    return YES;
}

Nothing too out of the ordinary here; this just sets up the GL view. Next is where it gets interesting. GLView is a custom subclass of UIView. It has an EAGLContext* for communicating with OpenGL, and its file name is GLView.mm -- that yummy-sounding extension activating the ObjC++ compiler. So, while the header is pure standard Cocoa, the .mm file can include C++ class headers, and make full use of our C++ drawing library.

The chief responsibilities of our GLView.mm are to (1) initialize the C++ game library, (2) invoke the drawing library on each frame, and (3) pass touches down to the drawing library. This isn't a game development blog, so I won't belabor the details, but to give you the flavor of it, here's the touchesBegan method:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    if (touch) {
        CGPoint touchPt = [self deviceToScreen:[touch locationInView:self]];
        RaDirector::sharedDirector()->touchReceived(Vector2f(touchPt.x,touchPt.y));
    }
}

- (CGPoint)deviceToScreen:(CGPoint)pt {
    return CGPointMake(pt.x, self.bounds.size.height - pt.y);
}

Here, RaDirector::sharedDirector() gets the singleton instance of our "director" class, which is responsible for things like dispatching events to the appropriate game objects. As you can see, the Cocoa code here is pretty simple; as soon as possible, we pass the work right off to our portable C++ code. We do have to standardize the coordinate system, of course, which deviceToScreen above does by inverting the Y axis. In the Android version of the project, we do pretty much the same thing, except that the original events come from Java. Once it gets into RaDirector::touchReceived, the same code runs on both platforms.

This approach works really well for apps that have a completely custom UI. The more standard platform UI you have, the more you'd have to write in the native environment (i.e. Cocoa on iOS, Java on Android, etc.). However, any time your app has a heavy-lifting portion -- some sort of number-crunching or complex algorithm -- you can benefit from writing that part of it in portable C++, and at least leave the door open to other platforms in the future. And as we saw last week, your number-crunchy algorithms will likely perform much faster in C++ than they would with the standard Cocoa data structures anyway.