[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier]
红孩儿Cocos2d-X学习园地QQ群:249941957 [暂满]加群写:Cocos2d-x
红孩儿Cocos2d-X学习园地QQ2群:44208467 加群写:Cocos2d-x
本章为我的Cocos2d-x教程一书初稿。望各位看官多提建议!
另:本章所用Cocos2d-x版本为:
cocos2d-2.0-x-2.0.2 @ Aug 30 2012
http://cn.cocos2d-x.org/download
之前我们讲解了Cocos2d-x中的TestCpp工程,它包含了43个小示例。每个示例都是通过Controller.cpp中的CreateTestScene函数创建的场景来进行演示的,从今天起,我们来详细学习每一个示例。今天我们来学习第一个示例:”ActionsTest”。
“ActionsTest”-- 这个工程要演示的是什么节目呢?它演示了精灵的各种动画。包括移动,旋转,缩放,跳跃,色彩变化,以及路径动画,帧动画等。这个示例是学习掌握图片精灵的一个很好的教材。注:虽然很明显,这个”ActionsTest”应该称之为“动作”演示,但我更想说“动作”这个词在表达本节众多动画的演示时太狭窄和片面,没有“动画”这个词合适,所以我在这里称之为“动画”。
为了更好的讲解Actions,我将本章分为三节课,话为“三板斧”。第一斧子,讲解各种Actions的原理。第二斧子,讲解各种衍生的Actions派生类,第三斧子,在前两斧子的基础上捋一下ActionsTest工程源码。相信三斧子下去,你会对于Cocos2d-x引擎的Actions有个系统的掌握。
好,现在我们开始举起斧子,准备好迎接新知识。
话说,一切的Actions都是由基类CCAction派生的,这个类描述了一个基本的动画控制方法。打开CCAction.h,我们来分析一下源码:
#ifndef __ACTIONS_CCACTION_H__ #define __ACTIONS_CCACTION_H__ #include "cocoa/CCObject.h" #include "cocoa/CCGeometry.h" #include "platform/CCPlatformMacros.h" //Cocos2d命名空间 NS_CC_BEGIN //定义一个枚举值来代表默认Tag无效值 enum { kCCActionTagInvalid = -1, }; //基础动画描述,其规定了一个动画所必须有的属性和接口。 class CC_DLL CCAction : public CCObject { public: //构造与析构 CCAction(void); virtual ~CCAction(void); //取得当前动画的描述 const char* description(); //产生一个当前类的实例对象拷贝 virtual CCObject* copyWithZone(CCZone *pZone); //返回动画是否播放结束了 virtual bool isDone(void); //设置将要演示本动画的结点,这里称为“演员”好了,在动作开始前调用。 virtual void startWithTarget(CCNode *pTarget); //设置动画停止结束,并将本动画的“演员”置空。 virtual void stop(void); //重点:每一帧传入时间流逝值,计算动画的进度。 virtual void step(float dt); //超级重点:传入进度参数,进行相应的动画播放计算。这个参数time取0~1,比如0代表动画开始播放,0.5代表播放到一半,1代表播放到结尾。本函数是个虚拟函数,所有由CCAction派生的子类都可以重载此函数来处理自已的动画算法以达到不同的动画效果,比如移动动画,那这里可以进行移动插值算法,淡入淡出动画,可以进行淡入淡出插值算法。 virtual void update(float time); //取得当前表演该动画的“演员”,哈,我们姑且用“演员”来表示演示动画的结点。 inline CCNode* getTarget(void) { return m_pTarget; } //设置当前表演该动画的“演员” inline void setTarget(CCNode *pTarget) { m_pTarget = pTarget; } //取得最初设置的“演员” inline CCNode* getOriginalTarget(void) { return m_pOriginalTarget; } //设置最初设置的“演员” inline void setOriginalTarget(CCNode *pOriginalTarget) { m_pOriginalTarget = pOriginalTarget; } //取得当前动画的用户标记。你可以设一个查询值,以方便的在外部来寻询到当前动画。 inline int getTag(void) { return m_nTag; } //设置当前动画的用户标记。 inline void setTag(int nTag) { m_nTag = nTag; } public: //创建并返回一个CCAction实例,内部是通过调用create函数来实现。 CC_DEPRECATED_ATTRIBUTE static CCAction* action(); //上面函数的实现。 static CCAction* create(); protected: //最初设置的“演员”。 CCNode *m_pOriginalTarget; //当前设置的“演员”。 CCNode *m_pTarget; //用户标记 int m_nTag; }; //时间动画:在基础动画的基础上加入了时间控制方面的属性和接口。 class CC_DLL CCFiniteTimeAction : public CCAction { public: //构造与析构 CCFiniteTimeAction() : m_fDuration(0) {} virtual ~CCFiniteTimeAction(){} //取得动画的时长,单位是秒。 inline float getDuration(void) { return m_fDuration; } //设置动画的时长,单位是秒。 inline void setDuration(float duration) { m_fDuration = duration; } //返回一个反向播放的动画,这是什么意思呢?在CCAction的类说明中我们知道它最重要的函数是step和update,依靠step计算出来的进度值传入update来显示相应的动画帧。一般来说时间进度的变化是从0~1,只要我们能让进度从1~0的进度变化就可以实现反向动画。 virtual CCFiniteTimeAction* reverse(void); protected: //动画的时长。 float m_fDuration; }; class CCActionInterval; class CCRepeatForever; //可调节目标动画播放速度的动画 class CC_DLL CCSpeed : public CCAction { public: //构造与析构函数 CCSpeed() : m_fSpeed(0.0) , m_pInnerAction(NULL) {} virtual ~CCSpeed(void); //取得动画播放速度 inline float getSpeed(void) { return m_fSpeed; } //设置动画播放速度 inline void setSpeed(float fSpeed) { m_fSpeed = fSpeed; } //初始化动画,参数一是一个CCActionInterval,它是一个匀速动画,代表了当前可调节速度的动画所控制的动画。参数二是手动指定的动画播放速度。 bool initWithAction(CCActionInterval *pAction, float fSpeed); //CCAction的函数重载 virtual CCObject* copyWithZone(CCZone *pZone); virtual void startWithTarget(CCNode* pTarget); virtual void stop(); virtual void step(float dt); virtual bool isDone(void); //产生一个反向播放动画 virtual CCActionInterval* reverse(void); //设置一个要控制的动画,这里称为“内部动画”,即当前动画的效果作用于另外的动画目标。 void setInnerAction(CCActionInterval *pAction); //取得所控制的“内部动画” inline CCActionInterval* getInnerAction() { return m_pInnerAction; } public: //创建一个可调节播放速度的动画,内部调用create来实现。参一为“内部动画”,参二为播放速度。 CC_DEPRECATED_ATTRIBUTE static CCSpeed* actionWithAction(CCActionInterval *pAction, float fSpeed); //创建一个可调节播放速度的动画 static CCSpeed* create(CCActionInterval* pAction, float fSpeed); protected: //播放速度 float m_fSpeed; //控制的“内部动画” CCActionInterval *m_pInnerAction; }; //跟随动画:控制演员跟随目标结点的动画运动。 class CC_DLL CCFollow : public CCAction { public: //构造与析构函数 CCFollow() : m_pobFollowedNode(NULL) , m_bBoundarySet(false) , m_bBoundaryFullyCovered(false) , m_fLeftBoundary(0.0) , m_fRightBoundary(0.0) , m_fTopBoundary(0.0) , m_fBottomBoundary(0.0) {} virtual ~CCFollow(void); //是否有边界 inline bool isBoundarySet(void) { return m_bBoundarySet; } //设置是否有边界 inline void setBoudarySet(bool bValue) { m_bBoundarySet = bValue; } //初始化,参数1为跟随目标结点,参数2为边界矩形,默认为CCRectZero代表无边界。 bool initWithTarget(CCNode *pFollowedNode, const CCRect& rect = CCRectZero); //CCAction的函数重载 virtual CCObject* copyWithZone(CCZone *pZone); virtual void step(float dt); virtual bool isDone(void); virtual void stop(void); public: //创建一个跟随动画,其内部调用create函数来实现。 CC_DEPRECATED_ATTRIBUTE static CCFollow* actionWithTarget(CCNode *pFollowedNode, const CCRect& rect = CCRectZero); //创建一个跟随动画 static CCFollow* create(CCNode *pFollowedNode, const CCRect& rect = CCRectZero); protected: //跟随的结点 CCNode *m_pobFollowedNode; //是否被限制在边界内 bool m_bBoundarySet; //边界是否只是一个点,代表边界无效。 bool m_bBoundaryFullyCovered; //定义变量保存屏幕尺寸信息,是为了快速取得。 //屏幕一半大小 CCPoint m_obHalfScreenSize; //屏幕全部大小 CCPoint m_obFullScreenSize; //边界的左,右,上,下的位置。 float m_fLeftBoundary; float m_fRightBoundary; float m_fTopBoundary; float m_fBottomBoundary; }; NS_CC_END #endif // __ACTIONS_CCACTION_H__
看这个头文件,除了CCAction之外还有几个由CCAction派生的类的定义,将它们都放在CCAction的意义是认为所有的动画都是由这几个类所派生的。
我们再来看一下CPP它们都是怎么实现的:
#include "CCAction.h" #include "CCActionInterval.h" #include "base_nodes/CCNode.h" #include "support/CCPointExtension.h" #include "CCDirector.h" #include "cocoa/CCZone.h" //使用Cocos2d命名空间 NS_CC_BEGIN //构造 CCAction::CCAction() :m_pOriginalTarget(NULL) ,m_pTarget(NULL) ,m_nTag(kCCActionTagInvalid) { } //析构 CCAction::~CCAction() { CCLOGINFO("cocos2d: deallocing"); } //创建并返回一个CCAction实例,内部是通过调用create函数来实现。 CCAction * CCAction::action() { return CCAction::create(); } //创建并返回一个CCAction实例。 CCAction* CCAction::create() { CCAction * pRet = new CCAction(); //新创建的CCAction设为由内存管理器进行释放。 pRet->autorelease(); return pRet; } //取得当前动画的描述 const char* CCAction::description() { //描述信息通过CCString的格式化字符串函数createWithFormat来创建字符串。 return CCString::createWithFormat("<CCAction | Tag = %d>", m_nTag)->getCString(); } //产生一个当前类的实例对象拷贝 CCObject* CCAction::copyWithZone(CCZone *pZone) { //定义用于保存新拷贝指针的变量,参看CCZone的定义可以知道,它的功能仅仅是保存一个CCObject指针。 CCZone *pNewZone = NULL; //定义用于返回结果的CCAction指针变量 CCAction *pRet = NULL; //如果pZone不为空且它保存了有效的CCObject,则返回 if (pZone && pZone->m_pCopyObject) { pRet = (CCAction*)(pZone->m_pCopyObject); } else { //否则手动创建一个新的动作实例,返回给pRet pRet = new CCAction(); //这一句其实可以不加,因为后面直接释放了,没有使用。 pNewZone = new CCZone(pRet); } //拷贝动作的用户数据 pRet->m_nTag = m_nTag; //释放pNewZone。 CC_SAFE_DELETE(pNewZone); return pRet; } //设置当前动画的演员。 void CCAction::startWithTarget(CCNode *aTarget) { m_pOriginalTarget = m_pTarget = aTarget; } //停止动画 void CCAction::stop() { m_pTarget = NULL; } //取得动画是否播放结束 bool CCAction::isDone() { return true; } //每一帧传入时间流逝值,计算动画的进度。在基类中并未做相应处理。需要子类进行实现。 void CCAction::step(float dt) { CC_UNUSED_PARAM(dt); CCLOG("[Action step]. override me"); } //传入进度参数,进行相应的动画播放计算。 void CCAction::update(float time) { CC_UNUSED_PARAM(time); CCLOG("[Action update]. override me"); } //时间动画的反向动画播放。基类中也并未做相应处理。需要子类进行实现。 CCFiniteTimeAction *CCFiniteTimeAction::reverse() { CCLOG("cocos2d: FiniteTimeAction#reverse: Implement me"); return NULL; } // //可调节播放速度的动画 // //析构函数。 CCSpeed::~CCSpeed() { CC_SAFE_RELEASE(m_pInnerAction); } //创建一个可调节播放速度的动画,内部调用create来实现。参一为“内部动画”,参二为播放速度。 CCSpeed * CCSpeed::actionWithAction(CCActionInterval *pAction, float fSpeed) { return CCSpeed::create(pAction, fSpeed); } //创建一个可调节播放速度的动画。 CCSpeed* CCSpeed::create(CCActionInterval* pAction, float fSpeed) { //创建一个可调节播放速度的动画返回其实例指针给变量pRet。 CCSpeed *pRet = new CCSpeed(); //如果返回的动画指针有效,并且初始化成功。将其交由内存管理器进行释放并返回动画指针。 if (pRet && pRet->initWithAction(pAction, fSpeed)) { pRet->autorelease(); return pRet; } //这是创建失败或者初始化失败的处理,释放并返回NULL。 CC_SAFE_DELETE(pRet); return NULL; } //初始化动画。 bool CCSpeed::initWithAction(CCActionInterval *pAction, float fSpeed) { //参数有效性判断 CCAssert(pAction != NULL, ""); //因使用pAction,将其引用计数加1 pAction->retain(); //将参数“内部动画”指针pAction保存入变量m_pInnerAction。 m_pInnerAction = pAction; //将速度值参数保存入变量m_fSpeed。 m_fSpeed = fSpeed; return true; } //产生一个当前类的实例对象拷贝 CCObject *CCSpeed::copyWithZone(CCZone *pZone) { //定义指针变量 CCZone* pNewZone = NULL; CCSpeed* pRet = NULL; //如果pZone有效且本身有拷贝 if(pZone && pZone->m_pCopyObject) { //返回这个拷贝存入变量pRet pRet = (CCSpeed*)(pZone->m_pCopyObject); } else { //如果pZone为空或没有拷贝,则创建一个CCSpeed返回给pRet,并由pRet做为拷贝取得一个新的CCZone返回给pZone。 pRet = new CCSpeed(); pZone = pNewZone = new CCZone(pRet); } //调用基类函数(对比基类的函数,可以得知这一句的意义是拷贝动作的用户数据到pRet和释放pZone) CCAction::copyWithZone(pZone); //初始化动画,这里的关健是通过取得内部动画的拷贝做为参数进行初始化。 pRet->initWithAction( (CCActionInterval*)(m_pInnerAction->copy()->autorelease()) , m_fSpeed ); //释放pNewZone CC_SAFE_DELETE(pNewZone); return pRet; } //设置将要演示本动画的结点 void CCSpeed::startWithTarget(CCNode* pTarget) { CCAction::startWithTarget(pTarget); m_pInnerAction->startWithTarget(pTarget); } //停止动画 void CCSpeed::stop() { //停止内部动画 m_pInnerAction->stop(); CCAction::stop(); } //每一帧传入时间流逝值,计算动画的进度。 void CCSpeed::step(float dt) { //对内部动画进行相关计算,这里通过对时间流逝值的缩放实现了播放速度的调节。 m_pInnerAction->step(dt * m_fSpeed); } //取得动画是否结束 bool CCSpeed::isDone() { //返回内部动画是否结束 return m_pInnerAction->isDone(); } //创建一个反向播放动画。 CCActionInterval *CCSpeed::reverse() { //返回一个内部动画的反向播放动画。 return (CCActionInterval*)(CCSpeed::create(m_pInnerAction->reverse(), m_fSpeed)); } //设置内部动画。 void CCSpeed::setInnerAction(CCActionInterval *pAction) { //在这里的处理是释放旧的内部动画,设置新的内部动画。 if (m_pInnerAction != pAction) { CC_SAFE_RELEASE(m_pInnerAction); m_pInnerAction = pAction; CC_SAFE_RETAIN(m_pInnerAction); } } // 跟随动画 //析构函数 CCFollow::~CCFollow() { //释放跟随的目标结点 CC_SAFE_RELEASE(m_pobFollowedNode); } //新创建一个跟随动画,参数1是跟随的目标,参数2是边界矩形。 CCFollow *CCFollow::actionWithTarget(CCNode *pFollowedNode, const CCRect& rect/* = CCRectZero*/) { return CCFollow::create(pFollowedNode, rect); } //新创建一个跟随动画。 CCFollow* CCFollow::create(CCNode *pFollowedNode, const CCRect& rect/* = CCRectZero*/) { //创建一个跟随动画,并初始化。 CCFollow *pRet = new CCFollow(); if (pRet && pRet->initWithTarget(pFollowedNode, rect)) { //如果创建成功并初始化成功则将其交由内存管理器进行释放处理。 pRet->autorelease(); return pRet; } //如果创建失败或初始化失败,释放pRet并返回NULL。 CC_SAFE_DELETE(pRet); return NULL; } //初始化跟随动画。 bool CCFollow::initWithTarget(CCNode *pFollowedNode, const CCRect& rect/* = CCRectZero*/) { //参数跟随结点的有效性判断 CCAssert(pFollowedNode != NULL, ""); //因为要用到跟随节点,所以手动对其引用计数器加1。 pFollowedNode->retain(); //将参数跟随结点保存到变量m_pobFollowedNode。 m_pobFollowedNode = pFollowedNode; //如果参数2无效,则将边界开关置为false,即无边界。 if (rect.equals(CCRectZero)) { m_bBoundarySet = false; } else { //否则有边界。 m_bBoundarySet = true; } //假设边界是有效矩形。 m_bBoundaryFullyCovered = false; //取和屏幕大小 CCSize winSize = CCDirector::sharedDirector()->getWinSize(); //将屏幕大小保存到m_obFullScreenSize。 m_obFullScreenSize = CCPointMake(winSize.width, winSize.height); //计算屏幕半大小保存到m_obHalfScreenSize m_obHalfScreenSize = ccpMult(m_obFullScreenSize, 0.5f); //如果有边界。将边界的四条边存入相应边量中。 if (m_bBoundarySet) { m_fLeftBoundary = -((rect.origin.x+rect.size.width) - m_obFullScreenSize.x); m_fRightBoundary = -rect.origin.x ; m_fTopBoundary = -rect.origin.y; m_fBottomBoundary = -((rect.origin.y+rect.size.height) - m_obFullScreenSize.y); //数据错误处理:如果右边界小于左边界,则将左右边界都设为中间位置。 if(m_fRightBoundary < m_fLeftBoundary) { m_fRightBoundary = m_fLeftBoundary = (m_fLeftBoundary + m_fRightBoundary) / 2; } //数据错误处理:如果下边界小于上边界,则将上下边界都设为中间位置。 if(m_fTopBoundary < m_fBottomBoundary) { m_fTopBoundary = m_fBottomBoundary = (m_fTopBoundary + m_fBottomBoundary) / 2; } //如果左右边界位置相等且上下边界位置相等,则这个边界矩形就只是一个点。 if( (m_fTopBoundary == m_fBottomBoundary) && (m_fLeftBoundary == m_fRightBoundary) ) { m_bBoundaryFullyCovered = true; } } return true; } //产生一个当前类的实例对象拷贝 CCObject *CCFollow::copyWithZone(CCZone *pZone) { CCZone *pNewZone = NULL; CCFollow *pRet = NULL; if(pZone && pZone->m_pCopyObject) { pRet = (CCFollow*)(pZone->m_pCopyObject); } else { pRet = new CCFollow(); pZone = pNewZone = new CCZone(pRet); } //调用基类函数(对比基类的函数,可以得知这一句的意义是拷贝动作的用户数据到pRet和释放pZone) CCAction::copyWithZone(pZone); //上一句已经实现拷贝动作的用户数据到pRet,这里再次进行拷贝,有点多余了。 pRet->m_nTag = m_nTag; CC_SAFE_DELETE(pNewZone); return pRet; } //每一帧传入时间流逝值,计算动画的进度。 void CCFollow::step(float dt) { CC_UNUSED_PARAM(dt); //如果有边界 if(m_bBoundarySet) { //边界只是一个点,则直接返回。 if(m_bBoundaryFullyCovered) return; //定义一个位置点做为相对于屏幕中心点的位置。这个位置点等于屏幕中心点减去跟随结点的位置。 CCPoint tempPos = ccpSub( m_obHalfScreenSize, m_pobFollowedNode->getPosition()); //把演示此动画的结点位置设置在边界范围内。 m_pTarget->setPosition(ccp(clampf(tempPos.x, m_fLeftBoundary, m_fRightBoundary), clampf(tempPos.y, m_fBottomBoundary, m_fTopBoundary))); } else { //定义一个位置点做为相对于屏幕中心点的位置。这个位置点等于屏幕中心点减去跟随结点的位置。设置演示此动画的结点的位置。 m_pTarget->setPosition(ccpSub(m_obHalfScreenSize, m_pobFollowedNode->getPosition())); } } //判断动画是否结束。 bool CCFollow::isDone() { //这里是判断跟随的结点是否不在运行中 return ( !m_pobFollowedNode->isRunning() ); } //停止动画 void CCFollow::stop() { m_pTarget = NULL; CCAction::stop(); } NS_CC_END
总结:我们在这一节分析了CCAction, CCFiniteTimeAction, CCSpeed,CCFollow四个类:
CCAction:一切动画的基类。它需要传入一个结点来做为表演动画的“演员”。
CCFiniteTimeAction:加入了时间属性的动画基类,用于派生各种由时间插值进行计算的动画。
CCSpeed:用来控制其它动画播放速度的动画,它需要传入一个动画来做控制目标。
CCFollow:用来控制演员跟随动画移动的基类,它需要传入一个结点来做跟随目标。
有了以上这几个基类,在后面我们就可以理解很多很多种各种各样的动画是如何创造出来的。二.动画管理器
动画是怎么管理和执行的呢。在Cocos2d-x中提供了一个动画管理器类CCActionManager。这个动画管理器负责对所有的动画进行管理。我们来学习一下它。
CCActionManager.h:
#ifndef __ACTION_CCACTION_MANAGER_H__ #define __ACTION_CCACTION_MANAGER_H__ //引用相应的头文件 //动画头文件 #include "CCAction.h" //CCObject实例指针管理器 #include "cocoa/CCArray.h" #include "cocoa/CCObject.h" //使用Cocos2d命名空间 NS_CC_BEGIN //要用到CCSet类进行存储 class CCSet; //使用哈希表元素 struct _hashElement; class CC_DLL CCActionManager : public CCObject { public: //构造与析构函数 CCActionManager(void); ~CCActionManager(void); //将一个CCNode及所应用的动画放入管理器,末参数指定是否立即播放。 void addAction(CCAction *pAction, CCNode *pTarget, bool paused); //移除所有的动画 void removeAllActions(void); //移除指定目标所应用的所有的动画 void removeAllActionsFromTarget(CCObject *pTarget); //移除所有的指定动画 void removeAction(CCAction *pAction); //移除指定目标所应用的某标记的动画 void removeActionByTag(unsigned int tag, CCObject *pTarget); //取得指定目标所应用的某标记的动画 CCAction* getActionByTag(unsigned int tag, CCObject *pTarget); //返回指定目标所应用的动画数量 unsigned int numberOfRunningActionsInTarget(CCObject *pTarget); //停止指定目标的所有的动画。 void pauseTarget(CCObject *pTarget); //继续播放指定目标的所有动画 void resumeTarget(CCObject *pTarget); //暂停所有正在动行的动画,并将这些暂停的动画存入CCSet中返回。 CCSet* pauseAllRunningActions(); //继结播放CCSet中的所有动画。 void resumeTargets(CCSet *targetsToResume); protected: // void removeActionAtIndex(unsigned int uIndex, struct _hashElement *pElement); //删除哈希表中的相应项 void deleteHashElement(struct _hashElement *pElement); //为pElement申请内存存放动画集。 void actionAllocWithHashElement(struct _hashElement *pElement); //这个函数重要了~,它在每一帧被调用,通过传入的时间流逝长度传给每一个非暂停的动画进行相应的更新。 void update(float dt); protected: // struct _hashElement *m_pTargets; struct _hashElement *m_pCurrentTarget; bool m_bCurrentTargetSalvaged; }; NS_CC_END #endif // __ACTION_CCACTION_MANAGER_H__
老规矩,上CPP:
#include "CCActionManager.h" #include "base_nodes/CCNode.h" #include "CCScheduler.h" #include "ccMacros.h" #include "support/data_support/ccCArray.h" #include "support/data_support/uthash.h" #include "cocoa/CCSet.h" //使用Cocos2d命名空间 NS_CC_BEGIN //这里定义了一个哈希表项结构,用于存储一个演员的动画集相关信息。 typedef struct _hashElement { struct _ccArray *actions; //动画集 CCObject *target; //演员的指针 unsigned int actionIndex;//动画在动画集中的索引。 CCAction *currentAction;//当前正在播放的动画 bool currentActionSalvaged;//当前动画是否被回收。 bool paused;//是否停止播放。 UT_hash_handle hh;//哈希表查询句柄。 } tHashElement; //构造函数 CCActionManager::CCActionManager(void) : m_pTargets(NULL), m_pCurrentTarget(NULL), m_bCurrentTargetSalvaged(false) { } //析构函数 CCActionManager::~CCActionManager(void) { CCLOGINFO("cocos2d: deallocing %p", this); //清空所有的动画 removeAllActions(); } //删除相应的哈希表项 void CCActionManager::deleteHashElement(tHashElement *pElement) { //释放pElement存储的动画集 ccArrayFree(pElement->actions); //从哈希表m_pTargets中删除pElement项 HASH_DEL(m_pTargets, pElement); //释放pElement pElement->target->release(); free(pElement); } //为哈希表项pElement申请内存,以存放动画集 void CCActionManager::actionAllocWithHashElement(tHashElement *pElement) { // 默认每个哈希表项存四个动画,所以如果当前哈希表项的动画集为空,则为其申请可存放4个动画指针的内存大小。 if (pElement->actions == NULL) { pElement->actions = ccArrayNew(4); }else if (pElement->actions->num == pElement->actions->max) { //如果哈希表项的动作集需要扩大,则扩增为原来的2倍直至达到最大容量。 ccArrayDoubleCapacity(pElement->actions); } } //从哈希表项的动画集中移除掉指定的动画。 void CCActionManager::removeActionAtIndex(unsigned int uIndex, tHashElement *pElement) { //通过参数uIndex找到哈希表项的动画集中的对应动画。 CCAction *pAction = (CCAction*)pElement->actions->arr[uIndex]; //如果当前动画正在播放中而且回收标记为否 if (pAction == pElement->currentAction && (! pElement->currentActionSalvaged)) { //引用计数器加1,代表被管理器使用中。 pElement->currentAction->retain(); //设其回收标记为true。 pElement->currentActionSalvaged = true; } //从动画集中移除指定位置的动画。 ccArrayRemoveObjectAtIndex(pElement->actions, uIndex, true); // if (pElement->actionIndex >= uIndex) { pElement->actionIndex--; } //如果pElement的动画集的数量为0 if (pElement->actions->num == 0) { //如果当前正有演员在演示本动画,那么先将其要回收标记设为true 。 if (m_pCurrentTarget == pElement) { m_bCurrentTargetSalvaged = true; } else { //否则立即释放 deleteHashElement(pElement); } } } //暂停目标演员身上的所有动画 void CCActionManager::pauseTarget(CCObject *pTarget) { //声明一个哈希表项指针 tHashElement *pElement = NULL; //通过哈希表处理相关宏找到对应的哈希表项。 HASH_FIND_INT(m_pTargets, &pTarget, pElement); //如果找到,则将其暂停开关变量设为true if (pElement) { pElement->paused = true; } } //继续目标演员身上的所有动画 void CCActionManager::resumeTarget(CCObject *pTarget) { //定义一个哈希表项指针变量并置空。 tHashElement *pElement = NULL; //通过哈希表处理相关宏找到对应的哈希表项。 HASH_FIND_INT(m_pTargets, &pTarget, pElement); //如果找到,则将其暂停开关变量设为false,使其处于播放状态。 if (pElement) { pElement->paused = false; } } //暂停所有正在播放中的动画 CCSet* CCActionManager::pauseAllRunningActions() { //创建一个CCSet容器 CCSet *idsWithActions = new CCSet(); //此容器交由内存管理器进行内存的最后释放。无需手动delete。 idsWithActions->autorelease(); //遍历所有的哈希表项 for (tHashElement *element=m_pTargets; element != NULL; element = (tHashElement *)element->hh.next) { //如果当前遍历位置的哈希表项处于播放状态,设置其暂停并将其放入CCSet容器中。 if (! element->paused) { element->paused = true; idsWithActions->addObject(element->target); } } //返回容器。 return idsWithActions; } //继续播放容器中的所有动画 void CCActionManager::resumeTargets(cocos2d::CCSet *targetsToResume) { //定义CCSet容器的迭代器。之后遍历参数容器。将每一个项设为播放状态。 CCSetIterator iter; for (iter = targetsToResume->begin(); iter != targetsToResume->end(); ++iter) { resumeTarget(*iter); } } //将一个动画和应用此动画的演员,以及当前动画是否处于暂停状态信息存入管理器中。 void CCActionManager::addAction(CCAction *pAction, CCNode *pTarget, bool paused) { //有效性判断。 CCAssert(pAction != NULL, ""); CCAssert(pTarget != NULL, ""); //定义一个哈希表项的指针变量并置空。 tHashElement *pElement = NULL; //将pTarget转为一个CCObject类型的指针。用于哈希表查询。 CCObject *tmp = pTarget; //通过这个指针,找到对应的哈希表项返回给pElement; HASH_FIND_INT(m_pTargets, &tmp, pElement); //如果找不到。则代表新加入的哈希表项。则申请内存创建此哈希表项,并将其加入哈希表中。 if (! pElement) { //创建一个哈希表项 pElement = (tHashElement*)calloc(sizeof(*pElement), 1); //设置其是否为暂停状态。 pElement->paused = paused; //引用计数器加1,代表被管理器使用中。 pTarget->retain(); //设置哈希表项中的演员为参数指定的演员。 pElement->target = pTarget; //调用哈希表处理宏将哈希表项加入哈希表。 HASH_ADD_INT(m_pTargets, target, pElement); } //为哈希表项pElement申请内存,以存放动画集 actionAllocWithHashElement(pElement); //判断pAction是否在pElement的动画集中。确保只放入一次。 CCAssert(! ccArrayContainsObject(pElement->actions, pAction), ""); //将pAction放入pElement的动画集中。 ccArrayAppendObject(pElement->actions, pAction); //设置是哪个CCNode要进行当前动画 pAction->startWithTarget(pTarget); } //清除所有的动画 void CCActionManager::removeAllActions(void) { //遍历哈希表中的所有项 for (tHashElement *pElement = m_pTargets; pElement != NULL; ) { //取得对应项信息中的演员指针 CCObject *pTarget = pElement->target; //这里是用于继续循环的处理,将当前项指向下一个哈希表项。 pElement = (tHashElement*)pElement->hh.next; //释放演员的所有动画 removeAllActionsFromTarget(pTarget); } } //删除对应演员的所有动画 void CCActionManager::removeAllActionsFromTarget(CCObject *pTarget) { // 有效性判断 if (pTarget == NULL) { return; } //定义一个哈希表项指针,并通过哈希表的查询宏取得对应项地址返回给指针。 tHashElement *pElement = NULL; HASH_FIND_INT(m_pTargets, &pTarget, pElement); //如果找到此项 if (pElement) { //如果此哈希表项有正在播放的动画并且这个动画并未被设为要回收。 if (ccArrayContainsObject(pElement->actions, pElement->currentAction) && (! pElement->currentActionSalvaged)) { //将当前正在播放的动画的引用计数器加1并设置其回收标记为true。引用计数加1的目的是人为使其暂不能被正常释放,需要待后面再减1后才可以被释放。 pElement->currentAction->retain(); pElement->currentActionSalvaged = true; } //清空当前哈希表项的动画集。 ccArrayRemoveAllObjects(pElement->actions); //如果当前哈希表项正处于使用中,暂不释放,只将其要回收的标记设为true。 if (m_pCurrentTarget == pElement) { m_bCurrentTargetSalvaged = true; } else { //释放当前哈希表项 deleteHashElement(pElement); } } else { // CCLOG("cocos2d: removeAllActionsFromTarget: Target not found"); } } //将指定动画从动画管理器中移除 void CCActionManager::removeAction(CCAction *pAction) { //有效性判断 if (pAction == NULL) { return; } //定义一个哈希表项指针,并通过哈希表的查询宏取得对应项地址返回给指针。 tHashElement *pElement = NULL; CCObject *pTarget = pAction->getOriginalTarget(); HASH_FIND_INT(m_pTargets, &pTarget, pElement); //如果找到此项 if (pElement) { //取得pAction处于当前项的动画集的索引 unsigned int i = ccArrayGetIndexOfObject(pElement->actions, pAction); //如果这个索引是有效的,调用函数将pElement的指定索引的动画移除。 if (UINT_MAX != i) { removeActionAtIndex(i, pElement); } } else { CCLOG("cocos2d: removeAction: Target not found"); } } //将指定演员的指定动画删除 void CCActionManager::removeActionByTag(unsigned int tag, CCObject *pTarget) { //有效性判断 CCAssert((int)tag != kCCActionTagInvalid, ""); CCAssert(pTarget != NULL, ""); //定义哈希表项指针变量,并通过哈希表处理宏取得相应哈希表项 tHashElement *pElement = NULL; HASH_FIND_INT(m_pTargets, &pTarget, pElement); //如果能找到哈希表项 if (pElement) { //取得哈希表项的动画集中的动画数量 unsigned int limit = pElement->actions->num; //遍历动画集中的所有动画 for (unsigned int i = 0; i < limit; ++i) { //取得每一项动画 CCAction *pAction = (CCAction*)pElement->actions->arr[i]; //查看是否是指定演员的指定动画。 if (pAction->getTag() == (int)tag && pAction->getOriginalTarget() == pTarget) { //如果是,则删除此项。 removeActionAtIndex(i, pElement); break; } } } } //取得指定演员的指定动画 CCAction* CCActionManager::getActionByTag(unsigned int tag, CCObject *pTarget) { //有效性判断 CCAssert((int)tag != kCCActionTagInvalid, ""); //定义哈希表项指针变量,并通过哈希表处理宏取得相应哈希表项 tHashElement *pElement = NULL; HASH_FIND_INT(m_pTargets, &pTarget, pElement); //如果能找到哈希表项 if (pElement) { //如果此哈希表项的动画集不为空 if (pElement->actions != NULL) { //取得哈希表项的动画集中的动画数量 unsigned int limit = pElement->actions->num; //遍历动画集中的所有动画 for (unsigned int i = 0; i < limit; ++i) { //取得每一项动画 CCAction *pAction = (CCAction*)pElement->actions->arr[i]; //查看是否是指定动画。 if (pAction->getTag() == (int)tag) { return pAction; } } } CCLOG("cocos2d : getActionByTag: Action not found"); } else { // CCLOG("cocos2d : getActionByTag: Target not found"); } //找不到要找的动画。返回NULL。 return NULL; } //取得指定演员的动画集中的动画数量 unsigned int CCActionManager::numberOfRunningActionsInTarget(CCObject *pTarget) { //定义哈希表项指针变量,并通过哈希表处理宏取得相应哈希表项 tHashElement *pElement = NULL; HASH_FIND_INT(m_pTargets, &pTarget, pElement); if (pElement) { //如果找到了,判断动画集是否为空,如果不为空返回动画数量,否则返回零。 return pElement->actions ? pElement->actions->num : 0; } return 0; } //动画管理器的更新函数。 void CCActionManager::update(float dt) { //遍历哈希表的所有项。 for (tHashElement *elt = m_pTargets; elt != NULL; ) { //将当前哈希表项保存到变量m_pCurrentTarget中。并将此项对应的回收标记m_bCurrentTargetSalvaged 设为false, m_pCurrentTarget = elt; m_bCurrentTargetSalvaged = false; //如果当前项的动画处于播放状态。 if (! m_pCurrentTarget->paused) { //遍历当前项的动画集中的所有动画 for (m_pCurrentTarget->actionIndex = 0; m_pCurrentTarget->actionIndex < m_pCurrentTarget->actions->num; m_pCurrentTarget->actionIndex++) { //取得遍历项的动画 m_pCurrentTarget->currentAction = (CCAction*)m_pCurrentTarget->actions->arr[m_pCurrentTarget->actionIndex]; //如果遍历项动画为空,跳过后遍历下一个动画。 if (m_pCurrentTarget->currentAction == NULL) { continue; } //设置回收标记为false。 m_pCurrentTarget->currentActionSalvaged = false; //更新当前动画的播放 m_pCurrentTarget->currentAction->step(dt); //如果当前项的回收标记为true,则进行释放处理。 if (m_pCurrentTarget->currentActionSalvaged) { m_pCurrentTarget->currentAction->release(); } else if (m_pCurrentTarget->currentAction->isDone()) { //如果当前动画处于结束状态,则停止动画 m_pCurrentTarget->currentAction->stop(); //为了在removeAction中正确释放动画,这里先创建一个临时变量pAction记录一下要释放的动画。 CCAction *pAction = m_pCurrentTarget->currentAction; //在removeAction之前将当前哈希表项中的当前动画设为NULL,否则不能释放。 m_pCurrentTarget->currentAction = NULL; removeAction(pAction); } m_pCurrentTarget->currentAction = NULL; } } //使for循环能够继续 elt = (tHashElement*)(elt->hh.next); // 如果当前哈希表项处于回收状态且其动画集为空,删除此哈希表项。 if (m_bCurrentTargetSalvaged && m_pCurrentTarget->actions->num == 0) { deleteHashElement(m_pCurrentTarget); } } // 将变量m_pCurrentTarge置空。 m_pCurrentTarget = NULL; } NS_CC_END
动画管理器的代码读完了,大概了解了它的功能,但它在哪里使用呢?我们可以打开显示设备管理器CCDirector类的头文件,在其中找到:
CC_PROPERTY(CCActionManager*,m_pActionManager, ActionManager);
并在CPP中搜索m_pActionManager:
C:\cocos2d-2.0-x-2.0.2\cocos2dx\CCDirector.cpp(145): m_pActionManager = new CCActionManager();
C:\cocos2d-2.0-x-2.0.2\cocos2dx\CCDirector.cpp(146): m_pScheduler->scheduleUpdateForTarget(m_pActionManager,kCCPrioritySystem, false);
C:\cocos2d-2.0-x-2.0.2\cocos2dx\CCDirector.cpp(175): CC_SAFE_RELEASE(m_pActionManager);
C:\cocos2d-2.0-x-2.0.2\cocos2dx\CCDirector.cpp(887): if (m_pActionManager != pActionManager)
C:\cocos2d-2.0-x-2.0.2\cocos2dx\CCDirector.cpp(890): CC_SAFE_RELEASE(m_pActionManager);
C:\cocos2d-2.0-x-2.0.2\cocos2dx\CCDirector.cpp(891): m_pActionManager = pActionManager;
C:\cocos2d-2.0-x-2.0.2\cocos2dx\CCDirector.cpp(897): return m_pActionManager;
第一句在init函数中创建动画管理器。
第二句在创建后将其加入系统更新函数要调用更新的对象。
第三句在析构函数中释放动画管理器。
第四,五,六句在setActionManager中更改动画管理器。
第七句是取得动画管理器。
这其中关键是第二句。因为有了它,所以动画管理器的update才会被调用。这样,我们在动画管理器的Update中加断点,启动程序,你可以发现中断并显示调用堆栈。
流程是这样的:在CCApplication的run函数中,显示设备链调用相应的场景显示函数drawScene来绘制场景,然后调用了CCScheduler的update函数,在这个函数里,对所有注册要调用update的对象指针进行遍历并调用其update函数,而CCActionManager的update会对所有演员的动作调用step函数,最终实现各个动画的播放。
好了,我们现在呼吸一下,听我把这个背后的故事讲给大家。
CCAction 是 动画基类,它派生出了很多可爱的动画类型,当你要让某个演员去表现一个动画的时候,你就对这个演员使用“runAction”来表演对应的动画。这个演员在收到你的指令后,它把自已的名字(其实是自已的指针)和这个动画一起告诉了显示部门长官—“设备管理器”手下的成员之一 -- “动画管理器”,“动画管理器”这家伙在收到演员的需求后,就答应在每一次刷新时更新相应的动画。
你懂了么?如果懂了?咱们就暂时休息一下。后面还有两斧子。