Create a scene helper class for iOS / Mac cross platform SpriteKit games

There was a lot of interest in my post giving a few vague suggestions for converting a sprite kit game from iOS to Mac. Since then I've refined my methods a bit and now work with a project which is targeted for both iOS and Mac to make it even easier to implement and test.

Even if you never plan to release your game on Mac OS X, the below is still useful. The iOS simulator is painful at times however running SpriteKit natively on the Mac is simply beautiful, it makes testing a lot more fun.

Getting Started

The first thing you need to do is make a project containing both a target for iOS and for Mac OS.

  1. Create a new Xcode project
  2. Select "Sprite Kit Game" from the iOS templates
  3. Give it a name
  4. Go to "File", "New", "Target..."
  5. Select "Sprite Kit Game" from the Mac templates

You now have a template containing both two targets. If you are familiar with both Mac and iOS development it shouldn't take you too long to work out what is going on here. Have a look at your schemes and project settings.

I like to arrange my folders so that my Mac only code is in a "Mac" folder, the iOS only code is in a "iOS" folder and the shared game is in a "Shared" folder.

You can control which target has access to which game files via the "Target Membership" settings in the file inspector on the right.

Platform specific code

In iOS you are probably familiar with performing different code specific you wether you are running the application on an iPhone or iPad. You will run in to the same problems when coding for both Mac and iOS.

There are a lot of differences in the way you code between both platforms, even which classes you are using, so in most cases a standard IF statement won't be enough and you will need pre-processor #IF statements.

Example:

#if TARGET_OS_IPHONE
#endif

There is also a TARGET_OS_MAC but you will probably find that iOS devices respond to this as well, so it is a lot easier to just check !TARGET_OS_IPHONE.

It's a different world

Before going any further it is worth noting that you are working in two different worlds governed by two different HIG documents. What you know in iOS may not apply to Mac development. 

You are also working with a touch screen device without keyboard and a mouse and keyboard operated device. This is a simple fact but it will show in your game if you don't pay attention to it. I had an app rejected at review once for the reason my start screen said "Tap anywhere to begin" instead of "Click", a rather embarrassing mistake but one I make sure to prepare better for now.

Ok, enough of the lecture, you all want the helper class right?

A scene helper class to get you started

This is a very simple class but I want you guys to take it and add your own code to it. If your game uses a onscreen DPAD on iOS then it makes no sense do the same on Mac, you would need to monitor key presses instead.

This class helps for all other cases, it allows you to single methods for handling both screen clicks and screen taps. Great for games where screen presses translate easily in to screen clicks.

Create a class called SKMScene that inherits from SKScene.

Put the below in SKMScene.h:

// Created by Neil North on 6/02/2014.
// Copyright (c) 2014 Neil North. All rights reserved.
//

#import

@interface SKMScene : SKScene


//Screen Interactions
-(void)screenInteractionStartedAtLocation:(CGPoint)location;
-(void)screenInteractionEndedAtLocation:(CGPoint)location;

@end

And the below in the SKMScene.m file:

// Created by Neil North on 6/02/2014.
// Copyright (c) 2014 Neil North. All rights reserved.
//

#import “SKMScene.h”

@implementation SKMScene

-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
/* Overridden by Subclass */

}
return self;
}

#if TARGET_OS_IPHONE
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
[self screenInteractionStartedAtLocation:positionInScene];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
[self screenInteractionEndedAtLocation:positionInScene];
}

- (void)touchesCancelled:(NSSet *)touches
withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
[self screenInteractionEndedAtLocation:positionInScene];
}
#else
-(void)mouseDown:(NSEvent *)theEvent {
CGPoint positionInScene = [theEvent locationInNode:self];
[self screenInteractionStartedAtLocation:positionInScene];
}

- (void)mouseUp:(NSEvent *)theEvent
{
CGPoint positionInScene = [theEvent locationInNode:self];
[self screenInteractionEndedAtLocation:positionInScene];
}

- (void)mouseExited:(NSEvent *)theEvent
{
CGPoint positionInScene = [theEvent locationInNode:self];
[self screenInteractionEndedAtLocation:positionInScene];
}
#endif

-(void)screenInteractionStartedAtLocation:(CGPoint)location {
/* Overridden by Subclass */
}

-(void)screenInteractionEndedAtLocation:(CGPoint)location {
/* Overridden by Subclass */
}

@end

That's it, now all you have to do is make sure all your scenes inherit from SKMScene instead of SKScene and implement the two screenIntereaction methods.

There's a lot more to this process and I'm only just starting to learn to make cross platform games myself but I hope the above tips have helped you on your journey. Give it a go and see what you think.

You can also download a sample project on GitHub.

Design your app for localization from day one

Something which had been more of an afterthought for me in the past had been Localization, I figured there were enough english speaking iOS users out there for my business to be profitable. In retrospect this was a rather uncultured and closed minded view considering how big my audience could potentially be and how little it could cost me to reach them.

Here are a few tips for early development to make the localization process easy and painless:

  • Consider the dialog early, every word works out to around 9 cents per language (using professional translation services), if you can keep your dialog short and maybe replace some words with common symbols like arrows then it will make things cheaper
  • Plan to use a professional translation service, they are rather cheap and while google translate is reasonably good you will miss out on a lot of dialect and detail which can be rather embarrassing for your company and lead to demands for refunds. It will also kill your chances of getting featured (Apple love to feature well localized apps!).
  • Whenever you create a NSString while programming, don't forget to do it as a NSLocalizedString(@"",@"") and actually leave a detailed note in the second parameter where there is room for confusion. For example: If the text is "Ok", then leave a note about what you are agreeing to with this text as certain things are agreed to in different ways in different languages. Also if you have abbreviations like "m" for meters, make sure you let the translator know with a note.
  • Don't worry about populating your Localizable.strings file as you go, it's a waste of time as using genstrings can do this automatically.
  • Plan to localize towards the end of your development cycle, most translators are quick and last minute updates or changes can be time consuming.
  • Don't forget about your app name, store description, key words, in-app purchases, game center achievements and leaderboards. They all need to be localized for a complete experience.
  • Don't forget about images or data in plists which may contain words.

Ok, so you have kept your strings short and minimal to reduce costs, you have run genstrings to prepare your localizable.strings file. What next? 

You need to decide what languages you need, it is recommended to localize in:

English, Spanish, French, German, Italian, Portuguese, Chinese (Simplified), Chinese (Traditional), Russian, Korean and Japanese.

You may want to do some market research first to see how popular the subject of your app will be in those regions. You may find that your game only really applies to English speaking countries or on the other hand you may find that Korea is 80% of your world market. If you are still unsure, its not that expensive to cover your bases and do all of the above.

Now that you have decided on your languages, it's time to pick a translation service. I like to use iCanLocalize. They are very affordable, very quick and allow for lots of communication with the translators so you can get your translation done with the right context and style of your app. They also have a review process where another translator checks the translations done to ensure impeccable quality. You can also send them your .strings files and they can return them in the same format for easy implementation.

It's good to keep Google Translate handy too for unimportant quick translations. Be sure to check the context is as accurate as possible even with these simple translations.

Follow these tips and localizing your apps should be as painless as it has been for me in my latest app which goes on sale in two to three weeks time.