0

I am creating an app that needs to store high scores from multiple different levels. I'm using the excellent tutorial over at http://www.raywenderlich.com/63235/how-to-save-your-game-data-tutorial-part-1-of-2 as a basis for what I'm trying to do. As per their suggestion I used the keychainwrapper from https://gist.github.com/dhoerl/1170641

In my iOS sim out of Xcode, the gamestate saving works perfectly fine, I can close Xcode, or even reboot my mac and the high score shows up like it should. But when I use my iPhone 5 as the destination, it doesn't work at all. If I kill the app, the high score goes away. Not sure what I'm missing on this deal.

Here's my gameState.h:

#import <Foundation/Foundation.h>

@interface GameState : NSObject <NSCoding>

@property (assign, nonatomic) long score;
@property (assign, nonatomic) long highScoreL1;
@property (assign, nonatomic) long highScoreL2;
@property (nonatomic) long levelIndex;
@property (nonatomic) long lvlIndexMax;

+(instancetype)sharedGameData;
-(void)reset;
-(void)resetAll;
-(void)save;

@end

And here's my gameState.m

 #import "GameState.h"
 #import "KeychainWrapper.h"

 @implementation GameState

 static NSString* const SSGameDataChecksumKey = @"SSGameDataChecksumKey";
 static NSString* const SSGameDataHighScoreL1Key = @"highScoreL1";
 static NSString* const SSGameDataHighScoreL2Key = @"highScoreL2";

 -(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeDouble:self.highScoreL1 forKey:SSGameDataHighScoreL1Key];
[encoder encodeDouble:self.highScoreL2 forKey:SSGameDataHighScoreL2Key];
}

-(instancetype)initWithCoder:(NSCoder *)decoder {
self = [self init];
if (self) {
    _highScoreL1 = [decoder decodeDoubleForKey:SSGameDataHighScoreL1Key];
    _highScoreL2 = [decoder decodeDoubleForKey:SSGameDataHighScoreL2Key];
}

return self;
}

+(NSString*)filePath {
static NSString* filePath = nil;
if (!filePath) {
    filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]stringByAppendingString:@"gamedata"];
}

return filePath;
}

+(instancetype)loadInstance {
NSData* decodedData = [NSData dataWithContentsOfFile: [GameState filePath]];
if (decodedData) {
    NSString* checksumOfSavedFile = [KeychainWrapper computeSHA256DigestForData:decodedData];
    NSString* checksumInKeychain = [KeychainWrapper keychainStringFromMatchingIdentifier:SSGameDataChecksumKey];

    if ([checksumOfSavedFile isEqualToString: checksumInKeychain]){
        GameState* gameData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
        return gameData;
    }
}

return [[GameState alloc]init];
}

-(void)save {
NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject: self];
[encodedData writeToFile:[GameState filePath] atomically:YES];
NSString* checksum = [KeychainWrapper computeSHA256DigestForData: encodedData];
if ([KeychainWrapper keychainStringFromMatchingIdentifier:SSGameDataChecksumKey]) {
    [KeychainWrapper updateKeychainValue:checksum forIdentifier:SSGameDataChecksumKey];
} else {
    [KeychainWrapper createKeychainValue:checksum forIdentifier:SSGameDataChecksumKey];
}
}

+(instancetype)sharedGameData {
static id sharedInstance = nil;

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    sharedInstance = [self loadInstance];
});

return sharedInstance;
}

-(void)reset {
self.score = 0;
}

-(void)resetAll {
self.score = 0;
//self.highScoreL1 = 0;
//self.highScoreL2 = 0;
}

@end

Inside each level I track score, then when the user loses the game, I use the following line to update high score before going to the Game Over scene:

[GameState sharedGameData].highScoreL1 = MAX([GameState sharedGameData].score, [GameState sharedGameData].highScoreL1);

Finally, inside of Game Over, I use

[[GameState sharedGameData] save];

to save the high score.

Thanks in advance for the help, any direction at all would be wonderful. Let me know if you need any more info!

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • the safest way to save local score is to use nsuserdefaults for e.g. -(void)setTopscore:(long long)newScore { NSString *newhighScore = [NSString stringWithFormat:@"%lld", (long long)newScore]; [_userlocalData setObject:newhighScore forKey:topScore]; [_userlocalData synchronize]; } //get top score from local data storage====================================================================// -(NSString*)getTopscore { NSString *newhighScore = [_userlocalData stringForKey:topScore]; return newhighScore; } – dragoneye Oct 06 '14 at 05:57

1 Answers1

0

the safest and the fastest way to save local data between different scenes is to use nsuserdefaults for e.g.

//static variable

static  NSString * topScore= @"topScore";




-(void)setTopscore:(long long)newScore
{

    NSString *newhighScore = [NSString stringWithFormat:@"%lld", (long long)newScore];

    [_userlocalData setObject:newhighScore forKey:topScore];
    [_userlocalData synchronize];
}


//get top score from local database storage====================================================================//
-(NSString*)getTopscore
{
    NSString *newhighScore = [_userlocalData stringForKey:topScore];
    return newhighScore;
}

i tried it and its working without any problem on all iOS device and mac itself

dragoneye
  • 703
  • 6
  • 14
  • Gotcha. That definitely makes sense, but I have always read that using NSUserDefaults is pretty insecure and super easy to hack. – Cameron Frank Oct 06 '14 at 22:51
  • Also, iCloud and GameCenter sync would be a plus. Which I guess I could just pull the NSUserDefault info and send that way when necessary. Thoughts? – Cameron Frank Oct 06 '14 at 22:53
  • agree with u nsuserdefault are insecure and easy to hack but working with games is time saving process if u r looking for safest way to save data go for coredata. – dragoneye Oct 07 '14 at 05:08