首页 > 代码库 > cocos2d-x实现多个精灵动画同步播放(一)
cocos2d-x实现多个精灵动画同步播放(一)
2D游戏经常有角色穿装备的情况,如下图角色手部加了一个武器.此外还有格斗游戏里常有的投技:
再看start 和 stop函数
setFrame函数:
其他的还有构造和析构函数
构造函数:
再看下重要的开始播放和停止播放函数
可能同学们还不明白了,主动画是CCAnimate, 而子动画将来我们也是CCAnimate并把它放入_members里,那么AnimateMember类的start函数没有调用CCAnimate::start和stop函数,那我们怎么让子动画开始播放呢?不错,这是个问题,我们想让子动画与动画同步播放,就不能再调用CCAnimate的start方法来开始播放动画,因为那样会产生不同步的问题,我们要采用最原始的办法,直接设置帧图片的办法, 通过AnimateGroup的update调用 子动画的 setFrame来实现.如下:
这样我们的动画联动类也就实现了,一遍下来发现原理也不复杂,就是在update里让子动画每帧切换下动画,那我们怎么运用它呢?
由于源工程比较宏大,不可能把所有的代码都贴出,我自己是个菜鸟,经常被所谓的高手们嘲笑,但我相信只要了解了原理,就算是像我这样智商一般的菜鸟也能运用自如:
好了不多废话,组建个动画还挺麻烦的,为了清晰起见写个方法: animateGroupWithActionWord 。
假定我们有个机器人类,继承于CCSprite,它有悠闲,攻击和行走各种动作,由于有它头上冒的烟所以每一个动画都应该是AnimateGroup, 方法如下:
具体运用它就很简单了。
例如机器人的站立动画:
robot->runAction(robot->_idleAction); 即可
可以看出,运行正常
总结:虽然运用它看起来很麻烦,步骤很烦琐,但我一直不相信简单就是美这种肤浅的话,要想实现复杂的功能,光想着简单是没有用的。不过基本原理确实不复杂,其实大量的代码都是基本的生成帧动画。动画组的主要步骤就是先生成主动画和子动画,用主动画来初始化动画组,子动画塞到动画组的_members数组里,然后就可以像正常的CCAnimate这样来播放了。
机器人的例子是完结了,相信读者看后可以运用的自己的工程中,但是别以为大功告成了。因为这个例子不具有代表性,因为机器人头上的烟本身就是以属于机器人类里的,而我们经常遇到的角色穿装备,还有格斗游戏里的投技,子对象就和主对象不是一个类的包含关系,而是两个独立的对象,有自己的位置和朝向,这时就需要考虑位置和朝向的关系了,不然会发生动画是联动了但位置却对的乱七八糟这种情况。这个放在下一节中讲解。
注意角色是处在站立状态下的,有Idle动画,手部武器也要随角色一起联动。我们是不是要让美术再画一套加手部动画的素材,那美术显然不干了,那要有脚呢,披风呢?不要画死了。他们只会给你一套纯武器的站立动画,让你自己去拼。
那我们要想让武器随角色一起联动,自然想到设定好位置和zorder后,调用CCSpawn同时动作的方法。可这有个大问题,就是独立执行两个不同的动画会有很大机率产生不同步的问题。为了解决这一问题,必须实现一种动画组的机制,就是让人物作为动画组的主动画,武器作为动画组的子成员,当主动画帧切换时子动画才切换。也就是我动你才你,要动一起动。
如这个机器人是由头上的烟和身体以及腰上的亮点组成的,攻击时机器人对攻击动画同时烟也有自己的动作,烟要随着机器人的每帧动作切换时它也要同步切换到下一帧,这时机器人作为动画组的主成员,而烟动画需要作为动画组子动画成员。
实现同步动画原理是CCAnimate的update方法是每执行一次就切换一次显示帧来实现动画效果,我们要重写这个update,让主动画update时也让动画组的所有成员也切换关键帧,这样就能实现绝对同步了。
首先实现动画组成员的方法 AnimateMember,继承于CCObject
#ifndef _AnimationMember_ #define _AnimationMember_ #include "cocos2d.h" class AnimationMember : public cocos2d::CCObject { public: AnimationMember(); ~AnimationMember(); static AnimationMember* memberWithAnimation(cocos2d::CCAnimation *animation, cocos2d::CCSprite *target); bool initWithAnimation(cocos2d::CCAnimation *animation, cocos2d::CCSprite *target); //用动画和播放对象来初始化 void start(); //开始播放动画 void stop(); //停止播放动画 void setFrame(int frameIndex); //设置播放动画的对象(_target)图片为动画中的某一帧 protected: cocos2d::CCSpriteFrame* _origFrame; //初始帧 cocos2d::CCAnimation* _animation; //动画 cocos2d::CCSprite *_target; //谁在播放动画 private: }; #endif这是头文件,有初始帧,动画和目标这几个关键方法。看下初始化的实现
AnimationMember* AnimationMember::memberWithAnimation(cocos2d::CCAnimation *animation, cocos2d::CCSprite *target) { AnimationMember* pRet = new AnimationMember(); if (pRet && pRet->initWithAnimation(animation, target)) { return pRet; } else { delete pRet; pRet = NULL; return pRet; } } bool AnimationMember::initWithAnimation(cocos2d::CCAnimation *animation, cocos2d::CCSprite *target) { bool bRet = false; do { //CC_BREAK_IF(!) this->_animation = animation; this->_target = target; this->_animation->retain(); this->_target->retain(); _origFrame = NULL; bRet = true; } while (0); return bRet; }初始化只不过将几个关键信息赋值,是非常简单的。
再看start 和 stop函数
void AnimationMember::start() { _origFrame = _target->displayFrame(); //取得当前显示的帧作为初始帧 } void AnimationMember::stop() { bool bRestore = _animation->getRestoreOriginalFrame(); //播放完成后是否恢复第一帧 if (bRestore) { _target->setDisplayFrame(_origFrame); //恢复第一帧 } }start和stop函数只是设置下初始帧,跟播放没有关系。别急,接着往下看。
setFrame函数:
void AnimationMember::setFrame(int frameIndex) { CCArray* frames = _animation->getFrames(); int nCount = frames->count(); if (frameIndex>=nCount) { CCLog("AnimationMember setFrame frameindex is greater than framecount"); return; } //从动画里取得index帧 CCAnimationFrame *frame = (CCAnimationFrame *)(frames->objectAtIndex(frameIndex)); CCSpriteFrame *spriteFrame = frame->getSpriteFrame(); _target->setDisplayFrame(spriteFrame); }setFrame是从动画中取得想要播放的帧,然后设置成当前显示的帧。此方法在以后会用到.
其他的还有构造和析构函数
AnimationMember::AnimationMember() { _target = NULL; _origFrame = NULL; _animation = NULL; } AnimationMember::~AnimationMember() { CC_SAFE_RELEASE_NULL(_animation); CC_SAFE_RELEASE_NULL(_target); }
再来看动画组AnimateGroup类,动画组是用来播放动画的,所以它要继承于CCAnimate类,因此它具有CCAnimate的一切功能,头文件如下
#ifndef _AnimateGroup_ #define _AnimateGroup_ #include "cocos2d.h" class AnimateGroup : public cocos2d::CCAnimate { public: AnimateGroup(); ~AnimateGroup(); //用数组来初始化函数 static AnimateGroup* actionWithAnimation(cocos2d::CCAnimation *animation, cocos2d::CCArray *members); bool initWithAnimation(cocos2d::CCAnimation *animation, cocos2d::CCArray *members); //用动画和数组来初始化 //用成员数来初始化 static AnimateGroup* actionWithAnimation(cocos2d::CCAnimation *animation, int memberCount); bool initWithAnimation(cocos2d::CCAnimation *animation, int memberCount); //用动画和数组数来初始化 void startWithTarget(cocos2d::CCNode *pTarget); void stop(); //所有动画停止 void update(float dt); cocos2d::CCArray* _members; //动画成员 protected: }; #endif可看到它的重要的成员变量是_members动画成员数组, 此外还有update方法, 看下初始化的实现
构造函数:
AnimateGroup::AnimateGroup() { _members = NULL; } AnimateGroup::~AnimateGroup() { CC_SAFE_RELEASE_NULL(_members); }根据数组初始化函数:
AnimateGroup* AnimateGroup::actionWithAnimation(cocos2d::CCAnimation *animation, cocos2d::CCArray *members) { AnimateGroup* pRet = new AnimateGroup(); if (pRet && pRet->initWithAnimation(animation, members)) { return pRet; } else { delete pRet; pRet = NULL; return pRet; } } bool AnimateGroup::initWithAnimation(cocos2d::CCAnimation *animation, cocos2d::CCArray *members) { bool bRet = false; do { CC_BREAK_IF(!CCAnimate::initWithAnimation(animation)); this->_members = members; this->_members->retain(); bRet = true; } while (0); return bRet; }可看出成员_members是直接传过来的,下面是另一个初始化函数
AnimateGroup* AnimateGroup::actionWithAnimation(cocos2d::CCAnimation *animation,int memberCount) { AnimateGroup* pRet = new AnimateGroup(); if (pRet && pRet->initWithAnimation(animation, memberCount)) { return pRet; } else { delete pRet; pRet = NULL; return pRet; } } bool AnimateGroup::initWithAnimation(cocos2d::CCAnimation *animation, int memberCount) { bool bRet = false; do { CC_BREAK_IF(!CCAnimate::initWithAnimation(animation)); this->_members = CCArray::createWithCapacity(memberCount); this->_members->retain(); bRet = true; } while (0); return bRet; }这里只是创建个容量为指定大小的空数组。
再看下重要的开始播放和停止播放函数
void AnimateGroup::startWithTarget(CCNode *pTarget) { CCAnimate::startWithTarget(pTarget); AnimationMember* aniMember = NULL; CCObject *member = NULL; CCARRAY_FOREACH(this->_members, member) { aniMember = (AnimationMember*)member; aniMember->start(); } } void AnimateGroup::stop() { CCAnimate::stop(); AnimationMember *aniMember = NULL; CCObject* member = NULL; CCARRAY_FOREACH(_members, member) { aniMember = (AnimationMember *)member; aniMember->stop(); } }可以看出开始播放和停止播放都是开始先调用基类的方法,再轮循调用每一个子成员的开始和停止方法, 由于 开始播放和停止播放都是基类来完成,所以子成员要作的工作仅仅是设置下当前显示的帧就行了。
可能同学们还不明白了,主动画是CCAnimate, 而子动画将来我们也是CCAnimate并把它放入_members里,那么AnimateMember类的start函数没有调用CCAnimate::start和stop函数,那我们怎么让子动画开始播放呢?不错,这是个问题,我们想让子动画与动画同步播放,就不能再调用CCAnimate的start方法来开始播放动画,因为那样会产生不同步的问题,我们要采用最原始的办法,直接设置帧图片的办法, 通过AnimateGroup的update调用 子动画的 setFrame来实现.如下:
void AnimateGroup::update(float dt) { CCAnimate::update(dt); int frameIndex = MAX(0, m_nNextFrame - 1); AnimationMember *aniMember = NULL; CCObject* member = NULL; CCARRAY_FOREACH(_members, member) { aniMember = (AnimationMember *)member; aniMember->setFrame(frameIndex); } }如果你查看CCAnimate的update源码实现,你会发现它也是在update里通过设置切换帧来实现动画效果,所以我们也如法泡制,在update里轮循每个动画成员,让它切换一下帧,注意m_nNextFrame是CCAnimate里的protected成员变量,表示要播放的下一帧索引。
这样我们的动画联动类也就实现了,一遍下来发现原理也不复杂,就是在update里让子动画每帧切换下动画,那我们怎么运用它呢?
由于源工程比较宏大,不可能把所有的代码都贴出,我自己是个菜鸟,经常被所谓的高手们嘲笑,但我相信只要了解了原理,就算是像我这样智商一般的菜鸟也能运用自如:
好了不多废话,组建个动画还挺麻烦的,为了清晰起见写个方法: animateGroupWithActionWord 。
假定我们有个机器人类,继承于CCSprite,它有悠闲,攻击和行走各种动作,由于有它头上冒的烟所以每一个动画都应该是AnimateGroup, 方法如下:
AnimateGroup* Robot::animateGroupWithActionWord(const char* actionKeyWord, int frameCount, float delay) { //根据frame的前缀名来组建基本动画 CCAnimation* baseAnimation = this->animationWithPrefix(CCString::createWithFormat("robot_base_%s",actionKeyWord)->getCString(), 0, frameCount,delay); //腰带动画 AnimationMember *beltMember = this->animationMemberWithPrefix(CCString::createWithFormat("robot_belt_%s", actionKeyWord)->getCString(), 0, frameCount, delay, _belt); //头上的烟动画 AnimationMember *smokeMember = this->animationMemberWithPrefix(CCString::createWithFormat("robot_smoke_%s", actionKeyWord)->getCString(), 0, frameCount, delay, _smoke); <span style="white-space:pre"> </span>//组建动画组成员 将腰带动画和烟动画放进去 CCArray *animationMembers = CCArray::create(); animationMembers->addObject(beltMember); animationMembers->addObject(smokeMember); <span style="white-space:pre"> </span>//生成动画组 return AnimateGroup::actionWithAnimation(baseAnimation, animationMembers); }注释写的很清楚,就是生成三个基本动画,将机器人的动画作为主动画(actionWithAnimation作为第一个参数传入,非常重要),其他两个作为子成员塞进_members里。等等,那个讨厌的animationMemberWithPrefix是什么?其实也就是将动画生成的一些步骤封装了一下,代码如下:
AnimationMember* ActionSprite::animationMemberWithPrefix(const char* prefix, int startFrameIdx, int frameCount, float delay, cocos2d::CCSprite* target) { CCAnimation* animation = this->animationWithPrefix(prefix, startFrameIdx, frameCount, delay); return AnimationMember::memberWithAnimation(animation, target); }疑?怎么还有一层animationWithPrefix封装,烦不烦呀,没办法,源工程代码就是这样写的,我也是拿来主义,这个方法才是真正的动画封装,意思是从plist中取出帧名前缀为prefix的帧, 根据开始索引号和结束索引号在代码里拼出帧名,如"robot_idle_00.png", "robot_idle_01.png",结合每帧延时delay来生成动画。具体代码如下:
CCAnimation* ActionSprite::animationWithPrefix(const char* prefix, int startFrameIdx, int frameCount, float delay) { int idxCount = frameCount + startFrameIdx; //总帧数 CCArray *frames = CCArray::createWithCapacity(frameCount); CCSpriteFrame *frame; for (int i=startFrameIdx; i<idxCount; i++) { frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("%s_%02d.png", prefix, i)->getCString()); frames->addObject(frame); } return CCAnimation::createWithSpriteFrames(frames, delay); }这个代码您一定很熟悉,是cocos2d-x的标准的动画生成步骤,不多解释,它返回的是CCAnimation。
具体运用它就很简单了。
例如机器人的站立动画:
//idle动画 AnimateGroup *idleAnimationGroup = this->animateGroupWithActionWord("idle", 5, 1.0f/12.0f); this->_idleAction = CCRepeatForever::create(idleAnimationGroup) this->idleAction->retain();这个机器人Robot类里专门有个成员变量是_idleAction,用来存放站立动画,要播放时直接:
robot->runAction(robot->_idleAction); 即可
可以看出,运行正常
总结:虽然运用它看起来很麻烦,步骤很烦琐,但我一直不相信简单就是美这种肤浅的话,要想实现复杂的功能,光想着简单是没有用的。不过基本原理确实不复杂,其实大量的代码都是基本的生成帧动画。动画组的主要步骤就是先生成主动画和子动画,用主动画来初始化动画组,子动画塞到动画组的_members数组里,然后就可以像正常的CCAnimate这样来播放了。
机器人的例子是完结了,相信读者看后可以运用的自己的工程中,但是别以为大功告成了。因为这个例子不具有代表性,因为机器人头上的烟本身就是以属于机器人类里的,而我们经常遇到的角色穿装备,还有格斗游戏里的投技,子对象就和主对象不是一个类的包含关系,而是两个独立的对象,有自己的位置和朝向,这时就需要考虑位置和朝向的关系了,不然会发生动画是联动了但位置却对的乱七八糟这种情况。这个放在下一节中讲解。
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。