in my game i want to make several themes. (like doodle jump)
the frame names are the same in different themes and i think i just need to change the plist files.
in the Helper.m:
+(void) addCache {
ThemeTypes themeType = [Helper getCurrentThemeType];
if (themeType == ThemeTypeDefaultTheme) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"block.plist"];
}
else if (themeType == ThemeTypeJungleTheme) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"jungle1.plist"];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"jungle2.plist"];
}
else if (themeType == ThemeTypeXmasTheme) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"xmas1.plist"];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"xmas2.plist"];
}
else if (themeType == ThemeTypeWhiteTheme) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"blockwhite.plist"];
}
else if (themeType == ThemeTypeOuterSpaceTheme) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"outer1.plist"];
}
else if (themeType == ThemeTypeSkyTheme) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"sky1.plist"];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"sky2.plist"];
}
else {
}
}
+(void) purgeCache {
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFrames];
[CCSpriteFrameCache purgeSharedSpriteFrameCache];
}
+(void) refreshCache {
[Helper purgeCache];
[Helper addCache];
}
in appdelegate, i will call 'addcache', then in my ShopScene, when choosing a theme, 'refresh cache' will get called.
this code is in my BlockCache ('normal block' is a kind of block in game')
CCSpriteFrame* frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:#"normal.png"];
batch = [CCSpriteBatchNode batchNodeWithTexture:frame.texture];
[self addChild:batch];
(note that #"normal.png" exist in every plist files. use the same name.
but after changing the theme, the 'block cache' still use the original images. however, the background etc uses the correct one (I use bg = [CCSprite spriteWithSpriteFrameName: ] method
UPDATE: after choosing the theme, i kill the game, and then relaunch it, it works fine. which means the call from Appdelegate is fine, but i can't change the cache during run time.
Sprite frames reference textures. The textures are cached in CCTextureCache. You probably also need to remove the textures from the CCTextureCache.
And if you still have sprites on screen which are making use of texture A but you want them to make use of texture B, you have these options:
send the setTexture: message with the new texture to each sprite
remove the old sprites and add new ones
reload the current scene
Note that calling [CCSpriteFrameCache purgeSharedSpriteFrameCache]; is superfluous in any case. You don't need to deallocate the singleton, just purge whatever it is caching.
You can find a working solution for texture unloading and replacing the texture a sprite uses in the code for my Cocos2D Webcam Viewer tutorial.
Related
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'm using CCScrollLayer. and I'm trying to prepare a level's texture at the stage select screen before level is started.
So, I made this screen as when I change level, then before level's textured that was prepared is supposed to be removed. But I don't think "removeSpriteFramesFromFile" method work well. because when I scroll the several layer, It suddenly call "Memory warning" and remove that textures at that late time then I expected.
-(void) prepareTexture:(NSNumber*)number
{
int _page = [number intValue];
if(loadingTexNum != 0 && (_page + 1) != loadingTexNum)
{
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFramesFromFile:[NSString stringWithFormat:#"L%d.plist", loadingTexNum]];
loadingTexNum = _page + 1;
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:[NSString stringWithFormat:#"L%d.plist", loadingTexNum]];
}
if(loadingTexNum == 0 && (_page + 1) != loadingTexNum)
{
loadingTexNum = _page + 1;
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:[NSString stringWithFormat:#"L%d.plist", loadingTexNum]];
}
}
Thanks
I believe you misunderstood what that method does:
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFramesFromFile:#"file.plist"];
It loads the sprite frames in the plist, then removes the CCSpriteFrame objects in that plist from the cache - if they are cached. It does NOT remove the texture, only the CCSpriteFrame objects!
To remove a texture from the cache you need to call:
[[CCTextureCache sharedTextureCache] removeTexture:tex];
You should know that a CCSpriteFrame object is a relatively light-weight object that consumes at most 64 Bytes of memory. That is nothing compared to even a very small 32x32 texture with 16-bit color depth which uses 2048 Bytes of memory.
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).
everywhere i look i find code on how to run an animation using file created from zwoptex, but it is using deprecated code. I can't find code on how to run the animation using CCSpriteBatchNode.
Any suggestions?
This is the way I do animations using files created from zwoptex, but I am using CCSpriteFrameCache. I don't get any warnings, so I suppose this code is not deprecated:
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"frameSheet.plist"];
NSMutableArray* frames = [NSMutableArray array];
for(int i = 1; i <= numberOfFrames; ++i) {
[frames addObject:
[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"frame-%03d.png", i]]];
}
CCAnimation* anim = [CCAnimation animationWithFrames:frames delay:0.3f];
Texture file is named frameSheet.png, plist file is frameSheet.plist, and the composing images are named frame-001.png ... frame-100.png.
Let me know if this helps you.
EDIT:
strangely, for me animationWithFrames:frames is not deprecated:
,
it says "from 0.99.5" and I have no issues with my animations...