So the issue here is when I create one animated projectile, everything is fine. As soon as the user creates a second, the first stops animating.
Alright, here's how I'm setting it up in my init:
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:
#"acorns.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode
batchNodeWithFile:#"acorns.png"];
[self addChild:spriteSheet];
NSMutableArray *flyingFrames = [NSMutableArray array];
for(int i = 1; i <= 4; ++i) {
[flyingFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"acorn%d.png", i]]];
}
CCAnimation *flying = [CCAnimation
animationWithFrames:flyingFrames delay:0.5f];
self.flying = [CCRepeatForever actionWithAction:
[CCAnimate actionWithAnimation:flying restoreOriginalFrame:NO]];
Then, in my create bullets method which is called when the user taps the screen, I do:
CCSprite *bullet = [CCSprite spriteWithSpriteFrameName:#"acorn1.png"];
bullet.position = CGPointMake(140.0f, FLOOR_HEIGHT+145.0f);
[bullet runAction:_flying];
[self addChild:bullet z:9];
[bullets addObject:bullet];
So the first time the user taps, everything works fine. The second time, an animated bullet is created, but the existing one stops animating, and so on.
I believe each sprite should have its own actions, ie you cant reuse an action while the action is in progress. Something like:
CCSprite *bullet = [CCSprite spriteWithSpriteFrameName:#"acorn1.png"];
bullet.position = CGPointMake(140.0f, FLOOR_HEIGHT+145.0f);
id _fly=[CCAnimation animationWithFrames:flyingFrames delay:0.5f];
id _flyForever = [[CCRepeatForever _fly];
[bullet runAction:_flyForever];
[self addChild:bullet z:9];
[bullets addObject:buller];
The flyingFrames can be referenced by multiple animations, so you must take care of retaining the array for reuse.
Related
I wrote some animation code in OS X 10.6, but the animations aren't working properly when I recompile the app for 10.9.
I've got an array of buttons, for each of them my goal is to have it shrink, then return to normal size, then shrink, and then return to normal size (to "flex" a couple of times).
NSMutableArray *animations = [NSMutableArray arrayWithCapacity:buttons.count];
NSMutableArray *allAnimations = [NSMutableArray arrayWithCapacity:buttons.count*4];
for(NSButton *button in buttons) {
// store the original frame and generate the smaller frame
NSRect originalFrame = button.frame;
NSRect smallFrame = [self smallRectForRect:originalFrame scale:0.9];
// build dictionaries which describe the animations from normal-to-small and vice versa
NSMutableDictionary *normalToSmall = [NSMutableDictionary dictionary];
[normalToSmall setObject:button forKey:NSViewAnimationTargetKey];
[normalToSmall setObject:[NSValue valueWithRect:originalFrame] forKey:NSViewAnimationStartFrameKey];
[normalToSmall setObject:[NSValue valueWithRect:smallFrame] forKey:NSViewAnimationEndFrameKey];
NSMutableDictionary *smallToNormal = [NSMutableDictionary dictionary];
[smallToNormal setObject:button forKey:NSViewAnimationTargetKey];
[smallToNormal setObject:[NSValue valueWithRect:smallFrame] forKey:NSViewAnimationStartFrameKey];
[smallToNormal setObject:[NSValue valueWithRect:originalFrame] forKey:NSViewAnimationEndFrameKey];
// create, and chain together, the shrink, reset, shrink, reset animation chain
NSViewAnimation *animation1 = [self createAnimationWithDictionary:normalToSmall duration:duration startingAfterAnimation:nil];
NSViewAnimation *animation2 = [self createAnimationWithDictionary:smallToNormal duration:duration startingAfterAnimation:animation1];
NSViewAnimation *animation3 = [self createAnimationWithDictionary:normalToSmall duration:duration startingAfterAnimation:animation2];
NSViewAnimation *animation4 = [self createAnimationWithDictionary:smallToNormal duration:duration startingAfterAnimation:animation3];
// store all of the animations in an array just in case ARC wants to release them prematurely.
[allAnimations addObjectsFromArray:#[animation1, animation2, animation3, animation4]];
// Store the first animation, so that we can start each of the chains after we're done creating them.
[animations addObject:animation1];
}
// start the first animation in each chain
for (NSViewAnimation *animation in animations) {
[animation startAnimation];
}
Here is my function used to create each of the animations:
- (NSViewAnimation*) createAnimationWithDictionary:(NSDictionary*)animationDictionary duration:(float)duration startingAfterAnimation:(NSViewAnimation*)previousAnimation {
NSViewAnimation *animation = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObject:animationDictionary]];
[animation setDuration:duration];
[animation setDelegate:self];
if (previousAnimation != nil) {
[animation startWhenAnimation:previousAnimation reachesProgress:1.0];
}
return animation;
}
I set my window controller as the delegate so I could watch the animations start and end and it appears that only the first animation in each chain starts and ends. The subsequent animations which were configured to -startWhenAnimation:reachesProgress:1.0 do not appear to start at all.
Why won't the subsequent animations start running?
EDIT:
I added the following code in both the dealloc and cleanup method of the previous scene (the one from which I call replaceScene but it does not have any effect. Even after 1/2/5 seconds from when InstructionScene has being created the memory does still contain the assets from the previous scene. The only way to force the removal of those is to remove them from the new scene 0.1f seconds after the scene is being created (via a Callback). This is kind of weird.
Here is the code of the previous scene cleanup and daelloc methods:
-(void) cleanup
{
CCLOG(#"");
CCLOG(#"");
CCLOG(#"PlanetSelection Menu cleanup");
CCLOG(#"");
[super cleanup];
[planetLayer removeAllChildrenWithCleanup:YES];
[self removeAllChildrenWithCleanup: YES];
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFramesFromFile:textureFileName];
[[CCTextureCache sharedTextureCache] removeUnusedTextures];
[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];
[CCAnimationCache purgeSharedAnimationCache];
}
-(void) dealloc
{
CCLOG(#"Dealloc gets caled");
[CCAnimationCache purgeSharedAnimationCache];
[[CCDirector sharedDirector] purgeCachedData];
[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFramesFromFile:textureFileName];
}
Original question:
I got several scenes in my game and so far I used the following bit of code at the beginning of each scene to remove previously stored textures. However there is a case in which this doesn't work: when I replace a scene (lets call it A) with a new scene (lets call it B) with no sprites and only some label created from font image sheet.
[[CCDirector sharedDirector] replaceScene: [InstructionsScene sceneWithLevelName:FIRST_LEVEL]];
The new object does get created too fast as the following call doesn't seem to have any effect:
-(id) initWithLevelName:(LevelName)name
{
if ((self = [super init]))
{
//Remove stuff from previous scene
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFrames];
[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames]; //Not really necessary
//Use these
[[CCTextureCache sharedTextureCache] removeUnusedTextures]; //Not really needed
[[CCTextureCache sharedTextureCache] removeAllTextures];
[[CCDirector sharedDirector] purgeCachedData];
[CCAnimationCache purgeSharedAnimationCache];
....
}
}
At the moment in which the replace scene method is being called the two CCLayer (CCScane) objects are alive at the same time. However texture from the previous scene do not get removed. The same code works perfectly if a sprite sheet is being added and used in the Instruction scene. A tweak to this is to use a callback to a selector removing all textures after 0.1f, however this is not very elegant and smooth:
[self runAction:[CCSequence actionOne:[CCDelayTime actionWithDuration:0.1f] two:[CCCallFunc actionWithTarget:self selector:#selector(removeStuffFromPreviousScene)]]];
Is this a known issue? It could cause potential crashes.
I paste here the code to make sure you can try it out:
//
// InstructionsScene.h
//
// Created by mm24 on 09/09/13.
// Copyright 2013 mm24. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "CommonEnumerations.h"
#interface InstructionsScene : CCLayer {
LevelName levelName;
float startTime;
CCLabelBMFont * levelNameTitle;
CCLabelBMFont * levelSubtitle;
CCLabelBMFont * instructionsHeader;
CCLabelBMFont * instructions;
}
+(id)sceneWithLevelName:(LevelName)name;
#end
//
// InstructionsScene.m
//
// Created by mm24 on 09/09/13.
// Copyright 2013 mm24. All rights reserved.
//
#import "InstructionsScene.h"
#import "ShooterScene.h"
#import "AppDelegate.h"
#import "mach/mach.h"
#implementation InstructionsScene
+(id)sceneWithLevelName:(LevelName)name
{
CCScene * scene = [CCScene node];
InstructionsScene * layer = [[self alloc] initWithLevelName:name];
[scene addChild:layer];
return scene;
}
-(id) initWithLevelName:(LevelName)name
{
if ((self = [super init]))
{
//Remove stuff from previous scene
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFrames];
[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];
//Use these
[[CCTextureCache sharedTextureCache] removeUnusedTextures];
[[CCTextureCache sharedTextureCache] removeAllTextures];
[[CCDirector sharedDirector] purgeCachedData];
[CCAnimationCache purgeSharedAnimationCache];
//Try out and use it. Not compulsory
[self removeAllChildrenWithCleanup: YES];
CCLOG(#"init with level name");
levelName = name;
startTime = 10.0f;
levelNameTitle = [CCLabelBMFont labelWithString:#"Title" fntFile:#"bitmapFontTest.fnt"];
levelNameTitle.position = CGPointMake(160.0f, 420.0f);
levelNameTitle.anchorPoint = CGPointMake(0.5f, 0.5f);
levelNameTitle.scale = 1.3f;
[self addChild:levelNameTitle z:1] ;
levelSubtitle = [CCLabelBMFont labelWithString:#"Subtitle" fntFile:#"bitmapFontTest.fnt"];
levelSubtitle.position = CGPointMake(160.0f, 400.0f);
levelSubtitle.anchorPoint = CGPointMake(0.5f, 0.5f);
levelSubtitle.scale = 0.7f;
[self addChild:levelSubtitle z:1] ;
instructionsHeader = [CCLabelBMFont labelWithString:#" Instructions " fntFile:#"bitmapFontTest.fnt"];
instructionsHeader.position = CGPointMake(160.0f, 240.0f);
instructionsHeader.anchorPoint = CGPointMake(0.5f, 0.5f);
instructionsHeader.scale = 0.7f;
[self addChild:instructionsHeader z:1] ;
instructions = [CCLabelBMFont labelWithString:#"Press any key" fntFile:#"bitmapFontTest.fnt"];
instructions.position = CGPointMake(160.0f, 200.0f);
instructions.anchorPoint = CGPointMake(0.5f, 0.5f);
instructions.scale = 0.7f;
[self addChild:instructions z:1] ;
[[CCDirector sharedDirector] resume];
// [self runAction:[CCSequence actionOne:[CCDelayTime actionWithDuration:0.1f] two:[CCCallFunc actionWithTarget:self selector:#selector(removeStuffFromPreviousScene)]]];
[self runAction:[CCSequence actionOne:[CCDelayTime actionWithDuration:1.0f] two:[CCCallFunc actionWithTarget:self selector:#selector(report_memory)]]];
[self runAction:[CCSequence actionOne:[CCDelayTime actionWithDuration:2.0f] two:[CCCallFunc actionWithTarget:self selector:#selector(report_memory)]]];
[self runAction:[CCSequence actionOne:[CCDelayTime actionWithDuration:5.0f] two:[CCCallFunc actionWithTarget:self selector:#selector(report_memory)]]];
[self callBackReplace];
}
return self;
}
-(void) removeStuffFromPreviousScene
{
//Use these
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFrames];
[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];
//Use these
[[CCTextureCache sharedTextureCache] removeUnusedTextures];
[[CCTextureCache sharedTextureCache] removeAllTextures];
[[CCDirector sharedDirector] purgeCachedData];
[CCAnimationCache purgeSharedAnimationCache];
}
-(void) report_memory {
CCLOG(#"");
CCLOG(#"");
CCLOG(#"InstructionScene info:");
CCLOG(#"");
[[CCTextureCache sharedTextureCache] dumpCachedTextureInfo];
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(),
TASK_BASIC_INFO,
(task_info_t)&info,
&size);
if( kerr == KERN_SUCCESS ) {
NSLog(#"Memory in use (in bytes): %u", info.resident_size);
} else {
NSLog(#"Error with task_info(): %s", mach_error_string(kerr));
}
}
-(void) nextSuggestionPressed
{
[self stopAllActions];
[self callBackReplace];
}
-(void) callBackReplace
{
[self runAction:[CCSequence actions:
[CCDelayTime actionWithDuration:startTime] ,
[CCCallFunc actionWithTarget:self selector:#selector(replaceWithShooterScene)],
nil]
];
}
-(void) replaceWithShooterScene
{
[[CCDirector sharedDirector] replaceScene:[ShooterScene sceneWithLevelName:levelName]];
}
#end
Since you initialize the new scene within an already running scene, both scenes are alive at the same time. If you try to remove "unused" resources during init of the new scene, nothing (or not much) will happen since the existing scene is still using these assets.
You can only do two things:
a loading scene in between the two scenes to allow the first scene to deallocate before the next scene is initialized
unloading all unused assets during dealloc of a scene, not when initializing a new one
The loading scene is the best approach if both scenes use a lot of unique assets, so having both in memory at the same time may cause memory warnings or even the app forced to terminate due to memory pressure.
The other approach is best used in all other cases. Just be careful to unload specific resources rather than using the "unused" methods because another scene may currently be using this resource and will be forced to reload it if the other scene removes it.
PS: as long as your app isn't under memory pressure even on devices with the least amount of memory you shouldn't remove resources simply to prevent them from being reloaded again and again. Switching scenes will be a lot faster with already cached textures.
The delayperunit property in my code below keeps the sprite on screen for 2.5 seconds before it changes. What I actually want to do is display the sprite for 0.5 seconds so that there is 2.0 second break (no animation displayed) before the next one appears for another 0.5 seconds and so on. How can I achieve this?
// Create the intro image
CGSize screenSize = [CCDirector sharedDirector].winSize;
CCSprite *introImage = [CCSprite spriteWithFile:#"intro1.png"];
[introImage setPosition:ccp(screenSize.width/2, screenSize.height/2)];
[self addChild:introImage];
// Create the intro animation, and load it from intro1 to intro7.png
CCAnimation *introAnimation = [CCAnimation animation];
[introAnimation delayPerUnit:2.5f];
for (int frameNumber=0; frameNumber < 8; frameNumber++) {
CCLOG(#"Adding image intro%d.png to the introAnimation.",frameNumber);
[introAnimation addSpriteFrameWithFilename:
[NSString stringWithFormat:#"intro%d.png",frameNumber]];
}
I dont think you can do that with a CCAnimation. You could either embed that in a class (if this will be a recurring theme), or do this in your module that displays these frames:
in .h, some ivars;
NSUInteger _currentFrame;
NSUInteger _maxFrames;
in .m, where you are ready to begin:
_currentFrame=1;
_maxFrames = 8;
[self scheduleOnce:#selector(flashFrame) delay:0.];
and
-(void) flashFrame: {
NSString *spriteName = [NSString stringWithFormat:#"intro%d.png",_currentFrame];
CCSprite *sprite = [CCSprite spriteWithFile:spriteName];
CGSize screenSize = [CCDirector sharedDirector].winSize;
sprite.position=ccp(screenSize.width/2, screenSize.height/2);
id wait = [CCDelayTime actionWithDuration:.5];
id clean = [CCCallBlock actionWithBlock:^{
[sprite removeFromParentAndCleanup:YES];
}];
id seq = [CCSequence actions:wait,clean,nil];
[self addChild sprite];
[sprite runAction:seq];
if(_currentFrame < maxFrame) {
_currentFrame++;
[self scheduleOnce:#selector(flashFrame) delay:2.0];
} else {
// do whatever you wanted to do after this sequence.
}
}
standard disclaimer : not tested, coded from memory, mileage will vary :)
Consider having an action composed by a CCSequence of CCAnimation (the one with 0.2 delay per unit) and add a CCDelayTime of 0.5 secs in between the animations. Then Repeat the action forever. Also, use CCAnimationCache to store your animations if you are using Cocos2d 2.x onwards.
-(void)createSprite{
CCSprite *shotV = [CCSprite spriteWithFile:#"green.png"];
[self addChild:shotV z:1];
[shotVArray addObject:shotV];
NSlog(#"%#",shotV);
}
-(void)aSpecialCase{
[self removeChild:[shotVArray lastObject] cleanup:YES];
}
I'm not getting this to work.
The function "createSprite" spams out sprites. "In aSpecialCase" I want to remove the last sprite that was created. Also hoping that removing it will end the current CCSequence for that instance.
-(void)aSpecialCase{
[self removeChild:[shotVArray lastObject] cleanup:YES];
}
This only removes the sprite from layer.. Doesn't remove it from array itself...
So better way is..
-(void)aSpecialCase{
CCSprite *sprite = [pshotVArray lastObject];
[self removeChild:sprite cleanup:YES];
[pshotVArray removeObject:sprite];
}
Hope this helps.. :)
I am trying to get a sprite to animate itself forever. There are no problems, and it builds fine. I get past the menus and when I click on the scene that has my sprite that I want to animate on it, it crashes. I am using the following code for my animation:
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"sprite_fly.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"sprite_fly.png"];
[self addChild:spriteSheet];
NSMutableArray *flapAnimFrames = [NSMutableArray array];
for(int i = 1; i<=6; ++i) {
[flapAnimFrames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:
[NSString stringWithFormat:#"Fly%d.png"]]];
}
CCAnimation *flapAnim = [CCAnimation animationWithFrames:flapAnimFrames delay:1];
CGSize winSize = [CCDirector sharedDirector].winSize;
fly = [CCSprite spriteWithSpriteFrameName:#"fly1.png"];
fly.position = ccp(winSize.width/2, winSize.height/2);
flapAction = [CCRepeatForever actionWithAction:
[CCAnimate actionWithAnimation:flapAnim restoreOriginalFrame:NO]];
[fly runAction:flapAction];
[spriteSheet addChild:fly];
I think that the problem is to do with the first line of code, CCSpriteFrameCache, but I can't see anything wrong with it. Please help, or give me another way to animate my sprite.
I think this is the best example for animation in cocos2d.click here.
I am not sure but i think the problem is with your sprite images or image name. check your image name in the plist file. Make sure that all images have same size which you are using for your animation.
Hope that will help you.