Animation must be non-nil in Cocos2d - objective-c

I am trying to create a CCSprite subclass called Enemy
#import "Enemy.h"
#implementation Enemy
{
CCSprite* ant;
CCAnimation *walkAnim ;
}
-(id)init
{
self = [super init];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"char.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"char.png"];
[self addChild:spriteSheet];
NSMutableArray *walkAnimFrames = [NSMutableArray array];
for (int i=1; i<=3; i++) {
[walkAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"antNormal_%d.png",i]]];
}
walkAnim = [CCAnimation animationWithSpriteFrames:walkAnimFrames delay:0.1f];
self = [CCSprite spriteWithSpriteFrameName:#"antNormal_1.png"];
CCAction* walkAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:walkAnim]];
[self runAction:walkAction];
return self;
}
#end
and then in my gamelayer I have the enemies added like this
enemies=[[CCArray alloc] initWithCapacity:100];
for (int i=0; i<10; i++) {
Enemy* ant=[[Enemy alloc] init];
[ant setPosition:ccp(100*i,100)];
[enemies addObject:ant];
}
But this is causing the program to crash on start with the error
'NSInternalInconsistencyException', reason: 'Animate: argument Animation must be non-nil'
If I comment out the CCAction though, the enemies display correctly, just without animation(obviously). Not sure how to solve this at the moment.

While I can't speak to Cocos2d specifically, there's several things wrong in your init method from an ObjC standpoint that probably contribute to the problem.
self is set self twice. The second one creates a new instance of the class and clobbers the old one. You should only set self to the result of an init method, not a class factory method
self is not checked for nil. You should always check self for nil before setting any ivars. If the init method you call fails, if will be nil and your app will crash.
walkAnim is not retained, it will be released at a later time and your app will crash when you try to use it. It must be retained.
Do not call methods on self in your custom init methods. By definition, your object is not in a fully initialized state inside init methods. Strange things can occur if you call methods on self. Other init methods are an exception.
While I cannot guarantee this is free of bugs, as I am not personally familiar with Cocoas2d, this should get you closer to what you want:
-(id)init
{
self = [CCSprite initWithSpriteFrameName:#"antNormal_1.png"];
if (self)
{
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"char.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"char.png"];
[self addChild:spriteSheet];
NSMutableArray *walkAnimFrames = [NSMutableArray array];
for (int i=1; i<=3; i++) {
[walkAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"antNormal_%d.png",i]]];
}
walkAnim = [[CCAnimation animationWithSpriteFrames:walkAnimFrames delay:0.1f] retain];
}
return self;
}
-(void)startWalkAction
{
CCAction* walkAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:walkAnim]];
[self runAction:walkAction];
}
And then add enemies like this:
enemies=[[CCArray alloc] initWithCapacity:100];
for (int i=0; i<10; i++) {
Enemy* ant=[[Enemy alloc] init];
[ant setPosition:ccp(100*i,100)];
[ant startWalkAction];
[enemies addObject:ant];
}

Related

What is the proper way to implement animation in a CCSprite subclass?

in my game I made a subclass of CCSprite (cocos2d) for my enemies.
Because I want control over animation, from within every instance of this subclass, I had to translate my animation code in my main class, which loads
the enemies, to this subclass.
I found this rather hard but.... after some time it magically started to work.
Unfortunately after creating and setting properties in this subclass I started to have weird crashes. Because they are in cocosDenshion and other places which have nothing to do with my
enemy class and after a research in depth in my code and on the net, I'm convinced its some kind of data corruption and I'm almost completely certain its because I did my enemie class with his animation code completely the wrong way.
To be honest, I cannot even wrap my mind around what is going on here anymore and how this actually works :S...I'm completely stuck. any help is much appreciated!
So my main questions would be: What is the proper way to implement animation in a CCSprite subclass ? / what am I doing wrong here?
simplified my code here: (it triggers the animation every 2 seconds to show how I want
to use it)
#import "cocos2d.h"
#interface Npc : CCSprite
{
CCAction *_runAnimation;
NSString* name;
int strength;
}
#property (nonatomic, retain) CCAction *runAnimation;
#property int strength;
#property (nonatomic, retain) NSString* name;
- (Npc*)loadAnimation;
- (void)animate;
#end
#import "Npc.h"
#implementation Npc
#synthesize runAnimation = _runAnimation;
#synthesize name;
#synthesize strength;
-(id) initWithTexture:(CCTexture2D*)texture rect:(CGRect)rect
{
if( (self=[super initWithTexture:texture rect:rect]))
{
}
return self;
}
- (Npc*)loadAnimation
{
int lastFrame = 11;
NSString *creatureFile = #"vis 1";
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:
[NSString stringWithFormat:#"%#.plist", creatureFile]];
CCSpriteBatchNode* sheet = [CCSpriteBatchNode batchNodeWithFile:
[NSString stringWithFormat:#"%#.png", creatureFile]];
self = [Npc spriteWithTexture:sheet.texture];
NSMutableArray* animFrames = [[NSMutableArray alloc] init];
for (int x = 0; x < lastFrame; x++)
{
[animFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"%# %d.png", creatureFile, x]]];
}
CCAnimation* anim = [CCAnimation animationWithFrames: animFrames delay: 0.1];
self.runAnimation = [CCAnimate actionWithAnimation:anim restoreOriginalFrame:NO];
[self runAction:_runAnimation];
return self;
}
- (void)animate
{
[self runAction:self.runAnimation];
}
- (void)dealloc
{
[super dealloc];
[name release];
}
#end
#import "HelloWorldLayer.h"
#import "Npc.h"
#implementation HelloWorldLayer
+(CCScene *) scene
{
CCScene *scene = [CCScene node];
HelloWorldLayer *layer = [HelloWorldLayer node];
[scene addChild: layer];
return scene;
}
-(id) init
{
if( (self=[super init]))
{
timer = 0;
creatureTemp = [Npc spriteWithFile:#"Icon.png"];
creature = [creatureTemp loadAnimation];
creature.position = ccp(100,100);
[self addChild:creature];
[self schedule:#selector(nextFrame:)];
}
return self;
}
- (void)nextFrame:(ccTime)dt
{
timer += dt;
if (timer > 2.)
{
[creature animate];
timer = 0.;
}
}
- (void) dealloc
{
[super dealloc];
}
#end
-------------------EDIT--------------------
I changed my code with help of a tutorial by Ray Wenderlich: http://www.raywenderlich.com/3888/how-to-create-a-game-like-tiny-wings-part-1
this is I think much closer to what it should be. Unfortunately it still crashes on my iphone (not the simulator) on SimpleAudioEngine (which I implement right) so I still do something wrong.
on top of the Npc class:
#synthesize batchNode = _batchNode;
the init of the Npc class:
-(id) initNpc
{
if( (self=[super initWithSpriteFrameName:#"vis 1 0.png"]))
{
_normalAnim = [[CCAnimation alloc] init];
NSMutableArray* animFrames = [[NSMutableArray alloc] init];
int lastFrame = 11;
for (int x = 0; x < lastFrame; x++)
{
[animFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"vis 1 %d.png", x]]];
}
_normalAnim = [CCAnimation animationWithFrames: animFrames delay: 0.1];
self.runAnimation = [CCAnimate actionWithAnimation:_normalAnim restoreOriginalFrame:NO];
}
return self;
}
and the init of the HelloWorldLayer
-(id) init
{
if( (self=[super init]))
{
timer = 0;
_batchNode = [CCSpriteBatchNode batchNodeWithFile:#"vis 1.png"];
[self addChild:_batchNode];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"vis 1.plist"];
creature = [[[Npc alloc] initNpc] autorelease];
creature.position = ccp(200,200);
[_batchNode addChild:creature];
[self schedule:#selector(nextFrame:)];
[[SimpleAudioEngine sharedEngine] preloadEffect:#"super1.mp3"];
}
return self;
}
You're reassigning self in loadAnimation:
self = [Npc spriteWithTexture:sheet.texture];
At that point I stopped reading the code. Since self already is an instance of the Npc class you have to ask yourself why you're doing this in an Npc instance method like loadAnimation.
So, I got it....here is the code:
Npc.h:
#import "cocos2d.h"
#interface Npc : CCSprite
{
CCAction *_runAnimation;
CCAnimation *_normalAnim;
CCAnimate *_normalAnimate;
}
#property (nonatomic, retain) CCAction *runAnimation;
- (void)animate;
- (id)initNpc;
#end
Npc.m
#synthesize runAnimation = _runAnimation;
#synthesize batchNode = _batchNode;
-(id) initNpc
{
if( (self=[super initWithSpriteFrameName:#"vis 1 0.png"]))
{
_normalAnim = [[CCAnimation alloc] init];
NSMutableArray* animFrames = [[NSMutableArray alloc] init];
int lastFrame = 7;
for (int x = 0; x < lastFrame; x++)
{
[animFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"vis 1 %d.png", x]]];
}
_normalAnim = [CCAnimation animationWithFrames: animFrames delay: 0.1];
self.runAnimation = [CCAnimate actionWithAnimation:_normalAnim restoreOriginalFrame:NO];
}
return self;
}
- (void)animate
{
[self runAction:_runAnimation];
}
in the HelloWorldLayer.m
-(id) init
{
if( (self=[super init]))
{
_batchNode = [CCSpriteBatchNode batchNodeWithFile:#"vis 1.png"];
[self addChild:_batchNode];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"vis 1.plist"];
timer = 0;
creature = [[[Npc alloc] initNpc] autorelease];
creature.position = ccp(200,200);
[_batchNode addChild:creature];
[self schedule:#selector(nextFrame:)];
}
return self;
}
- (void)nextFrame:(ccTime)dt
{
timer += dt;
if (timer > 2.)
{
[creature animate];
timer = 0.;
}
}
And about the weird crashes in cocosDenshion. That is also solved...it turned out to be a known bug in SimpleAudioEngine where it threw exceptions only when I had an exception breakpoint active. Workaround: made a class for my sound and if I need a exception breakpoint, I comment out the sound...
-- have to say, I do would prefer the:
_batchNode = [CCSpriteBatchNode batchNodeWithFile:#"vis 1.png"];
[self addChild:_batchNode];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"vis 1.plist"];
inside the Npc class, but that is too advanced oop for me. If anybody knows that, let me know...would be great to know actually, to understand oop better.
But it is not strictly necessary...

NSArray causes EXC_BAD_ACCESS

I declare an NSArray in one class like this:
.h
#interface HTTP : NSObject {
NSArray *listOfProfiles;
}
#property (nonatomic, retain) NSArray *listOfProfiles;
.m
-(id) init {
if ((self = [super init])) {
listOfProfiles = [[NSArray alloc] init];
}
return self;
}
-(void) someMethod {
...
case GET_LIST_OF_PROFILES:
listOfProfiles = [result componentsSeparatedByString:#"^-^"];
NSLog(#"first break: %#",[listOfProfiles objectAtIndex:0]);
break;
...
}
I can access it here just fine, then when I try to access it in another class after creating an object I receive the error EXC_BAD_ACCESS and the debugger goes to main.m:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
http = [[HTTP alloc] init];
[http createProfileArray];
profileListDelay = [[NSTimer alloc] init];
profileListDelay = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(profileListSelector) userInfo:nil repeats:YES];
}
- (void) profileListSelector
{
if (http.activityDone)
{
// http.listofprofiles mem leak?
for (int i = 0; i < http.listOfProfiles.count; i++)
{
NSLog(#"%#",[http.listOfProfiles objectAtIndex:i]);
}
[profileListDelay invalidate];
profileListDelay = nil;
}
}
I'm thinking it's a memory issue maybe, but I could be completely wrong.
It is a memory issue
It is in someMethod
-(void) someMethod {
...
case GET_LIST_OF_PROFILES:
listOfProfiles = [result componentsSeparatedByString:#"^-^"];
NSLog(#"first break: %#",[listOfProfiles objectAtIndex:0]);
break;
...
}
componentsSeparatedByString: returns an autoreleased object
Since you declared the array as a retain property, you should update it like so:
self.listOfProfiles = [result componentsSeparatedByString:#"^-^"];

Improve Load Time of Sectioned UITableView

I am displaying a UITableView modally, but it takes about two seconds for it to appear, below is the code that is holding up the transition.
ModalViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
// get all songs from iTunes library
MPMediaQuery *songQuery = [MPMediaQuery songsQuery];
// put the songs into an array
self.songsArray = [songQuery items];
// create a sectioned array where songs are sectioned by title
self.sectionedSongsArray = [self partitionObjects:self.songsArray collationStringSelector:#selector(title)];
}
- (NSArray *)partitionObjects:(NSArray *)array collationStringSelector:(SEL)selector
{
UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation];
NSInteger sectionCount = [[collation sectionTitles] count];
NSMutableArray *unsortedSections = [NSMutableArray arrayWithCapacity:sectionCount];
for(int i = 0; i < sectionCount; i++)
{
[unsortedSections addObject:[NSMutableArray array]];
}
for (id object in array)
{
NSInteger index = [collation sectionForObject:object collationStringSelector:selector];
[[unsortedSections objectAtIndex:index] addObject:object];
}
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (NSMutableArray *section in unsortedSections)
{
[sections addObject:[collation sortedArrayFromArray:section collationStringSelector:selector]];
}
return sections;
}
The above code works fine, but its slow to load the modal view first time, is there a better way to do this? Thanks.
Yeah: don’t do it in -viewDidLoad. A better place would be in the view controller’s -init or -initWithNibNamed:bundle: or whatever, and in the background. Example:
- (id)init
{
self = [super init];
if(self)
{
// ...
dispatch_async(dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT, 0), ^{
// since it's not on the main thread, you need to create your own autorelease pool to prevent leaks
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
MPMediaQuery *songQuery = [MPMediaQuery songsQuery];
self.songsArray = [songQuery items];
self.sectionedSongsArray = [self partitionObjects:self.songsArray collationStringSelector:#selector(title)];
// UI calls have to be on the main thread, so we go back to that here
dispatch_async(dispatch_get_main_queue(), ^{
if([self isViewLoaded])
{
[self.tableView reloadData];
}
});
// this releases any objects that got autoreleased earlier in the block
[pool release];
});
}
return self;
}
Your -tableView:numberOfRowsInSection: method should of course now check whether sectionedSongsArray is non-nil and in that case return 0 (or 1 if you want to display a “loading” cell, which you probably should).

cocos2d sprite animation "SIGABRT"

i have followed many videos on how tho animate sprites in cocos2d. But i seem to always com across this when the code is run.
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode SpriteSheetWithFile:#"animbear.png"];
it says (Thread 1: Program received signal "SIGBRT")
im not sure what do do I've tried everything i can please help?
heres the code within the init method
heres the part of the code!
// Import the interfaces
#import "HelloWorldLayer.h"
// HelloWorldLayer implementation
#implementation HelloWorldLayer
// At the top, under #implementation
#synthesize bear = _bear;
#synthesize moveAction = _moveAction;
#synthesize walkAction = _walkAction;
-(id) init {
if((self = [super init])) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:
#"bear.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode
batchNodeWithFile:#"bear.jpg"];
[self addChild:spriteSheet];
NSMutableArray *walkAnimFrames = [NSMutableArray array];
for(int i = 1; i <= 8; ++i) {
[walkAnimFrames addObject: *****THIS IS WERE (PROGRAM RECEIVED SIGNAL:"SIGABRT***
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"bear%d.png", i]]];
}
CCAnimation *walkAnim = [CCAnimation
animationWithFrames:walkAnimFrames delay:0.1f];
CGSize winSize = [CCDirector sharedDirector].winSize;
self.bear = [CCSprite spriteWithSpriteFrameName:#"bear.jpg"];
_bear.position = ccp(winSize.width/2, winSize.height/2);
self.walkAction = [CCRepeatForever actionWithAction:
[CCAnimate actionWithAnimation:walkAnim restoreOriginalFrame:NO]];
[_bear runAction:_walkAction];
[spriteSheet addChild:_bear];
}
return self;
}
Have you added animbear.png to your project?
Make Sure you SAVE the sheet before publishing the PNG and PLIST file using Zwoptex

NSCFArray leak in the NSMutablearray allocation

I am getting the leak at this allocation
filteredListContent = [[NSMutableArray alloc] initWithCapacity:[showList count]];
CODE:
-(void)reloadTable
{
EventListAppDelegate *appDelegate;
UIApplication app = [UIApplication sharedApplication];
appDelegate = (EventListAppDelegate *)[app delegate];
contactList = [appDelegate getAllContactsList];
inviteeList = [appDelegate getInviteeListForEvent:event.primaryKey];
if (isInvited == YES)
{
showList = [appDelegate getInviteeListForEvent:event.primaryKey];
}
else
{
showList = [appDelegate getAllContactsList];
}
filteredListContent = [[NSMutableArray alloc] initWithCapacity:
[showList count]];
[filteredListContent addObjectsFromArray: showList];
[self organizeContactItemsIntoIndexes];
self.title = [event.name capitalizedString];
[self getToolbar];
[theTableView reloadData];
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[filteredListContent removeAllObjects];
ContactDTO *currentElement;
NSRange range;
for (currentElement in showList)
{
range = [currentElement.lastName rangeOfString:searchText
options:NSCaseInsensitiveSearch];
if(range.location == 0)
{
[filteredListContent addObject:currentElement];
}
}
[self organizeContactItemsIntoIndexes];
[theTableView reloadData];
}
- (void)dealloc
{
[filteredListContent release];
[super dealloc];
}
Your code will allocate a new instance of filteredListContent every time reloadTable is called, which will usually happen several times during the lifetime of your application. This causes a leak because the old instances are not released.
The best (and easiest) way to fix it would be to make filteredListContent a retain property:
in your class header:
#property (nonatomic, retain) NSMutableArray * filteredListContent;
in your reloadTable method:
self.filteredListContent = [NSMutableArray arrayWithCapacity:[showList count]];
Note the use of self. in the second code snippet. That syntax informs Cocoa that it should use the property accessor to set the value of filteredListContent, which will then send the appropriate retain and release messages for you.
You've posted three nearly-identical questions pertaining to memory leaks. It might be helpful for you to read through Apple's Memory Management Programming Guide.