cocos2d-x實現多個精靈動畫同步播放(一)
阿新 • • 發佈:2019-02-17
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這樣來播放了。
機器人的例子是完結了,相信讀者看後可以運用的自己的工程中,但是別以為大功告成了。因為這個例子不具有代表性,因為機器人頭上的煙本身就是以屬於機器人類裡的,而我們經常遇到的角色穿裝備,還有格鬥遊戲裡的投技,子物件就和主物件不是一個類的包含關係,而是兩個獨立的物件,有自己的位置和朝向,這時就需要考慮位置和朝向的關係了,不然會發生動畫是聯動了但位置卻對的亂七八糟這種情況。這個放在下一節中講解。