Game State Singleton Cocos2d, initwithEncoder sempre retorna nulo
-
28-09-2019 - |
Pergunta
Estou tentando escrever um teste básico de "estado de jogo" Singleton em Cocos2D, mas, por algum motivo, ao carregar o aplicativo, o initWithCoder nunca é chamado. Qualquer ajuda seria muito apreciada, obrigado.
Aqui está o meu singleton 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
... e 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
... Estou salvando o estado na saída do aplicativo, assim:
- (void)applicationWillTerminate:(UIApplication *)application {
[GameState saveState];
[[CCDirector sharedDirector] end];
}
... e carregando o estado quando o aplicativo termina de carregar, assim:
- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
[GameState loadState];
...
}
Tentei me mover onde também chamo de LoadState, por exemplo, na minha principal CCScene, mas isso também não parecia funcionar.
Obrigado novamente antecipadamente.
Solução
Justo! Eu acho que descobri. Além disso, encontrei uma boa macro que economiza tempo para inicializar: http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html
E a macro modificada que estou usando: http://github.com/taberrr/objective-c-timized-singleton.git (Eu gosto de "compartilhamento de gamestate" sobre "sharedinstance")
Espero que isso ajude outra pessoa tentando fazer a mesma coisa ... Aqui está o meu funcionamento NSCODER GAMESTATE SINGLETON:
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
Salvando:
- (void)applicationWillTerminate:(UIApplication *)application {
...
[GameState saveState];
...
}
Carregando:
// somewhere in your app, maybe in applicationDidFinishLaunching
GameState *state = [GameState sharedGameState];
NSLog(@"sharedGameState: %@", state);
[GameState loadState];
Se alguém vê algum problema com isso, fale. :)
Parece funcionar bem, no entanto.
Outras dicas
Você não precisa baixar a macro modificada. O alocwithzone original retornou nulo. Apenas corrija o original assim:
a partir de:
+ (id)allocWithZone:(NSZone *)zone \
{ \
@synchronized(self) \
{ \
if (shared##classname == nil) \
{ \
shared##classname = [super allocWithZone:zone]; \
return shared##classname; \
} \
} \
\
return nil; \
} \
para:
+ (id)allocWithZone:(NSZone *)zone \
{ \
@synchronized(self) \
{ \
if (shared##classname == nil) \
{ \
shared##classname = [super allocWithZone:zone]; \
} \
} \
\
return shared##classname; \
} \