Monday, December 10, 2012

Customizing the core data store.

CoreData is a pretty awesome way to manage and persist your game objects, especially for a game with complex inter-related items, such as an adventure game.

Trouble is there is a helluva learning curve getting going with CoreData.  I just solved one little thing that was driving me crazy and wanted to share: how do you customize aspects of the CoreData store?

Image of apple core courtesy of http://www.wpclipart.com/


In my game I have two project targets: one is the game itself written in Cocos2D, and the other target is a game data editor written in Objective-C & Cocoa for the Mac.  They share a lot of classes, and the idea is that I can edit the games data, save it out as a SQLite file (which is one of CoreData's persistence formats) and then when someone goes to play the game for the first time they get a fresh instance of that data as the initial game state.

Trouble is, how do I tell the Mac Cocoa gui, to create my custom instance of an SQLite database, instead of popping up a dialog - which is the default thing you get using XCode's CoreData enabled app wizard?

And for that matter how do I get my database of game objects into my Cocos2D app?

If you're a CoreData beginner like me I suggest:
  • The answer begins with having to understand the bare essentials of CoreData.  For that I recommend Bob Uelands video tutorials.  He has a patient and careful manner that is a bit frustrating but his clarity is hard to beat.  If you've some familiarity with CoreData you can probably fast forward or even skip his stuff.
  • You could try Ray Wenderlich's tutorials - but I find they're a bit too specific and don't always give me the "why am I doing this" answers I want so I can build my own stuff.  But have a look and they may just have exactly what you need.
  • Go and run through Apple's CoreData command line tutorial.  It builds a CoreData app up from first principles all inside main.m.  If its all gobble-de-gook to you, then maybe skipping patient Bob's tutorials was a bit hasty...
What is great about the Apple CoreData tutorial is that the final result - a CoreData app that does not start with any of AppKit or UIKit, or any XCode scaffolding - is exactly what you need for dropping CoreData into a Cocos2D app.  If you have the time and want a good understanding work through the tutorial building up the code in the order given, and resist the temptation to just past the whole source.

Seriously - if you're used to skipping the boring documentation in the Apple developer site you're missing a buried gem with this tutorial.  They give the steps to building a whole data persistence stack.

I found working through these gave me the answers I needed.  So for example to solve my problem with customising the data store, I took the code from Apple's CoreData tutorial managedObjectContext() function and overrode the managedObjectContext function in the NSPersistentDocument sub-class I was using in my Mac OSX game editor GUI.

Edit:  OK, this looked like it was working but wasn't - my apologies.  Basically you can use this code, but you need to create your own window controller and UI class, not use the NSPersistentDocument - which is built to handle arbitrary "documents", not one well-known path for a database.  Sigh.

That let me specify the details of my store:

- (NSManagedObjectContext*)managedObjectContext
{
    static NSManagedObjectContext *moc = nil;
    if (moc != nil) {
        return moc;
    }
    
    NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc]
                                                 initWithManagedObjectModel:[self managedObjectModel]];
    
    NSString *STORE_TYPE = NSSQLiteStoreType;
    NSString *STORE_FILENAME = @"GameData.sqlite";
    
    NSError *error;
    NSString *path = [[PreferenceValues sharedPreferenceValues] saveGamePath];
    NSURL *url = [NSURL fileURLWithPath:path isDirectory:YES];
    url = [url URLByAppendingPathComponent:STORE_FILENAME];
    
    NSLog(@"Setting up the store with type %@ - at %@", STORE_TYPE, [url path]);
    
    NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:STORE_TYPE
                                                            configuration:nil
                                                                      URL:url
                                                                  options:nil
                                                                    error:&error];
    
    if (newStore == nil)
    {
        NSLog(@"Store Configuration Failure\n%@",
              ([error localizedDescription] != nil) ?
              [error localizedDescription] : @"Unknown Error");
    }
    
    moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [moc setPersistentStoreCoordinator:coordinator];
    
    return moc;
}

Wait, what is that PreferenceValues thing doing in there? That is my own NSUserDefaults front-end. More on that in another post.

How do I get my data into Cocos2D?  I'll show you how I did it, but that's for another blog post.  Subscribe - give me feedback and I will post more!  Thanks for reading!