Cocos-2d 关于多个CCSprite精灵播放同一个CCAction动画问题

问题描述:

在Cocos-2d场景动画中,常常出现多个Sprite的同一行为动画

假设场景中此时有两个精灵sprite1,sprite2

他们其实点分别在场景左侧,需要完成的动作CCMoteTo到场景最右侧

初始状态如下图:

初始尝试:

  1. - (void)playAction 
  2.     //1.试图两个精灵播放同一个动画 
  3.     CGSize size = [[CCDirector sharedDirector] winSize]; 
  4.  
  5.     CCMoveBy *move = [CCMoveBy actionWithDuration:3.0f position:ccp(size.width-[sprite1 textureRect].size.width,0)]; 
  6.     [sprite1 runAction:move]; 
  7.     [sprite2 runAction:move]; 
- (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没有执行。效果不尽人意!

原理解释:

我们跟踪一下

[sprite1runAction:move];

  1. -(CCAction*) runAction:(CCAction*) action 
  2.     NSAssert( action != nil, @"Argument must be non-nil"); 
  3.  
  4.     [actionManager_ addAction:action target:self paused:!isRunning_]; 
  5.     return action; 
-(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 = [targetretain];

HASH_ADD_INT(targets,target, element);

// CCLOG(@"cocos2d: ---- buckets: %d/%d - %@", targets->entries, targets->size, element->target);


}


//为哈希表项element申请内存,以存放动画集

[selfactionAllocWithHashElement: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 类型)的成员,表示该动画是有哪一个演员来执行的。

所以

  1. [sprite1 runAction:move]; 
  2. [sprite2 runAction:move]; 
[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 特此做出感谢!

解决方案:

  1. - (void)playAction 
  2.     //1.试图两个精灵播放同一个动画 
  3.     CGSize size = [[CCDirector sharedDirector] winSize]; 
  4.  
  5.     CCMoveBy *move = [CCMoveBy actionWithDuration:3.0f position:ccp(size.width-[sprite1 textureRect].size.width,0)]; 
  6.     [sprite1 runAction:move]; 
  7.     //[sprite2 runAction:move]; 
  8.      
  9.     //2.改进 
  10.     [sprite2 runAction:[move copy]]; 
- (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]时

  1. -(void) stopAction: (CCAction*) action 
  2.     [actionManager_ removeAction: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.无论上述哪种方式,一定注意不要内存泄露!

源码分享:

  1. HelloWorldLayer.h 
HelloWorldLayer.h

  1. #import <GameKit/GameKit.h> 
  2.  
  3. // When you import this file, you import all the cocos2d classes 
  4. #import "cocos2d.h" 
  5.  
  6. // HelloWorldLayer 
  7. @interface HelloWorldLayer : CCLayer  
  8.     BOOL isPlay_; 
  9.     CCSprite *sprite1; 
  10.     CCSprite *sprite2; 
  11.  
  12. // returns a CCScene that contains the HelloWorldLayer as the only child 
  13. +(CCScene *) scene; 
  14.  
  15. @end 
#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 这里只贴出有用部分

  1. -(id) init 
  2.     // always call "super" init 
  3.     // Apple recommends to re-assign "self" with the "super's" return value 
  4.     if( (self=[super init]) )  
  5.     {        
  6.         // create and initialize a Label 
  7.         CCLabelTTF *label = [CCLabelTTF labelWithString:@"TwoActionDemo" fontName:@"Marker Felt" fontSize:32]; 
  8.         CGSize size = [[CCDirector sharedDirector] winSize]; 
  9.         label.position =  ccp( size.width /2 , 4*size.height/5 ); 
  10.         [self addChild: label]; 
  11.          
  12.         //实例化两个精灵 
  13.         sprite1 = [CCSprite spriteWithFile:@"Icon.png"]; 
  14.         sprite2 = [CCSprite spriteWithFile:@"Icon.png"]; 
  15.         sprite1.position = ccp([sprite1 textureRect].size.width/2 , size.height/2); 
  16.         sprite2.position = ccp([sprite1 textureRect].size.width/2 , size.height/4); 
  17.         [self addChild:sprite1]; 
  18.         [self addChild:sprite2]; 
  19.          
  20.         //菜单按钮 
  21.         CCMenuItem *play = [CCMenuItemFont itemWithString:@"Play"]; 
  22.         CCMenuItem *stop = [CCMenuItemFont itemWithString:@"Stop"]; 
  23.         CCMenuItem *menuBtn = [CCMenuItemToggle itemWithTarget:self selector:@selector(btnPressed) items:play,stop, nil]; 
  24.         CCMenu *menu = [CCMenu menuWithItems:menuBtn, nil]; 
  25.         menu.position = ccp(9*size.width /10 , size.height/10); 
  26.         [self addChild:menu]; 
  27.          
  28.         isPlay_ = NO; 
  29.  
  30.     } 
  31.     return self; 
-(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;
}

  1. - (void)btnPressed 
  2.     isPlay_ = !isPlay_; 
  3.     YES == isPlay_ ? [self playAction]:[self stopToOrignPostion]; 
  4.  
  5. - (void)playAction 
  6.     //1.试图两个精灵播放同一个动画 
  7.     CGSize size = [[CCDirector sharedDirector] winSize]; 
  8.  
  9.     CCMoveBy *move = [CCMoveBy actionWithDuration:3.0f position:ccp(size.width-[sprite1 textureRect].size.width,0)]; 
  10.     [sprite1 runAction:move]; 
  11.     //[sprite2 runAction:move]; 
  12.      
  13.     //2.改进 
  14.     [sprite2 runAction:[move copy]]; 
  15.  
  16. - (void)stopToOrignPostion 
  17.     [self stopAllActions]; 
  18.      
  19.     CGSize size = [[CCDirector sharedDirector] winSize]; 
  20.      
  21.     CCPlace *place1 = [CCPlace actionWithPosition:ccp([sprite1 textureRect].size.width/2 , size.height/2)]; 
  22.     CCPlace *place2 = [CCPlace actionWithPosition:ccp([sprite1 textureRect].size.width/2 , size.height/4)]; 
  23.     [sprite1 runAction:place1]; 
  24.     [sprite2 runAction:place2]; 
- (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];
}

  1. // on "dealloc" you need to release all your retained objects 
  2. - (void) dealloc 
  3.     [sprite1 release]; 
  4.     <span style="white-space: pre;">    </span>[sprite2 release]; 
  5.     [super dealloc]; 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
抱歉,作为AI语言模型,我无法提供完整的代码,但我可以给您提供一些思路和示例代码。 1. 创建飞机类 ```typescript class Plane extends cc.Node { private speed: number = 10; private bullets: Bullet[] = []; private isShooting: boolean = false; constructor() { super(); const sprite = new cc.Sprite("plane.png"); this.addChild(sprite); this.schedule(this.shootBullet, 0.2, cc.macro.REPEAT_FOREVER); this.scheduleUpdate(); } public update(dt: number) { const keyboard = cc.systemEvent.keyboard; if (keyboard.isKeyDown(cc.macro.KEY.left)) { this.x -= this.speed; } if (keyboard.isKeyDown(cc.macro.KEY.right)) { this.x += this.speed; } if (keyboard.isKeyDown(cc.macro.KEY.up)) { this.y += this.speed; } if (keyboard.isKeyDown(cc.macro.KEY.down)) { this.y -= this.speed; } } private shootBullet() { const bullet = new Bullet(); this.addChild(bullet); this.bullets.push(bullet); bullet.setPosition(this.x, this.y + 20); } public removeBullet(bullet: Bullet) { const index = this.bullets.indexOf(bullet); if (index !== -1) { this.bullets.splice(index, 1); } } } ``` 2. 创建子弹类 ```typescript class Bullet extends cc.Node { private speed: number = 20; constructor() { super(); const sprite = new cc.Sprite("bullet.png"); this.addChild(sprite); this.scheduleUpdate(); } public update(dt: number) { this.y += this.speed; if (this.y > cc.winSize.height) { this.removeFromParent(); } } } ``` 3. 创建敌机类 ```typescript class Enemy extends cc.Node { private speed: number = 5; private hp: number = 3; constructor() { super(); const sprite = new cc.Sprite("enemy.png"); this.addChild(sprite); this.scheduleUpdate(); } public update(dt: number) { this.y -= this.speed; if (this.y < -cc.winSize.height) { this.removeFromParent(); } } public hit() { this.hp--; if (this.hp <= 0) { this.removeFromParent(); } } } ``` 4. 创建游戏场景 ```typescript class GameScene extends cc.Scene { private plane: Plane; private enemies: Enemy[] = []; constructor() { super(); this.init(); } private init() { const bg = new cc.Sprite("bg.png"); bg.setPosition(cc.winSize.width / 2, cc.winSize.height / 2); this.addChild(bg); this.plane = new Plane(); this.plane.setPosition(cc.winSize.width / 2, 100); this.addChild(this.plane); this.schedule(this.addEnemy, 1, cc.macro.REPEAT_FOREVER); this.scheduleUpdate(); } public update(dt: number) { this.checkCollision(); } private addEnemy() { const enemy = new Enemy(); enemy.setPosition(Math.random() * cc.winSize.width, cc.winSize.height); this.addChild(enemy); this.enemies.push(enemy); } private checkCollision() { for (let i = this.enemies.length - 1; i >= 0; i--) { const enemy = this.enemies[i]; const box1 = enemy.getBoundingBox(); for (let j = this.plane.childrenCount - 1; j >= 0; j--) { const bullet = this.plane.children[j] as Bullet; if (bullet) { const box2 = bullet.getBoundingBox(); if (box1.intersects(box2)) { enemy.hit(); this.plane.removeBullet(bullet); } } } if (box1.intersects(this.plane.getBoundingBox())) { cc.director.loadScene(new GameOverScene()); } } } } ``` 以上代码仅供参考,可能存在错误或不足之处,请根据实际情况进行修改和完善。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值