Getting Cocos 1.x / Kobold2D to work with the latest CocosBuilder

CocosBuilder is a brilliant tool that helps you rapidly develop Cocos2D applications.

But the latest versions require the Cocos 2.x branch.

Some of us are stuck with Cocos 1.x for the time being. So let’s figure out how to get things going.

First of all, the latest versions use a binary file. Make sure you use the ‘publish as’ functionality and create a ‘.ccbi’ file. Loading the ‘.ccb’ files will result in errors.

Next, you’re going to get errors loading some classes

ERROR: Uncaught exception CCMenu: Init not supported.

This is because classes like CCMenu and CCLayerTTF purposely throw exceptions in their ‘init’ selector.

CCNode.m

- (id) init
{
    NSAssert(NO, @"CCMenu: Init not supported.");
    [self release];
    return nil;
}

Fun!

So let’s fix that.

Cocos+DefaultInits.h

//
//  Cocos+DefaultInits.h
//
//  Created by Adam Griffiths on 30/03/12.
//  Copyright (c) 2012 Twisted Pair Development. All rights reserved.
//

@interface CCMenu (Constructor)
- (id)initWithoutParams;
@end

@interface CCLabelTTF (Constructor)
- (id)initWithoutParams;
@end

Cocos+DefaultInits.m

//
//  Cocos+DefaultInits.m
//
//  Created by Adam Griffiths on 30/03/12.
//  Copyright (c) 2012 Twisted Pair Development. All rights reserved.
//

#import "Cocos+DefaultInits.h"

@implementation CCMenu (Constructor)

- (id)initWithoutParams
{
    return [self initWithItems:nil vaList:nil];
}

@end

@implementation CCLabelTTF (Constructor)

- (id)initWithoutParams
{
    self = [self initWithString:@"OH NO" fontName:@"arial" fontSize:12];
    return self;
}

@end

This won’t work on it’s own. The code will call the existing init functions. We need some way to over-ride the default init function. Enter ‘selector swizzling’. The following code was taken from MikeAsh’s comment on CocoaDev.

SelectorSwizzle.h

//
//  SelectorSwizzle.h
//
//  via comment from MikeAsh
//  http://www.cocoadev.com/index.pl?MethodSwizzling
//

#import

void MethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel);

SelectorSwizzle.c

//
//  SelectorSwizzle.c
//
//  via comment from MikeAsh
//  http://www.cocoadev.com/index.pl?MethodSwizzling
//

#import "SelectorSwizzle.h"
#import </usr/include/objc/objc-class.h>

void MethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel)
{
    Method orig_method = nil, alt_method = nil;
    // First, look for the methods
    orig_method = class_getInstanceMethod(aClass, orig_sel);
    alt_method = class_getInstanceMethod(aClass, alt_sel);
    // If both are found, swizzle them
    if ((orig_method != nil) && (alt_method != nil))
    {
        char *temp1;
        IMP temp2;
        temp1 = orig_method->method_types;
        orig_method->method_types = alt_method->method_types;
        alt_method->method_types = temp1;
        temp2 = orig_method->method_imp;
        orig_method->method_imp = alt_method->method_imp;
        alt_method->method_imp = temp2;
    }
}

Now we need to inject the code before we begin parsing any CCBI files. I added this to my AppDelegate.m file. This is from Kobold2d which provides a simple version of this, so it may not match yours. Just place it somewhere before you call any CCBReader methods.


#import "SelectorSwizzle.h"
#import "Cocos+DefaultInits.h"

-(void) tryToRunFirstScene
{
    CCDirector* director = [CCDirector sharedDirector];

    // swizzle in our CCMenu fix for CCBuilder
    {
        MethodSwizzle( [CCLabelTTF class], @selector(init), @selector(initWithoutParams) );
        MethodSwizzle( [CCMenu class], @selector(init), @selector(initWithoutParams) );
    }

So now we’ve provided a default ‘init’ selector. Let’s run it.

readUTF8 numBytes: 4 str: Done
ERROR: Uncaught exception [ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key fontName.
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key fontName.'

Pants Ok, so it seems that Objective-C has a built in feature to let you call ‘setValue:forKey’ and ‘valueForKey:’ on objects and dynamically find variables by name. That… is awesome.

Unfortunately Cocos2.x changed how these variables are found. You can see the key is ‘fontName’ but looking at the Cocos1.x/2.x source, we can see the variable name itself is ‘fontName_’. Cocos2.x must be doing some mapping from ‘variableName_’ to ‘variableName’ for the keys.

I don’t know how to do that properly, so let’s over-ride the ‘setValue:forKey:’ method in CCNode, the base of all Cocos objects.

We’ll try setValue:forKey:, and if it throws an exception we’ll try taking a ‘_’ to the end and try again.

CCNode+KVC.h

//
//  CCNode+KVC.h
//
//  Created by Adam Griffiths on 30/03/12.
//  Copyright (c) 2012 Twisted Pair Development. All rights reserved.
//

#import "CCNode.h"

@interface CCNode (KVC)

- (void)setValue:(id)value forKey:(NSString *)key;

@end

CCNode+KVC.m

//
//  CCNode+KVC.m
//
//  Created by Adam Griffiths on 30/03/12.
//  Copyright (c) 2012 Twisted Pair Development. All rights reserved.
//

#import "CCNode+KVC.h"

@implementation CCNode (KVC)

- (void)setValue:(id)value forKey:(NSString *)key
{
    @try
    {
        NSLog( @"[%@] INFO: Setting value for key %@", [self class], key );
        [super setValue:value forKey:key];
    }
    @catch (NSException *e)
    {
        @try
        {
            NSLog( @"[%@] WARNING: Key %@ not valid, trying %@_", [self class], key, key );
            [super setValue:value forKey:[NSString stringWithFormat:@"%@_", key]];
            NSLog( @"Success" );
        }
        @catch (NSException *e)
        {
            // we can't fix this
            NSLog( @"[%@] ERROR: Key %@ failed!", [self class], key );
        }
    }
}
@end

Now let’s see what happens

[CCLabelTTF] INFO: Setting value for key fontName
[CCLabelTTF] WARNING: Key fontName not valid, trying fontName_
Success
[CCLabelTTF] INFO: Setting value for key fontSize
[CCLabelTTF] WARNING: Key fontSize not valid, trying fontSize_
Success

So we can see that a few setValue calls failed, but succeeded the second time around.
 
And we’ve also gotten a scene to load! Brilliant!
 
Unfortunately, the CCControlButton class is totally un-cooperative at the moment. It doesn’t even show up (it’s probably off the screen). It also spits out a tonne of errors about properties not existing.

[CCControlButton] INFO: Setting value for key selected
[CCControlButton] INFO: Setting value for key title|1
[CCControlButton] WARNING: Key title|1 not valid, trying title|1_
[CCControlButton] ERROR: Key title|1 failed!
[CCControlButton] INFO: Setting value for key titleBMFont|1
[CCControlButton] WARNING: Key titleBMFont|1 not valid, trying titleBMFont|1_
[CCControlButton] ERROR: Key titleBMFont|1 failed!

How do we fix this? ……. I don’t know, you tell me!

You can find a Gist of this code here.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: