My spriteSheet animation crashes my app - iphone

so I'm creating a sprite every second but now I would like to replace this sprite by a spriteSheetAnimation. but when I run this code my app crashes :
- (void)spawnCat {
CCSpriteBatchNode *spriteSheet2 = [CCSpriteBatchNode batchNodeWithFile:#"AnimBulle.png"];
[self addChild:spriteSheet2];
// Load up the frames of our animation
NSMutableArray *walkAnimFrames = [NSMutableArray array];
for(int i = 1; i <= 8; ++i) {
[walkAnimFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"AnimBulle%d.png", i]]];
}
CCAnimation *walkAnim = [CCAnimation animationWithFrames:walkAnimFrames
delay:0.1f];
[target runAction:_walkAction];
target.tag = 1;
[_targets addObject:target];
[self addBoxBodyForSprite:target];
[spriteSheet2 addChild:target z:0 tag:1];
id actionMove = [CCMoveTo actionWithDuration:actualDuration
position:ccp(240, 160)];
id actionMoveDone = [CCCallFuncN actionWithTarget:self
selector:#selector(spriteDone:)];
[target runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];
}

EDIT:
From the kind of error you get (not enough stack traces), you have an inifinite recursion (i.e., a method that ends up calling itself, either directly or indirectly). The code in spawnCat does not seem to entail such an infinite recursion, so the problem must be somewhere else.
How do you call spawnCat? could you put some NSLog traces in the methods that are executed to create the animation, so that you see if someone keeps calling itself?
ORIGINAL ANSWER:
Are you sure this is what you meant?
CCAnimation *walkAnim = [CCAnimation animationWithFrames:walkAnimFrames delay:0.1f];
[target runAction:_walkAction];
I can't see _walkAction initialization, but I would expect you do that after creating the animation by doing something like:
CCAnimation *walkAnim = [CCAnimation animationWithFrames:walkAnimFrames delay:0.1f];
_walkAction = [CCAnimate actionWithAnimation:walkAnim];
[target runAction:_walkAction];

The only thing i can see in the code is the possibility that your spriteFrame is not found. When you try to add nil to an array, you can get nasty outcomes. I would amend the loop as follows (logging helps).
for(int i = 1; i <= 8; ++i) {
NSString *sfn = [NSString stringWithFormat:#"AnimBulle%d.png", i];
CCSpriteFrame *sf = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:sfn];
if(sf) {
[walkAnimFrames addObject:sf];
} else {
CCLOGERROR(#"%#<spawnCat> : *** Sprite frame named [%#] not found in cache, bailing out.",self.class,sfn);
return; // ok here, nothing is retained before this may be executed
}
}
Also, can you ensure that you actually loaded the spriteFrameCache with the .plist and texture before invoking this AND that you dont removeUnusedSpriteFrames from the cache anywhere else in the run loop.

Related

why CCAnimation does not use CCSpriteBatchNode to print its images?

why CCSpriteBatchNode is not explicitly used with CCAnimation ? instead we use the following : (instead of adding each image to the batchNode and let the batchNode print those images, the code only uses spriteFrameByName) :
CCSpriteBatchNode *chapter2SpriteBatchNode = [CCSpriteBatchNode batchNodeWithFile:#"scene1atlas.png"];
CCSprite *vikingSprite = [CCSprite spriteWithSpriteFrameName:#"sv_anim_1.png"];
[chapter2SpriteBatchNode addChild:vikingSprite];
// Animation example with a CCSpriteBatchNode
CCAnimation *exampleAnim = [CCAnimation animation];
[exampleAnim addFrame:
[[CCSpriteFrameCache sharedSpriteFrameCache]
spriteFrameByName:#"sv_anim_2.png"]];
Thanks for your answer
A batchNode is just a texture draw artefact ... has no knowledge whatsoever about what frames the texture contains, nor whether it embeds multiple 'logical' files in a single texture. You must create that association, typically by adding spriteFrame's to the spriteFrameCache, each spriteFrame providing metadata about a 'fragment' of the batchNode. Here is an example from one of my games:
-(void) setupIdleAnimation{
[self setupAnimations];
NSString* animationName = #"Idle";
NSString* framesFileName = [self getPlistFileNameForAction:animationName];
CCSpriteBatchNode *bn = [CCSpriteBatchNode batchNodeWithFile:[self getTextureFileNameForAction:animationName]];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:framesFileName texture:bn.texture];
// create array of frames for the animation
self.idleBatchNode=bn;
NSMutableArray *animFrames = [NSMutableArray array];
for(NSUInteger i = 1; i <= 8; ++i) {
NSString *sfn = [self getFrameNameForAnimation:animationName
andFrameNumber:i];
CCSpriteFrame *sf = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:sfn];
if(sf) {
[animFrames insertObject:sf atIndex:i-1];
} else {
CCLOGERROR(#"%#<setupIdleAnimation> : *** Sprite frame named [%#] not found in cache, bailing out.",self.class,sfn);
return;
}
}
CCAnimation *anim=[CCAnimation animationWithFrames:walkAnimFrames delay:ANIM_FRAME_DELAY];
[animFrames removeAllObjects];
anim.name=animationName;
self.idleAction = [CCRepeatForever
actionWithAction:[CCAnimate actionWithAnimation:anim
restoreOriginalFrame:NO]] ;
self.idleSprite = [CCSprite spriteWithSpriteFrameName:[self getFrameNameForAnimation:animationName
andFrameNumber:1]];
self.idleSprite.visible=NO;
[self.idleBatchNode addChild:self.idleSprite];
}
So i have prepared in a .plist the data that describes where each frame is located in the texture, and add these definitions to the frame cache. The batchNode per say is a container that will optimise rendering performance. In the above example it is well suited singe the texture embeds the idle sprites for 16 character classes that are often in view and idling simultaneously.
You can find a good introduction in this tutorial.

Cocos2d game termination problem when a moving sprite goes inside the bounding box of a animating still sprite(a ball get into a hole)

Let me explain it in depth, when ever if(CGRectContainsPoint([hole1 boundingBox], ball1.position)) condition goes true, i do lots of stuffs, like unscheduled, a selector, destroying a ball body calling an animation (please refer Code below)etc. This work properly most of the time. But sometimes when ball is really near to hole(just touching the hole but but not enough to make the above condition true), or is been throws towards the hole really fast speed, then application got terminated. I have checked, by commenting many actions which are been performed in this section, but got nothing helpful, application keep terminating when some efforts are been done to make it terminate.
if(CGRectContainsPoint([hole1 boundingBox], ball1.position))
{
ballBody->SetLinearVelocity(b2Vec2(0.0f,0.0f));
ballBody->SetAngularVelocity(0.0f);
[self unschedule:#selector(tick:)];
self.isTouchEnabled = NO;
[self removeChild:ball1 cleanup:YES];
world->DestroyBody(ballBody);
// create the sprite sheet
CCSpriteSheet *spriteSheet;
GolfBallsAppDelegate *appDelegate = (GolfBallsAppDelegate *)[[UIApplication sharedApplication] delegate];
if([appDelegate.ballValue isEqualToString:#"cricketball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"cricket_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"ironball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"iron_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"golfball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"golf_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"soccerball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"soccer_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"basketball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"basket_ball_strip.png"];
}
spriteSheet.position = ccp(hole1.position.x,60);
[self addChild:spriteSheet];
float frameWidth = 96;
float frameHeight = 84;
CCSprite *sprite = [CCSprite spriteWithTexture:spriteSheet.texture rect:CGRectMake(0, 0, frameWidth, frameHeight)];
[spriteSheet addChild:sprite];
//if(animation)
{
// create the animation
CCAnimation *spriteAnimation = [CCAnimation animationWithName:#"potting" delay:0.1f];
int frameCount = 0;
for (int x = 0; x < 6; x++)
{
// create an animation frame
CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:spriteSheet.texture rect:CGRectMake(x*frameWidth,0*frameHeight,frameWidth,frameHeight) offset:ccp(0,0)];
[spriteAnimation addFrame:frame];
frameCount++;
// stop looping after we've added 14 frames
if (frameCount == 6)
{
//[self removeChild:spriteSheet cleanup:YES];
break;
}
}
// create the action
CCAnimate *spriteAction = [CCAnimate actionWithAnimation:spriteAnimation];
//CCRepeatForever *repeat = [CCRepeatForever actionWithAction:spriteAction];
// run the action
[sprite runAction:spriteAction];
//[sprite runAction:repeat];
}
[self schedule:#selector(loading) interval:0.5];
[self schedule:#selector(holeFinish) interval:1];
//[self removeChild:spriteSheet cleanup:YES];
}
Any suggestion will be highly appreciated.
EDIT: What i found is, problem with folling lines [self removeChild:ball1 cleanup:YES];
world->DestroyBody(ballBody);
(MAY be). but as it not occurs always, (as I mentioned), thus it's being ridiculous.
I think your problem will be that you are trying to delete a body when the b2World is 'locked', (When the world is busy working out collisions).
Try flagging the object as ready for deletion, and deleting it at the start of your next loop:
Replace:
[self removeChild:ball1 cleanup:YES];
world->DestroyBody(ballBody);
with
ball1.isDead = YES;
And at the start of your next game loop:
for (Ball b in balls)
{
if (b.isDead)
world->DestroyBody(b.ballBody);
}

Animations in cocos2d

I am wondering if someone can explain me how to re-use animations in cocos2d? I was used to so-called usual way: I create a 'game object', load animations and after that simply call my animations like gameObj->idle(); or gameObj->walkToPoint();...
I understand that these helper methods should be defined by myself, and I also studied a lot of guides about CC2d and using animations in different ways, but these guides are too simple and don't shot real cases.
So at the moment I wrote method which loads animation from plist and png texture, adds this texture to parent layer and assigns first sprite to the 'Game Object' sprite. But I have some questions - I still can't find the way to play animations.
Situation is more difficult for me (but I know that this is usual case :) - animations should be assigned to 'actions' and there should be different types of actions. For example - idle action plays forever, death plays once and stays at the last frame and shoot action plays once and restores previous frame - idle's one.
To make things simple I'll show some basic code w\o complex checks and for..in loops (I wondering who decided store all frames in cc2d animation format as Array and load them with frame_%d, not as Dictionary with structure Animations->Idle->Frames Animations->Shoot->Frames, but this not main point :))
//These things are global
CCAnimation *idleAnim;
CCAction * idleAction; // idle animation should be played forever
CCAnimation *deathAnim;
CCAction * deathAction; // death animation should be played once and stop at last frame
CCAnimation *shootAnim;
CCAction * shootAction; // shoot animation should be played once and idle frame restored
// loading animations with simple for loops
- (id)init {
self = [super initWithColor:ccc4(255,255,255,255)];
if (self) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:
#"player.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode
batchNodeWithFile:#"player.png"];
[self addChild:spriteSheet];
NSMutableArray *idleAnimFrames = [NSMutableArray array];
for(int i = 1; i <= 20; ++i) {
[idleAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"hero_idle01_%04i_Layer-%i.png", i-1, i]]];
}
idleAnim = [CCAnimation animationWithFrames:idleAnimFrames delay:0.1f];
idleAction = [CCRepeatForever actionWithAction:
[CCAnimate actionWithAnimation:idleAnim restoreOriginalFrame:NO]];
NSMutableArray *deathAnimFrames = [NSMutableArray array];
for(int i = 0; i <= 30; ++i) {
[deathAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"hero_death01_%04i_Layer-%i.png", i, i+1]]];
}
deathAnim = [CCAnimation animationWithFrames:deathAnimFrames delay:0.1f];
deathAction = [CCAnimate actionWithAnimation:deathAnim restoreOriginalFrame:NO];
NSMutableArray *shootAnimFrames = [NSMutableArray array];
for(int i = 0; i <= 19; ++i) {
[shootAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"hero_idleshoot02_%04i_Layer-%i.png", i, i+1]]];
}
shootAnim = [CCAnimation animationWithFrames:shootAnimFrames delay:0.1f];
shootAction = [CCAnimate actionWithAnimation:shootAnim restoreOriginalFrame:YES];
self.player = [CCSprite spriteWithSpriteFrameName:#"hero_idle01_0000_Layer-1.png"];
self.player.position = ccp(20, self.size.height/2);
[self.player runAction:shootAction];
[spriteSheet addChild:self.player];
[self.player runAction:[CCRotateTo actionWithDuration:0.0f angle:45]];
[self schedule:#selector(gameLogic:) interval:1.0];
}
return self;
}
This code runs correctly (loads idle animation and plays it forever) but I can't find the way to switch between actions and animatinos nicely. I've seen approaches when for example when someone wan't shoot animation, he deletes original sprite takes first sprite from shoot, plays shoot, deleted shoot sprite, restores original sprite. But what if sprite was rotated? One should pass all the params from original sprite such as flip, rotation, scale, etc. back and forward between sprites? Usually Game Objects are inherited from CCSprite, so this method generally means that one should delete and restore main game object all the time? This is not clear for me.
I tried next things, but they didn't work for me, program just halts and nothing is written to debug (I guest it is because error happens in other thread, the one which is responsible for touch handling).
// somewhere after touch happened in [shootToPoint:(CGPoing)point] method
[idleAction stop];
[shootAction startWithTarget:self.player];
[self.player runAction:[CCSequence actions:
shootAction,
[CCCallFuncN actionWithTarget:self selector:#selector(shooted)],
nil]];
And in shooted method I call again something like this:
[shootActino stop]; // or [self.player stopAllActions] - doesn't matter I guess
[idleAction startWithTarget:self.player];
CCSprite *sprite = (Bullet *)sender; // remove bullet sprite from memory
[self removeChild:sprite cleanup:YES]; // remove bullet sprite from memory
// other routines...
So can someone explain me how to create game objects with helper methods to switch animations? Please don't send me to cc2d docs because for it is unclear how to solve this simple problem from cc2d docs or other tutorial on the web.
Since you are creating your instances with helper methods instead of alloc/initWith... they are getting autoreleased, that's why you get a crash.
For example your idle animation should be created like this:
NSMutableArray *idleAnimFrames = [[NSMutableArray alloc] init];
for(int i = 1; i <= 20; ++i) {
[idleAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"hero_idle01_%04i_Layer-%i.png", i-1, i]]];
}
CCAnimation *idleAnimation = [[CCAnimation alloc] initWithFrames:idleAnimFrames delay:0.1f];
[idleAnimFrames release];
CCAnimate *idleAnimate = [[CCAnimate alloc] initWithAnimation:idleAnimation restoreOriginalFrame:NO];
[idleAnimation release];
idleAction = [[CCRepeatForever alloc] initWithAction:idleAnimate];
[idleAnimate release];
That way you can use this action multiple times until you release it (which you should do in your dealloc method, or somewhere else when you're done with it).

Frame Rate per Second (FPS) starts to gdecrease in Coco2d game iPhone?

i have three simple images each for 2 hens (6 images) which I am trying to animate (a hen walking) using a very good tutorial by Ray Wenderlich:
http://www.raywenderlich.com/1271/how-to-use-animations-and-sprite-sheets-in-cocos2d
The animation works fine when I start the game but after 2-3 mins the Frame Rate begins to drop and slowly it drops to below 10 and the application hangs.. btw I am using iPhone 3G with iOS 4.1 ... can that be the reason for the FPS drop or just the iPhone becomes idle after some time?
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"hen.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"hen.png"];
[self addChild:spriteSheet];
// Load up the frames of our animation
NSMutableArray *walkAnimFrames = [NSMutableArray array];
for(int i = 1; i <= 3; ++i) {
[walkAnimFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"%d.png", i]]];
}
CCAnimation *walkAnim = [CCAnimation animationWithFrames:walkAnimFrames delay:0.1f];
self.bear = [CCSprite spriteWithSpriteFrameName:#"1.png"];
_bear.position = ccp(20,400);
self.walkAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:walkAnim restoreOriginalFrame:NO]];
[_bear runAction:_walkAction];
[spriteSheet addChild:_bear];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"monkey.plist"];
CCSpriteBatchNode *spriteSheetMonkey = [CCSpriteBatchNode batchNodeWithFile:#"monkey.png"];
[self addChild:spriteSheetMonkey];
NSMutableArray *walkAnimFramesMonkey = [NSMutableArray array];
for(int i = 1; i <= 3; ++i) {
[walkAnimFramesMonkey addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"%d.png", i]]];
}
CCAnimation *walkAnimMonkey = [CCAnimation animationWithFrames:walkAnimFramesMonkey delay:0.1f];
self.monkey = [CCSprite spriteWithSpriteFrameName:#"1.png"];
_monkey.position = ccp(40,80);
self.walkMonkey = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:walkAnimMonkey restoreOriginalFrame:NO]];
[_monkey runAction:_walkMonkey];
[spriteSheetMonkey addChild:_monkey];
float bearVelocity = 480.0/3.0;
CGPoint moveDifferenceHen = ccpSub(HenLoction, _bear.position);
float distanceToMoveHen = ccpLength(moveDifferenceHen);
float moveDurationHen = distanceToMoveHen / bearVelocity;
self.moveAction = [CCSequence actions:
[CCMoveTo actionWithDuration:moveDurationHen position:HenLoction],
[CCCallFunc actionWithTarget:self selector:#selector(bearMoveEnded)],
nil
];
[_bear runAction:_moveAction];
Sounds like you might have a memory leak. My advice would be to run the Leaks and ObjectAlloc Instruments on the device. Also, you should post the relevant code if you want more detailed assistance.
A memory leak seems most likely.
If that's not it then I'd look at things like data structures you're traversing, any kind of decision-tree-like elements you're building over time, that kind of thing. Anything where there isn't necessarily a memory leak but something might be legitimately getting big or complex, requiring increasing processing time.
If the app has to work through this several-to-many times a second, everything would slow down. It could be the kind of thing where it's getting twice as complicated or large ever few minutes, and the early doublings are still fast and easy to calculate. When it doubles from, say, 256 times as complex as the original to 512 times, the slowdown starts to become apparent.

What is the best way to start Cocos2d Animation of Multiple Sprites?

I have an array randomAlphabets which contains CCSprite objects. I need to start animation on these objects. The randomAlphabets array (NSMutable) can contain max of 4 elements. I am running a loop and then starting the animation. Is this the correct way?
-(void) startAnimation:(CCSprite *) sprite
{
[self generateRandomCoordinates];
id actionMove = [CCMoveTo actionWithDuration:3.0 position:ccp(x,y)];
id actionRotate = [CCRotateBy actionWithDuration:0.0 angle:rotateBy];
id actionMoveDone = [CCCallFuncN actionWithTarget:self selector:#selector(finishedMoving:)];
[sprite runAction:[CCSequence actions:actionMove,actionRotate, actionMoveDone, nil]];
}
-(void) addAlphabetsOnScreen
{
for (int i=0; i<=randomAlphabets.count -1; i++) {
CCSprite *sprite = [randomAlphabets objectAtIndex:i];
[self generateRandomCoordinates];
sprite.position = ccp(x,y);
[self addChild:sprite];
[self startAnimation:sprite];
}
}
Sure, why not?
If have performance issues or sprites not starting their anims simultaneously, you might want to "prepare" the sequences for each sprite in one step (maybe after loading the level) and then just kick them all of in another step. 4 Sprites starting at the same time seems not too tough though.