Cocos2d messaging/event system.

__________

It's been a while since I wanted to add a event system to cocos2d, and these days I've been working with Unity3D, which mostly relays on the event system to send information across all classes so I started working on it for cocos2d.

For those who don't know how a messaging system works, just imaging that you are creating a IA that for your FPS, and you want to notify all surrounding IA troops that the enemy base camp is under attack, then you'll have to perform this tasks:
  1. Create the "UnderAttack" event.
  2. Then you'll subscribe, in your IA enemies, to the message/event "UnderAttack" you want to listen.
  3. create the listener for these event in which you'll have the code to react under this event.
  4. then you'll send the "UnderAttack" event from your IA enemy that has discovered you.
And that is all.

Cocoa has it's on system, which is called Notification System, that works very well, but you have to write quite a lot of code to get it working so I've written a wrapper to simplify everything.

Messaging system

There are bassically three actions you can do on a messaging system as described before, subscribe to the event, unsubscribe of that event, and send messages.

Subscribe

The wrapper for that action is:
//Subscribes to aMessage event.
#define AMSubscribe(aSelector, aMessage, aObject) \
[[NSNotificationCenter defaultCenter] addObserver:(self) selector:(aSelector) name:(aMessage) object:(aObject)]
And what you'll have to write in your code to subscribe the events:

Create the event, (writting the event listener):

-(void)eventHandler: (NSNotification *) notification
{
// senderObject contains the pointer to the sendMessage passed object. can be nil.
id senderObject = [notification object];

NSLog(@"%@ event triggered", self);
}
And then subscribe to the event with:
// custom onEnter
-(void) onEnter
{
[super onEnter];

//Here we should register the event listener
AMSubscribe(@selector(eventHandler:), @"theTestEvent", nil);
}
I've added the AMSubscribe inside the cocos2d onEnter call, just to make sure it is called before any possible message is sent and thus lost, but you can add it everywhere you actually need it.

Things you should remember:
  • the selector name is the listener function
  • @"theTestEvent" is the name of the event and how you'll call it.
  • you don't actually need the third parameter, so left it as nil. It's used if you only want to listen to this object messages.
  • the listener function has to be in the same class subscribing to the event.
Unsubscribe

The function you should call is defined as:
//Unsubscribe aMessage event for caller object.
#define AMUnsubscribeMessage (aMessage) \
[[NSNotificationCenter defaultCenter] removeObserver:(self) forKeyPath:(aMessage)]

//Unsubscribes all events for caller object.
#define AMUnsubscribe \
[[NSNotificationCenter defaultCenter] removeObserver:(self)]
And this is how you should use it:
// custom onExit
-(void) onExit
{
[super onExit];

//Here we should unregister the event listener
AMUnsubscribe;
}
As before, I've implemented it on the onExit function to make sure it is removed when the scene is moved, but you'll be able to add it wherever you want.

NOTE: Just remember to Unsubscribe to all the events before releasing the object or it will leak.

Send Messages

You'll send the messages with:
// send the message aMessage with aObject data
#define AMSendMessage(aMessage, aObject) \
[[NSNotificationCenter defaultCenter] postNotificationName:(aMessage) object:(aObject)]
And you it as:
-(void) menuCallbackSEND: (id) sender
{
AMSendMessage(@"theTestEvent", [NSNumber numberWithInt:10]);
}
This code is extracted from the sample code below. Which sends a theTestEvent message with the NSNumber 10 as data.

Implementation

The implementation of the system is as easy as adding the "MessagingSystem.h" class to your project and calling the functions as described before.

Download

You can download a sample project with everything on it.
It consist of a clean project, just to be as familiar as possible, with the library added.

It consist on the HelloWorld scene subscribed to the event and another CCNode class with a label also registered to the event.

that will run as:

And after pressing the SEND button:

The file | MessagingSystem.h

The sample project
| Test project

If anyone has any suggestion or question leave it in the comments. I'll be glad to answer.
Thank you for reading.

Custom localization system for your iphone games

__________

Localizating your application is crucial if you want to reach a broader audience, imaging how a Spanish or German kid will react if the game they try to play is in english or french, they just won't buy your game.

The iphone SDK has it's own localization system, which works very well, but as you may know the default localization system does not allow you to change the language in run-time, just use the OS default system, and it also does not allow other languages beyond what's defined on the iphone OS. So, if you want to change the language from a menu in your game there are many things you can do:
  • write your own system, which is utterly boring and implies parsing your language files, or...
  • overload and use what the framework has to offer.
This post tries to explain, first, how to the new localization system works, and second how to add localization to your system and how to use it to change the language in run-time.

Note: Although it is used for games it might be used for your own applications as well.

How the new system works

a. The iphone SDK way

The iphone SDK provides the NSLocalizableString("tag", "alternative") macro to obtain the localized string, from the Localizable.strings, for the given "tag" string or in case it didn't find a proper match it returns the "alternative" string.

I'll explain in the Implementation section how to build the Localizable.strings, just know that it has a strings in the format:
"tag" = "Localized text for the tag";
E.g.
NSString *localizedStr = NSLocalizableString(@"hello", @"Hello World");
//IF it finds the "hello" tag on the localizable.strings for the current OS language it will return the localized string.
//ELSE will return "Hello World"
b. The new way

With adding the LocalizationSystem.h and LcalizationSystem.m you add the following functionality to the default system.
  • you'll be fully compatible with what was already done.
  • you'll be able to change the language in run-time
  • you'll be able to support languages not added in the iphone OS implementation, like Esperanto, Catalá, Elfic,...
Using it:

It's usage is fairly simple, there has been defined 4 macros that extends the default functionality. These macros are:

1. LocalizationSetLanguage("language")
Using the following macro you'll be able to change the localization default language into the language you want.
// Sets the desired language of the ones you have.
// example calls:
// LocalizationSetLanguage(@"Italian");
// LocalizationSetLanguage(@"German");
// LocalizationSetLanguage(@"Spanish");
//
// If this function is not called it will use the default OS language.
// If the language does not exists y returns the default OS language.
- (void) setLanguage:(NSString*) l{
NSLog(@"preferredLang: %@", l);

NSString *path = [[ NSBundle mainBundle ] pathForResource:l ofType:@"lproj" ];

if (path == nil)
//in case the language does not exists
[self resetLocalization];
else
bundle = [[NSBundle bundleWithPath:path] retain];
}
The "language" string should match what you have on you Localizable.strings file

2. AMLocalizedString("tag", "alternative")
Gets the Localized string:
// Gets the current localized string as in NSLocalizedString.
//
// example calls:
// AMLocalizedString(@"Text to localize",@"Alternative text, in case hte other is not find");
- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)comment
{
return [bundle localizedStringForKey:key value:comment table:nil];
}
The AMLocalizedString works just the same way as the NSLocalizedString.

3. LocalizationReset;
Resets all settings, in case you'll allow your user to leave the language settings as default.
// Resets the localization system, so it uses the OS default language.
//
// example call:
// LocalizationReset;
- (void) resetLocalization
{
bundle = [NSBundle mainBundle];
}
4. LocalizationGetLanguage;
Gets the current set language.
// Just gets the current setted up language.
// returns "es","fr",...
//
// example call:
// NSString * currentL = LocalizationGetLanguage;
- (NSString*) getLanguage{

NSArray* languages = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLanguages"];

NSString *preferredLang = [languages objectAtIndex:0];

return preferredLang;
}

Implementation

Step 1. Creating the project

I'll use a Cocos2d 0.99.0 project as I wanted to demonstrate the system on games, but you'll be able to create the project better suits your application.

Step 2. Add localization to your project

To do so, you'll need to:
  1. in resources group within your project, right click add... -> New File
  2. I'll appear a window like the following. Go to Mac OS X -> Resources and select Strings File. Name your file "Localizable.string".
At this point you have added the localization file to you project, now open the information of the file (right click -> Get Info / cmd + i), and on the General tab press the Make File Localizable. You will get this as a result.

Now its time to add the localization you want.
tip: Name it as the language name in english. e.g "spanish", "french", "german",...

Step 3. Add the text.

Now open each of the files to add the text you want to localize, remember the format. Do it in the desired language file.

"tag" = "localized text";
"hello" = "HOLA MUNDO";
Add this point is exactly as if you were adding normal localization.

Step 4. Add the new localizatio file to your project.

You have to add the LocalizationSystem.h and LocalizationSystem.m to your project.

This can be done in two ways.
  1. Right click -> Add existing files... wherever you want to add the files, select them and click ok.
  2. You can darg from the sample project the group containing both files to your project.
Step 5. Just use the library.

1. Add
#import "LocalizationSystem.h"
In the classes that need localization,

2. Use AMLocalizableString instead of NSLocalizableString.

3. Use LocalizationSetLanguage to change the language.
LocalizationSetLanguage(@"Spanish");
CCLabel* label = [CCLabel labelWithString:AMLocalizedString(@"hello",@"Hello World") fontName:@"Marker Felt" fontSize:32];
The label will contain "Hola mundo".

Download

You can download a sample project with everything on it.
It consist of a clean project, just to be as familiar as possible, with the library added.

that will run as:

Each button represents a language added and OS, returns to the OS default language.

The files:
LocalizationSystem.h
LocalizationSystem.m

The sample project
Test project

If anyone has any suggestion or question leave it in the comments. I'll be glad to answer.
Thank you for reading.
top