одиночное состояние игры cocos2d, initWithEncoder всегда возвращает ноль - PullRequest
0 голосов
/ 20 апреля 2010

Я пытаюсь написать базовый тестовый синглтон "игровое состояние" в cocos2d, но по какой-то причине после загрузки приложения initWithCoder никогда не вызывается. Любая помощь будет высоко ценится, спасибо.

Вот мой синглтон GameState.h:


#import "cocos2d.h"

@interface GameState : NSObject <NSCoding>
{
  NSInteger level, score;
  Boolean seenInstructions;
}

@property (readwrite) NSInteger level;
@property (readwrite) NSInteger score;
@property (readwrite) Boolean seenInstructions;

+(GameState *) sharedState;
+(void) loadState;
+(void) saveState;

@end

... и GameState.m:


#import "GameState.h"
#import "Constants.h"

@implementation GameState

static GameState *sharedState = nil;

@synthesize level, score, seenInstructions;

-(void)dealloc {
  [super dealloc];
}

-(id)init {
  if(!(self = [super init]))
    return nil;  
  level = 1;
  score = 0;
  seenInstructions = NO;

  return self;
}

+(void)loadState {
  @synchronized([GameState class]) {    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *saveFile = [documentsDirectory stringByAppendingPathComponent:kSaveFileName];
    Boolean saveFileExists = [[NSFileManager defaultManager] fileExistsAtPath:saveFile];

    if(!sharedState) {
      sharedState = [GameState sharedState];
    }

    if(saveFileExists == YES) {
      [sharedState release];
      sharedState = [[NSKeyedUnarchiver unarchiveObjectWithFile:saveFile] retain];
    }
    // at this point, sharedState is null, saveFileExists is 1
    if(sharedState == nil) {
      // this always occurs
      CCLOG(@"Couldn't load game state, so initialized with defaults");
      sharedState = [self sharedState];
    }
  }  
}

+(void)saveState {
  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  NSString *documentsDirectory = [paths objectAtIndex:0];
  NSString *saveFile = [documentsDirectory stringByAppendingPathComponent:kSaveFileName];
  [NSKeyedArchiver archiveRootObject:[GameState sharedState] toFile:saveFile];
}

+(GameState *)sharedState {
  @synchronized([GameState class]) {
    if(!sharedState) {
      [[GameState alloc] init];
    }
    return sharedState;
  }
  return nil;
}

+(id)alloc {
  @synchronized([GameState class]) {
    NSAssert(sharedState == nil, @"Attempted to allocate a second instance of a singleton.");
    sharedState = [super alloc];
    return sharedState;
  }
  return nil;
}

+(id)allocWithZone:(NSZone *)zone
{
  @synchronized([GameState class]) {
    if(!sharedState) {
      sharedState = [super allocWithZone:zone];
      return sharedState;
    }
  } 
  return nil;
}

...

-(void)encodeWithCoder:(NSCoder *)coder {
  [coder encodeInt:level forKey:@"level"];
  [coder encodeInt:score forKey:@"score"];
  [coder encodeBool:seenInstructions forKey:@"seenInstructions"];
}

-(id)initWithCoder:(NSCoder *)coder {
  CCLOG(@"initWithCoder called");
  self = [super init];
  if(self != nil) {
    CCLOG(@"initWithCoder self exists");
    level = [coder decodeIntForKey:@"level"];
    score = [coder decodeIntForKey:@"score"];
    seenInstructions = [coder decodeBoolForKey:@"seenInstructions"];
  }
  return self;
}
@end

... Я сохраняю состояние при выходе из приложения, например:


- (void)applicationWillTerminate:(UIApplication *)application {
  [GameState saveState];
  [[CCDirector sharedDirector] end];
}

... и загрузка состояния, когда приложение завершает загрузку, например:


- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  ...
  [GameState loadState];
  ...
}

Я пытался переместиться туда, где я тоже вызываю loadState, например, в моей основной CCScene, но, похоже, это тоже не сработало.

Еще раз спасибо заранее.

Ответы [ 2 ]

5 голосов
/ 20 апреля 2010

Праведный! Я думаю, что я понял это. Плюс я нашел хороший макрос для экономии времени при загрузке: http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html

И модифицированный макрос, который я использую: http://github.com/taberrr/Objective-C-Optimized-Singleton.git (мне нравится «sharedGameState», а не «sharedInstance»)

Надеюсь, это поможет кому-то другому, пытающемуся сделать то же самое ... вот мой рабочий синглтон NSCoder GameState:

GameState.h:


#import "SynthesizeSingleton.h"
#import "cocos2d.h"

@interface GameState : NSObject <NSCoding>
{
  NSInteger level, score;
  Boolean seenInstructions;
}

@property (readwrite) NSInteger level;
@property (readwrite) NSInteger score;
@property (readwrite) Boolean seenInstructions;

SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(GameState);

+(void)loadState;
+(void)saveState;

@end

GameState.m:


#import "SynthesizeSingleton.h"
#import "GameState.h"
#import "Constants.h"

@implementation GameState

@synthesize level, score, seenInstructions;

SYNTHESIZE_SINGLETON_FOR_CLASS(GameState);

- (id)init {
  if((self = [super init])) {

    self.level = 1;
    self.score = 0;
    self.seenInstructions = NO;

  }
  return self;
}

+(void)loadState
{
  @synchronized([GameState class]) {
    // just in case loadState is called before GameState inits
    if(!sharedGameState)
      [GameState sharedGameState];

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *file = [documentsDirectory stringByAppendingPathComponent:kSaveFileName];
    Boolean saveFileExists = [[NSFileManager defaultManager] fileExistsAtPath:file];

    if(saveFileExists) {
      // don't need to set the result to anything here since we're just getting initwithCoder to be called.
      // if you try to overwrite sharedGameState here, an assert will be thrown.
      [NSKeyedUnarchiver unarchiveObjectWithFile:file];
    }
  }
}

+(void)saveState
{
  @synchronized([GameState class]) {  
    GameState *state = [GameState sharedGameState];

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *saveFile = [documentsDirectory stringByAppendingPathComponent:kSaveFileName];

    [NSKeyedArchiver archiveRootObject:state toFile:saveFile];
  }
}

#pragma mark -
#pragma mark NSCoding Protocol Methods

-(void)encodeWithCoder:(NSCoder *)coder
{
  [coder encodeInt:self.level forKey:@"level"];
  [coder encodeInt:self.score forKey:@"score"];
  [coder encodeBool:self.seenInstructions forKey:@"seenInstructions"];
}

-(id)initWithCoder:(NSCoder *)coder
{
  self = [super init];
  if(self != nil) {
    self.level = [coder decodeIntForKey:@"level"];
    self.score = [coder decodeIntForKey:@"score"];
    self.seenInstructions = [coder decodeBoolForKey:@"seenInstructions"];
  }
  return self;
}

@end

Сохранение:


- (void)applicationWillTerminate:(UIApplication *)application {
  ...
  [GameState saveState];
  ...
}

Загрузка:


// somewhere in your app, maybe in applicationDidFinishLaunching
GameState *state = [GameState sharedGameState];
NSLog(@"sharedGameState: %@", state);
[GameState loadState];

Если кто-то видит какие-либо проблемы с этим, пожалуйста, говорите. :)

Похоже, все работает нормально.

0 голосов
/ 01 октября 2010

Вам не нужно загружать модифицированный макрос. Исходный allocWithZone вернул ноль. Просто исправьте оригинал так:

от

+ (id)allocWithZone:(NSZone *)zone \
{ \
    @synchronized(self) \
    { \
        if (shared##classname == nil) \
        { \
            shared##classname = [super allocWithZone:zone]; \
            return shared##classname; \
        } \
    } \
    \
    return nil; \
} \

до:

+ (id)allocWithZone:(NSZone *)zone \
{ \
    @synchronized(self) \
    { \
        if (shared##classname == nil) \
        { \
            shared##classname = [super allocWithZone:zone]; \
        } \
    } \
    \
    return shared##classname; \
} \
...