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).
Related
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.
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.
I am creating an app which has around 15 different animations. Some animations are played on button taps while some are played on certain event. As I am new to cocos2d I just cannot figure out how to initialize these animations. I have separate sprite sheets for each animation. What I have done is created a convenience class that has as a data member, a CCSprite object. And in the init function I have placed all the code to create animations. But as soon as the init function is called I get memory warning.
I even tried to initialize the animations only when the request for playing the animation is sent, but I still get the memory warning. Also if I do so the animation doesn't play until the whole sprite sheet is loaded.
Please check the following code for reference
- (void)initEntryAnimation
{
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"entry.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"entry.png"];
[self.sardarSprite addChild:spriteSheet];
NSMutableArray *entryFrames = [NSMutableArray array];
for(int i = 0; i < 77; i++) {
if([[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"enter in scene%04i.png", i+1]])
[entryFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"enter in scene%04i.png", i+1]]];
}
self.entryAnimation = [CCAnimation animationWithFrames:entryFrames delay:0.05f];
}
- (void)playEntryAnimation
{
[CCSpriteFrameCache purgeSharedSpriteFrameCache];
[self.sardarSprite runAction:[CCAnimate actionWithAnimation:self.entryAnimation restoreOriginalFrame:NO]];
}
I have created similar functions for all the 15 animations. I call all the initialization functions one after another. And the play functions are called whenever a request for the animation is sent.
I desperately need some expert advise. Any help would be highly appreciated.
The problem is the size of your sprite sheets. The sprite sheet file size (the size of the png) may be very small, but that is the size on disk. Once your app has loaded that into memory, it is stored uncompressed so that's 2048x2048x8 per sprite sheet.
You can reduce the colour depth to RGBA4444, for example which may help. See TexturePacker for more details. There are other tools available (I am not affiliated with TexturePacker in any way).
Other options you have are to reduce the colour depth further or to use one of the other formats available. You could also look at your animations and see if there is any way to break them down into more discrete components. For example, if you have an animation of a man running, perhaps instead of having the full screen frames, you could have one frame of the torso and then different leg animations and arm animations which you then use.
Without knowing the exact animation, it's difficult to advise.
Try adding NSAutoreleasePool like this
- (void)initEntryAnimation
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"entry.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"entry.png"];
[self.sardarSprite addChild:spriteSheet];
NSMutableArray *entryFrames = [NSMutableArray array];
for(int i = 0; i < 77; i++) {
if([[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"enter in scene%04i.png", i+1]])
[entryFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"enter in scene%04i.png", i+1]]];
}
self.entryAnimation = [CCAnimation animationWithFrames:entryFrames delay:0.05f];
[pool release];
}
I am making a game in Cocos2d. Everything is okay, so far. I used Ray Wenderlich's tutorial to get collision detection to work. It works, but whenever an 'enemy' spawns where a bullet was deleted (because the bullet that was deleted hit a target, therefore, was deleted), the enemy is automatically deleted, too. I think it's because it doesn't remove the rect that was declared for the sprite. Note, it also can go through more than one enemy, even though the bullet is deleted. Any help is appreciated. Thanks!
EDIT:
I found out what the problem was. I had the shoot method set in a schedule:#selector method, with no set interval. That meant that it would fire bullets 60fps fast. So I was getting TWO bullets with ONE click. They were so close together, that it took me a while to notice it. I won't make that mistake again!!!
Are you using the following code? (from How To Make A Simple iPhone Game with Cocos2D Tutorial)
- (void)update:(ccTime)dt {
NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
for (CCSprite *projectile in _projectiles) {
CGRect projectileRect = CGRectMake(
projectile.position.x - (projectile.contentSize.width/2),
projectile.position.y - (projectile.contentSize.height/2),
projectile.contentSize.width,
projectile.contentSize.height);
NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
for (CCSprite *target in _targets) {
CGRect targetRect = CGRectMake(
target.position.x - (target.contentSize.width/2),
target.position.y - (target.contentSize.height/2),
target.contentSize.width,
target.contentSize.height);
if (CGRectIntersectsRect(projectileRect, targetRect)) {
[targetsToDelete addObject:target];
}
}
for (CCSprite *target in targetsToDelete) {
[_targets removeObject:target];
[self removeChild:target cleanup:YES];
}
if (targetsToDelete.count > 0) {
[projectilesToDelete addObject:projectile];
}
[targetsToDelete release];
}
for (CCSprite *projectile in projectilesToDelete) {
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
}
whenever an 'enemy' spawns where a bullet was deleted, the enemy is automatically deleted, too.
It sounds that the bullet is removed from the layer, but it is not removed from _projectiles array.
[_projectiles removeObject:projectile];
Are you sure that this code works?
Rect is not a seperate entity from your bullet. Rect is the property associated with the bullet. As soon as your "bullet is deleted" the rect will no longer be valid.
What you should be looking at is your collision checking code.
You probably want to surround your bullet collision check code with a condition like:
if(bullet exists)
{
check for collision
}
Since you haven't posted code I could only post pseudo code here. Maybe if you post your collision checking code I could show you in more detail.
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.