問題描述:
在Cocos-2d場景動畫中,常常出現多個Sprite的同一行爲動畫
假設場景中此時有兩個精靈sprite1,sprite2
他們其實點分別在場景左側,需要完成的動作CCMoteTo到場景最右側
初始狀態如下圖:
初始嘗試:
- (void)playAction
{
//1.試圖兩個精靈播放同一個動畫
CGSize size = [[CCDirector sharedDirector] winSize];
CCMoveBy *move = [CCMoveBy actionWithDuration:3.0f position:ccp(size.width-[sprite1 textureRect].size.width,0)];
[sprite1 runAction:move];
[sprite2 runAction:move];
}
初始效果展示:
點擊Play按鈕後效果圖:
我們發現儘管在代碼中sprite1和sprite2都是runAction:move,但是貌似只有下方的sprite2執行了此動作,而sprite1沒有執行。效果不盡人意!
原理解釋:
我們跟蹤一下
[sprite1 runAction:move];
-(CCAction*) runAction:(CCAction*) action
{
NSAssert( action != nil, @"Argument must be non-nil");
[actionManager_ addAction:action target:self paused:!isRunning_];
return action;
}
重點看一下[addAction:action target:self paused:!isRunning_];
-(void) addAction:(CCAction*)action target:(id)target paused:(BOOL)paused
{
//有效性判斷。
NSAssert( action != nil, @"Argument action must be non-nil");
NSAssert( target != nil, @"Argument target must be non-nil");
//定義一個哈希表項的指針變量並置空。
tHashElement *element = NULL;
//通過這個指針,找到對應的哈希表項返回給element;
HASH_FIND_INT(targets, &target, element);
//如果找不到。則代表新加入的哈希表項。則申請內存創建此哈希表項,並將其加入哈希表中。
if( ! element ) {
element = calloc( sizeof( *element ), 1 );
element->paused = paused;
element->target = [target retain];
HASH_ADD_INT(targets, target, element);
// CCLOG(@"cocos2d: ---- buckets: %d/%d - %@", targets->entries, targets->size, element->target);
}
//爲哈希表項element申請內存,以存放動畫集
[self actionAllocWithHashElement:element];
//判斷action是否在element的動畫集中。確保只放入一次。(這也是爲什麼一個動畫不能重複添加的原因!)
NSAssert( !ccArrayContainsObject(element->actions, action), @"runAction: Action already running");
//將action放入element的動畫集中。
ccArrayAppendObject(element->actions, action);
//設置是哪個CCNode要進行當前動畫 (重點原因在這裏!!!)
[action startWithTarget:target];
}
每一個CCAction對象都有且僅有一個target(id 類型)的成員,表示該動畫是有哪一個演員來執行的。
所以
[sprite1 runAction:move];
[sprite2 runAction:move];
對於同一個move(CCMoveTo對象)來說,第一次[sprite1 runAction:move];我們將move的target成員設置成了sprite1;
而第二次[sprite2 runAction:move];我們又將move的target成員設置成了sprite2;這樣第一次註冊sprite1的動畫move就會失效;
因此效果上只有sprite2在執行move了!
(本段參考“紅孩兒Cocos2d-x 2.0 之 Actions “三板斧” 之一”文章:http://www.2cto.com/kf/201211/169345.html 特此做出感謝!)
解決方案:
- (void)playAction
{
//1.試圖兩個精靈播放同一個動畫
CGSize size = [[CCDirector sharedDirector] winSize];
CCMoveBy *move = [CCMoveBy actionWithDuration:3.0f position:ccp(size.width-[sprite1 textureRect].size.width,0)];
[sprite1 runAction:move];
//[sprite2 runAction:move];
//2.改進
[sprite2 runAction:[move copy]];
}
很簡單:把move 複製一份,move的副本的target爲sprite2,與sprite1無關,這樣兩個精靈就可以同時執行了。
效果圖:
順便提一句:
1.[CCNode stopAction:action]時
-(void) stopAction: (CCAction*) action
{
[actionManager_ removeAction:action];
}
action會被釋放
2.
-(void) removeFromParentAndCleanup:(BOOL)cleanup;
-(void) removeChild: (CCNode*)node cleanup:(BOOL)cleanup;
-(void) removeChildByTag:(NSInteger) tag cleanup:(BOOL)cleanup;
-(void) removeAllChildrenWithCleanup:(BOOL)cleanup;
這是CCNODE的刪除對象的方法,後面帶了一個cleanup參數,如果你將cleanup的值設爲YES,系統在刪除對象的時候會對自動對當前對象進行stopAllActions的操作,也會釋放action。
3.無論上述哪種方式,一定注意不要內存泄露!
源碼分享:
HelloWorldLayer.h
#import <GameKit/GameKit.h>
// When you import this file, you import all the cocos2d classes
#import "cocos2d.h"
// HelloWorldLayer
@interface HelloWorldLayer : CCLayer
{
BOOL isPlay_;
CCSprite *sprite1;
CCSprite *sprite2;
}
// returns a CCScene that contains the HelloWorldLayer as the only child
+(CCScene *) scene;
@end
HelloWorldLayer.m 這裏只貼出有用部分
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super's" return value
if( (self=[super init]) )
{
// create and initialize a Label
CCLabelTTF *label = [CCLabelTTF labelWithString:@"TwoActionDemo" fontName:@"Marker Felt" fontSize:32];
CGSize size = [[CCDirector sharedDirector] winSize];
label.position = ccp( size.width /2 , 4*size.height/5 );
[self addChild: label];
//實例化兩個精靈
sprite1 = [CCSprite spriteWithFile:@"Icon.png"];
sprite2 = [CCSprite spriteWithFile:@"Icon.png"];
sprite1.position = ccp([sprite1 textureRect].size.width/2 , size.height/2);
sprite2.position = ccp([sprite1 textureRect].size.width/2 , size.height/4);
[self addChild:sprite1];
[self addChild:sprite2];
//菜單按鈕
CCMenuItem *play = [CCMenuItemFont itemWithString:@"Play"];
CCMenuItem *stop = [CCMenuItemFont itemWithString:@"Stop"];
CCMenuItem *menuBtn = [CCMenuItemToggle itemWithTarget:self selector:@selector(btnPressed) items:play,stop, nil];
CCMenu *menu = [CCMenu menuWithItems:menuBtn, nil];
menu.position = ccp(9*size.width /10 , size.height/10);
[self addChild:menu];
isPlay_ = NO;
}
return self;
}
- (void)btnPressed
{
isPlay_ = !isPlay_;
YES == isPlay_ ? [self playAction]:[self stopToOrignPostion];
}
- (void)playAction
{
//1.試圖兩個精靈播放同一個動畫
CGSize size = [[CCDirector sharedDirector] winSize];
CCMoveBy *move = [CCMoveBy actionWithDuration:3.0f position:ccp(size.width-[sprite1 textureRect].size.width,0)];
[sprite1 runAction:move];
//[sprite2 runAction:move];
//2.改進
[sprite2 runAction:[move copy]];
}
- (void)stopToOrignPostion
{
[self stopAllActions];
CGSize size = [[CCDirector sharedDirector] winSize];
CCPlace *place1 = [CCPlace actionWithPosition:ccp([sprite1 textureRect].size.width/2 , size.height/2)];
CCPlace *place2 = [CCPlace actionWithPosition:ccp([sprite1 textureRect].size.width/2 , size.height/4)];
[sprite1 runAction:place1];
[sprite2 runAction:place2];
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
[sprite1 release];
[sprite2 release];
[super dealloc];
}