0

I have subclassed a CCSprite to make an object that can be encoded and decoded. I want to save sprites state and load it again at particular positions. Everything seems to be okay apart from decoding with NSKeyedUnarchiver (see loadIconSpriteState below) which gives an EXC_BAD_ACCESS Below is the code:

HelloWorldLayer.h

@interface CCIconSprite : CCSprite {
   NSString *iconName;
   float iconXPos;
   float iconYPos;
}

@property (nonatomic, retain) NSString *iconName;
@property (nonatomic, assign) float iconXPos;
@property (nonatomic, assign) float iconYPos;

+ (id)iconWithType:(NSString*)imageName;
- (id)initWithIconType:(NSString*)imageName;
@end

@interface HelloWorldLayer : CCLayer < NSCoding>
{
    CCIconSprite* testSprite;
    BOOL savedState;
    CGSize size;
    CCMoveTo* moveTo;
    NSMutableArray* saveSpriteArray;
    NSData* savedSpriteData;

}
+(CCScene *) scene;

@end

HelloWorldLayer.m:

CCIconSprite implementation:

@implementation CCIconSprite
@synthesize  iconXPos;
@synthesize iconYPos;
@synthesize iconName;

+ (id)iconWithType:(NSString*)imageName
{
    return [[[[self class] alloc] initWithIconType:imageName] autorelease];
}

- (id)initWithIconType:(NSString*)imageName
{
   self = [super initWithFile:imageName];
   if (!self) return nil;

   iconName = imageName;

   self.position = ccp(iconXPos, iconYPos);

   return self;
}

Encoding and decoding:

- (id)initWithCoder:(NSCoder *)decoder {
   NSString* imageFileName = [decoder decodeObjectForKey:@"imageFileName"];
   self = [self initWithIconType:imageFileName];
   if (!self) return nil;

   self.iconXPos = [decoder decodeFloatForKey:@"iconXPos"];
   self.iconYPos = [decoder decodeFloatForKey:@"iconYPos"];

   return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder
{
   [encoder encodeObject:iconName forKey:@"imageFileName"];
   [encoder encodeFloat:self.iconXPos forKey:@"iconXPos"];
   [encoder encodeFloat:self.iconYPos forKey:@"iconYPos"];
}

@end

HelloWorldLayer implementation:

@implementation HelloWorldLayer
+(CCScene *) scene
{
CCScene *scene = [CCScene node];
HelloWorldLayer *layer = [HelloWorldLayer node];
[scene addChild: layer];
    return scene;
}

-(id) init
{
if( (self=[super init]) ) {
    self.scale = 0.5;
    savedState = NO;

    size = [[CCDirector sharedDirector] winSize];

    testSprite = [CCIconSprite spriteWithFile:@"Icon.png"];
    testSprite.position = ccp(size.width/4, size.width/4);
    testSprite.anchorPoint = ccp(0,0);
    [self addChild:testSprite];

    moveTo = [CCMoveTo actionWithDuration:3 position:ccp(3*size.width/4, 3*size.width/4)];
    [testSprite runAction:moveTo];

    [self schedule:@selector(saveAndLoadSpriteState)];

}
return self;
}

Saving and loading state:

-(void)saveIconSpriteState  {
    saveSpriteArray = [[NSMutableArray alloc] init];
    [saveSpriteArray addObject:testSprite];
    savedSpriteData = [NSKeyedArchiver archivedDataWithRootObject:saveSpriteArray];
 }

-(void)loadIconSpriteState  {
   [NSKeyedUnarchiver unarchiveObjectWithData:savedSpriteData];
}

-(void)saveAndLoadSpriteState    {
  if ((testSprite.position.x > size.width/2) && !savedState) {
    savedState = YES;
    [self saveIconSpriteState];
}
else if ((testSprite.position.x == 3*size.width/4) && savedState) {
    savedState = NO;
    [self loadIconSpriteState];
    [testSprite runAction:moveTo];
    }
}

@end

EDIT

After setting an exception break point I got the following error

Assertion failure in -[CCIconSprite initWithFile:]

pointing to the in line:

NSAssert(filename != nil, @"Invalid filename for sprite");

in the

-(id) initWithFile:(NSString*)filename

method of the CCSprite.m class.

  • Where exactly does it crash? Enable exception breakpoint if you haven't already. Did you single-step through to see if the imageName is encoded/decoded properly? Are you aware that you're using self.position = ccp(iconXPos, iconYPos); before iconXYPos are decoded? Why aren't you using ARC? Please do! – CodeSmile Nov 13 '13 at 19:16
  • @LearnCocos2D: Please see the edit I added. Also I've enabled ARC as you recommended. About the positions, how should I decode them before using them? I have to admit, I wasn't too sure about how I handled the positions. – errorallergic Nov 13 '13 at 23:42
  • You should set self.position = ccp(iconXPos, iconYPos); in initWithCoder after reading the variables. – CodeSmile Nov 14 '13 at 00:16

1 Answers1

0

Just a hunch but maybe that's it. When decoding a string, you should copy it because you don't own it at this point, nor are you assigning it to a strong or copy property.

- (id)initWithCoder:(NSCoder *)decoder {
   NSString* imageFileName = [[decoder decodeObjectForKey:@"imageFileName"] copy];
   self = [self initWithIconType:imageFileName];
   if (!self) return nil;
   ...
}

If that's not it, set a breakpoint and verify that the string is indeed correct when encoding and when decoding.

CodeSmile
  • 64,284
  • 20
  • 132
  • 217
  • I realized that I was using `+(id)spriteWithFile:(NSString*)filename` on the `testSprite` instead of `+ (id)iconWithType:(NSString*)imageName` so the `imageFileName` was `nil`. It's working now, Thanks. But now I would like to save the state of the above `HelloWorldLayer`, How will that work out? Do I need to subclass from `CCLayer`? – errorallergic Nov 14 '13 at 11:55