在cocos2d-x中动作的执行调度是受cocos2d-x的全局定时器控制的,当初始完导演后便已经启动用于管理动作的update定时器。

bool CCDirector::init(void)
{...m_pActionManager = new CCActionManager();m_pScheduler->scheduleUpdateForTarget(m_pActionManager, kCCPrioritySystem, false);...return true;
}

也就说主循环每帧都会调用这个定时器的调度,且优先级是小于0的,是一个高优先级的定时器。从上一篇文章可以得到的一个结果是当执行这个定时器调度时,由于是update的定时器,所以直接是每帧的调用m_pActionManager的update方法。

所以先看下这个动作管理器的定义:

//动作管理类
class CC_DLL CCActionManager : public CCObject
{
public:/***  @js ctor*///构造CCActionManager(void);/***  @js NA*  @lua NA*///析构~CCActionManager(void);// actions/** Adds an action with a target. If the target is already present, then the action will be added to the existing target.If the target is not present, a new instance of this target will be created either paused or not, and the action will be added to the newly created target.When the target is paused, the queued actions won't be 'ticked'.*///添加一个动作到管理器中void addAction(CCAction *pAction, CCNode *pTarget, bool paused);/** Removes all actions from all the targets.*///从管理器中移除所有动作void removeAllActions(void);/** Removes all actions from a certain target.All the actions that belongs to the target will be removed.*///从管理器中移除节点的所有动作void removeAllActionsFromTarget(CCObject *pTarget);/** Removes an action given an action reference.*///从管理器中移除一个动作void removeAction(CCAction *pAction);/** Removes an action given its tag and the target *///从管理器中根据动作标记移除一个动作void removeActionByTag(unsigned int tag, CCObject *pTarget);/** Gets an action given its tag an a target@return the Action the with the given tag*///从管理器中获取一个带有指定标记的动作CCAction* getActionByTag(unsigned int tag, CCObject *pTarget);/** Returns the numbers of actions that are running in a certain target. * Composable actions are counted as 1 action. Example:* - If you are running 1 Sequence of 7 actions, it will return 1.* - If you are running 7 Sequences of 2 actions, it will return 7.*///返回指定节点中正在运行的动作的数量unsigned int numberOfRunningActionsInTarget(CCObject *pTarget);/** Pauses the target: all running actions and newly added actions will be paused.*///暂停节点的动作void pauseTarget(CCObject *pTarget);/** Resumes the target. All queued actions will be resumed.*///恢复节点的动作void resumeTarget(CCObject *pTarget);/** Pauses all running actions, returning a list of targets whose actions were paused.*///暂停所有正在运行的动作,并返回这些动作的集合CCSet* pauseAllRunningActions();/** Resume a set of targets (convenience function to reverse a pauseAllRunningActions call)*///恢复集合中指定的动作void resumeTargets(CCSet *targetsToResume);protected:// declared in CCActionManager.m//根据索引移除void removeActionAtIndex(unsigned int uIndex, struct _hashElement *pElement);//删除哈希表中指定的项void deleteHashElement(struct _hashElement *pElement);//为动作申请内存空间void actionAllocWithHashElement(struct _hashElement *pElement);//更新,由主循环的update定时器调用更新void update(float dt);protected:struct _hashElement    *m_pTargets;//存储目标节点的哈希表struct _hashElement    *m_pCurrentTarget;//当前的节点的哈希元素bool            m_bCurrentTargetSalvaged;//可回收标记
};

从上面的注释可以看出它的功能就是将动作加到自己的管理器中进行管理,当动作执行完后便会把这个动作从管理器中移除掉,另外还定义了两个哈希表来管理多个节点和执行节点和一个辅助的回收标记变量,跟定时器是设计十分相像:

typedef struct _hashElement
{struct _ccArray             *actions;//动作数组,即一个节点可以允许有多个动作CCObject                    *target;//执行动作的目标节点unsigned int                actionIndex;//索引CCAction                    *currentAction;//当前正在执行的动作bool                        currentActionSalvaged;//当前正在执行的动作是否被标记为可回收bool                        paused;//是否暂停UT_hash_handle                hh;//操作哈希表的句柄
} tHashElement;

整个实现细节如下(最终要的便是update是每帧都会被主循环所调用):‘

CCActionManager::CCActionManager(void)
: m_pTargets(NULL), m_pCurrentTarget(NULL),m_bCurrentTargetSalvaged(false)
{}CCActionManager::~CCActionManager(void)
{CCLOGINFO("cocos2d: deallocing %p", this);removeAllActions();//移除多有的动作
}// privatevoid CCActionManager::deleteHashElement(tHashElement *pElement)
{ccArrayFree(pElement->actions);//释放资源HASH_DEL(m_pTargets, pElement);//从哈希表中删除元素pElement->target->release();//动作目标的引用计数减1free(pElement);//释放哈希元素
}void CCActionManager::actionAllocWithHashElement(tHashElement *pElement)
{// 4 actions per Node by defaultif (pElement->actions == NULL)//每个节点初始化可存放4个节点{pElement->actions = ccArrayNew(4);}else if (pElement->actions->num == pElement->actions->max)//如果节点的动作数量大于当前的已拥有的动作数量{ccArrayDoubleCapacity(pElement->actions);//将容量扩大2倍}}void CCActionManager::removeActionAtIndex(unsigned int uIndex, tHashElement *pElement)
{CCAction *pAction = (CCAction*)pElement->actions->arr[uIndex];if (pAction == pElement->currentAction && (! pElement->currentActionSalvaged))//如果待移除的动作与当前正在执行的动作相等且没有被标记可回收,那么将其标记可回收{pElement->currentAction->retain();//引用计数加1,保证当前的动作能完整的被执行完pElement->currentActionSalvaged = true;//标记为可回收该动作}ccArrayRemoveObjectAtIndex(pElement->actions, uIndex, true);//将该动作从动作管理器中移除掉// update actionIndex in case we are in tick. looping over the actionsif (pElement->actionIndex >= uIndex)//保证动作被移除时循环能被正确执行{pElement->actionIndex--;}if (pElement->actions->num == 0)//如果节点的没有动作列表{if (m_pCurrentTarget == pElement)//如果待移除的动作与当前正在执行的动作相等{m_bCurrentTargetSalvaged = true;//将该正在执行的动作标记为可回收}else{//不相等deleteHashElement(pElement);//直接从哈希表中移除掉}}
}// pause / resumevoid CCActionManager::pauseTarget(CCObject *pTarget)
{tHashElement *pElement = NULL;HASH_FIND_INT(m_pTargets, &pTarget, pElement);//在哈希表中查找该节点的哈希项if (pElement)//找到,设置暂停{pElement->paused = true;}
}void CCActionManager::resumeTarget(CCObject *pTarget)
{tHashElement *pElement = NULL;HASH_FIND_INT(m_pTargets, &pTarget, pElement);//在哈希表中查找该节点的哈希项if (pElement)//找到,恢复运行{pElement->paused = false;}
}CCSet* CCActionManager::pauseAllRunningActions()
{CCSet *idsWithActions = new CCSet();//创建一个收集即将被设置为暂停的集合idsWithActions->autorelease();//放到内存回收池for (tHashElement *element=m_pTargets; element != NULL; element = (tHashElement *)element->hh.next) //遍历动作管理器{if (! element->paused) //如果不是暂停状态{element->paused = true;//设置暂停idsWithActions->addObject(element->target);//加到集合中}}    //返回集合return idsWithActions;
}void CCActionManager::resumeTargets(cocos2d::CCSet *targetsToResume)
{    CCSetIterator iter;for (iter = targetsToResume->begin(); iter != targetsToResume->end(); ++iter)//遍历集合中的动作{resumeTarget(*iter);//恢复动作}
}// runvoid CCActionManager::addAction(CCAction *pAction, CCNode *pTarget, bool paused)
{//参数检查CCAssert(pAction != NULL, "");CCAssert(pTarget != NULL, "");tHashElement *pElement = NULL;// we should convert it to CCObject*, because we save it as CCObject*CCObject *tmp = pTarget;HASH_FIND_INT(m_pTargets, &tmp, pElement);//在哈希表中查找该节点的哈希项if (! pElement)//没找到{//创建一个新的哈希项pElement = (tHashElement*)calloc(sizeof(*pElement), 1);pElement->paused = paused;//设置暂停pTarget->retain();//引用计数加1pElement->target = pTarget;//设置目标节点HASH_ADD_INT(m_pTargets, target, pElement);//将新创建的哈希项放到哈希表中}//为哈希项申请存放内存空间actionAllocWithHashElement(pElement);CCAssert(! ccArrayContainsObject(pElement->actions, pAction), "");ccArrayAppendObject(pElement->actions, pAction);//加到动作管理器中//初始化执行目标节点pAction->startWithTarget(pTarget);
}// removevoid 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)
{// explicit null handlingif (pTarget == NULL)//参数检查{return;}tHashElement *pElement = NULL;HASH_FIND_INT(m_pTargets, &pTarget, pElement);//在哈希表中查找该节点的哈希项if (pElement)//找到{if (ccArrayContainsObject(pElement->actions, pElement->currentAction) && (! pElement->currentActionSalvaged))//如果待移除的动作与当前正在执行的动作相等且没有被标记可回收,那么将其标记可回收{pElement->currentAction->retain();//引用计数加1,保证当前的动作能完整的被执行完pElement->currentActionSalvaged = true;//标记为可回收}ccArrayRemoveAllObjects(pElement->actions);//将该动作从动作管理器中移除掉if (m_pCurrentTarget == pElement)//如果待移除的动作与当前正在执行的动作相等{m_bCurrentTargetSalvaged = true;//将该正在执行的动作标记为可回收}else{//不相等deleteHashElement(pElement);//直接从哈希表中移除掉}}else{
//        CCLOG("cocos2d: removeAllActionsFromTarget: Target not found");}
}void CCActionManager::removeAction(CCAction *pAction)
{// explicit null handlingif (pAction == NULL){return;}tHashElement *pElement = NULL;CCObject *pTarget = pAction->getOriginalTarget();//获取原始的动作执行节点HASH_FIND_INT(m_pTargets, &pTarget, pElement);//在哈希表中查找该节点的哈希项if (pElement)//找到{unsigned int i = ccArrayGetIndexOfObject(pElement->actions, pAction);//获取该动作在动作管理中的索引值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;}}}
}// getCCAction* 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(tag = %d): Action not found", tag);}else{// CCLOG("cocos2d : getActionByTag: Target not found");}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;
}// main loop
void CCActionManager::update(float dt)
{for (tHashElement *elt = m_pTargets; elt != NULL; ){m_pCurrentTarget = elt;m_bCurrentTargetSalvaged = false;if (! m_pCurrentTarget->paused)//如果已暂停,不执行{// The 'actions' CCMutableArray may change while inside this loop.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;}m_pCurrentTarget->currentActionSalvaged = false;//标记当前的节点为不可回收,保证动作执行完毕m_pCurrentTarget->currentAction->step(dt);//计算动作执行的进度if (m_pCurrentTarget->currentActionSalvaged)//如果在上面的计算进度的过程中通知删除自己{// The currentAction told the node to remove it. To prevent the action from// accidentally deallocating itself before finishing its step, we retained// it. Now that step is done, it's safe to release it.m_pCurrentTarget->currentAction->release();//释放掉当前节点的当前执行动作} elseif (m_pCurrentTarget->currentAction->isDone())//是否执行完毕{m_pCurrentTarget->currentAction->stop();//停止动作CCAction *pAction = m_pCurrentTarget->currentAction;//当前节点的当前执行动作// Make currentAction nil to prevent removeAction from salvaging it.m_pCurrentTarget->currentAction = NULL;//执行完后将当前的节点的执行动作置空removeAction(pAction);//从动作管理器中移除掉,释放掉该动作}m_pCurrentTarget->currentAction = NULL;}}// elt, at this moment, is still valid// so it is safe to ask this here (issue #490)elt = (tHashElement*)(elt->hh.next);//下一个哈希项// only delete currentTarget if no actions were scheduled during the cycle (issue #481)if (m_bCurrentTargetSalvaged && m_pCurrentTarget->actions->num == 0)//如果当前节点被标记为可回收且动作集为空{deleteHashElement(m_pCurrentTarget);//从哈希表删除该节点}}// issue #635m_pCurrentTarget = NULL;
}

既然动作类是被这个管理器所管理的那么它的实现肯定是相当重要。

(CCActionInterval的子类没截完)

从上图可以看出动作类CCAction有三个子类分别是:

1)有限时间的执行的动作(CCFiniteTimeAction)

2)跟随动作(CCFollow)

3)可变速度动作(CCSpeed)

而其中有限时间的执行的动作又有两个重要的子类,分别是:

1)瞬时动作(CCActionInstant):持续时间为0,

2)持续动作(CCActionInterval):拥有自己的持续时间

这两个类衍生很多通用的动作供开发者使用。

接下来是动作类的头文件:

enum {//! Default tag//动作的默认标记kCCActionTagInvalid = -1,
};/*** @addtogroup actions* @{*//**
@brief Base class for CCAction objects.*/
//所有动作的基类
class CC_DLL CCAction : public CCObject
{
public:/*** @js ctor*///构造CCAction(void);/*** @js NA* @lua NA*///析构virtual ~CCAction(void);/*** @js NA* @lua NA*///描述该动作const char* description();/*** @js NA* @lua NA*///拷贝一个动作virtual CCObject* copyWithZone(CCZone *pZone);//! return true if the action has finished//返回动作已经执行完毕virtual bool isDone(void);//! called before the action start. It will also set the target.//在动作开始前会设置一个执行目标virtual void startWithTarget(CCNode *pTarget);/** called after the action has finished. It will set the 'target' to nil.IMPORTANT: You should never call "[action stop]" manually. Instead, use: "target->stopAction(action);"*///动手执行完毕后会调用它,并将执行目标重置为空virtual void stop(void);//! called every frame with it's delta time. DON'T override unless you know what you are doing.//计算动作执行的进度virtual void step(float dt);/** called once per frame. time a value between 0 and 1For example: - 0 means that the action just started- 0.5 means that the action is in the middle- 1 means that the action is over*///动作的实现方法virtual void update(float time);//获取执行目标inline CCNode* getTarget(void) { return m_pTarget; }/** The action will modify the target properties. *///设置执行目标inline void setTarget(CCNode *pTarget) { m_pTarget = pTarget; }//获取原始的执行目标inline CCNode* getOriginalTarget(void) { return m_pOriginalTarget; } /** Set the original target, since target can be nil.Is the target that were used to run the action. Unless you are doing something complex, like CCActionManager, you should NOT call this method.The target is 'assigned', it is not 'retained'.@since v0.8.2*///设置原始的执行目标inline void setOriginalTarget(CCNode *pOriginalTarget) { m_pOriginalTarget = pOriginalTarget; }//获取动作的标记inline int getTag(void) { return m_nTag; }//设置动作的标记inline void setTag(int nTag) { m_nTag = nTag; }public:/** Create an action *///创建一个动作static CCAction* create();
protected://原始执行目标CCNode    *m_pOriginalTarget;/** The "target".The target will be set with the 'startWithTarget' method.When the 'stop' method is called, target will be set to nil.The target is 'assigned', it is not 'retained'.*///执行目标CCNode    *m_pTarget;/** The action tag. An identifier of the action *///动作标记,默认为kCCActionTagInvalid(-1)int     m_nTag;
};/**
@brief Base class actions that do have a finite time duration.Possible actions:- An action with a duration of 0 seconds- An action with a duration of 35.5 secondsInfinite time actions are valid*/
//有限时间型动作(包括瞬时和持续型的)
class CC_DLL CCFiniteTimeAction : public CCAction
{
public:/***  @js ctor*///构造,内联实现,初始化执行时间为0,即瞬时执行完毕CCFiniteTimeAction(): m_fDuration(0){}/***  @js NA*  @lua NA*///析构virtual ~CCFiniteTimeAction(){}//! get duration in seconds of the action//获取执行动作的所需时间(单位:秒)inline float getDuration(void) { return m_fDuration; }//! set duration in seconds of the action//设置执行动作的所需时间(单位:秒)inline void setDuration(float duration) { m_fDuration = duration; }/** returns a reversed action *///返回一个反向动作virtual CCFiniteTimeAction* reverse(void);
protected://! duration in seconds//动作时长float m_fDuration;
};class CCActionInterval;
class CCRepeatForever;/** @brief Changes the speed of an action, making it take longer (speed>1)or less (speed<1) time.Useful to simulate 'slow motion' or 'fast forward' effect.@warning This action can't be Sequenceable because it is not an CCIntervalAction*/
//可变速度的动作
class CC_DLL CCSpeed : public CCAction
{
public:/***  @js ctor*///够着,内联实现CCSpeed(): m_fSpeed(0.0), m_pInnerAction(NULL){}/***  @js NA*  @lua NA*///析构virtual ~CCSpeed(void);//返回当前动作速度inline float getSpeed(void) { return m_fSpeed; }/** alter the speed of the inner function in runtime *///设置当前动作速度inline void setSpeed(float fSpeed) { m_fSpeed = fSpeed; }/** initializes the action *///初始动作bool initWithAction(CCActionInterval *pAction, float fSpeed);/***  @js NA*  @lua NA*///重载基类CCAtion的方法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 the action *///创建可变速度的动作static CCSpeed* create(CCActionInterval* pAction, float fSpeed);
protected://执行速度float m_fSpeed;//内部动作CCActionInterval *m_pInnerAction;
};/**
@brief CCFollow is an action that "follows" a node.Eg:
layer->runAction(CCFollow::actionWithTarget(hero));Instead of using CCCamera as a "follower", use this action instead.
@since v0.99.2
*/
//跟随动作
class CC_DLL CCFollow : public CCAction
{
public:/***  @js ctor*///构造,内联实现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){}/***  @js NA*  @lua NA*///析构virtual ~CCFollow(void);//获取是否有设置边界inline bool isBoundarySet(void) { return m_bBoundarySet; }/** alter behavior - turn on/off boundary *///设置是否有边界inline void setBoudarySet(bool bValue) { m_bBoundarySet = bValue; }/** initializes the action with a set boundary *///初始化执行目标bool initWithTarget(CCNode *pFollowedNode, const CCRect& rect = CCRectZero);/***  @js NA*  @lua NA*///重载基类CCAction的方法virtual CCObject* copyWithZone(CCZone *pZone);virtual void step(float dt);virtual bool isDone(void);virtual void stop(void);public:/** creates the action with a set boundary,It will work with no boundary if @param rect is equal to CCRectZero.*///创建一个跟随动作static CCFollow* create(CCNode *pFollowedNode, const CCRect& rect = CCRectZero);
protected:// node to follow//被跟随的节点CCNode *m_pobFollowedNode;// whether camera should be limited to certain area//是否现在摄像头的可见区域内bool m_bBoundarySet;// if screen size is bigger than the boundary - update not needed//边界是否只是一个点bool m_bBoundaryFullyCovered;// fast access to the screen dimensions//用于快速获取屏幕此存信息//保存屏幕一半的大小CCPoint m_obHalfScreenSize;//保存屏幕全部的大小CCPoint m_obFullScreenSize;// world boundaries//边界,左、右、上、下float m_fLeftBoundary;float m_fRightBoundary;float m_fTopBoundary;float m_fBottomBoundary;
};

实现:

CCAction::CCAction()
:m_pOriginalTarget(NULL)
,m_pTarget(NULL)
,m_nTag(kCCActionTagInvalid)
{
}CCAction::~CCAction()
{CCLOGINFO("cocos2d: deallocing");
}CCAction* CCAction::create()
{CCAction * pRet = new CCAction();//创建一个新动作实例pRet->autorelease();//放入内存回收池return pRet;
}const char* CCAction::description()
{return CCString::createWithFormat("<CCAction | Tag = %d>", m_nTag)->getCString();//返回绑定tag的描述
}CCObject* CCAction::copyWithZone(CCZone *pZone)
{CCZone *pNewZone = NULL;CCAction *pRet = NULL;if (pZone && pZone->m_pCopyObject)//如果传进来的参数的对象不为空{pRet = (CCAction*)(pZone->m_pCopyObject);//直接强转}else{//传进来的参数的对象为空pRet = new CCAction();//创建一个新的动作pNewZone = new CCZone(pRet);//将该动作的指针让CCZone保存(这个指针所指向的内存空间在这个方法返回前被释放了,也就是这句话是没用的)}//copy member datapRet->m_nTag = m_nTag;//设置动作的标记CC_SAFE_DELETE(pNewZone);//释放pNewZone的资源return pRet;
}void CCAction::startWithTarget(CCNode *aTarget)
{m_pOriginalTarget = m_pTarget = aTarget;//初始化原始执行目标和执行目标都为aTarget
}void CCAction::stop()
{m_pTarget = NULL;//将执行目标置空,动作结束后会调用它
}bool CCAction::isDone()
{return true;//动作执行完毕,直接返回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");
}//
// FiniteTimeAction
//CCFiniteTimeAction *CCFiniteTimeAction::reverse()
{//空实现,由持续性动作子类重载CCLOG("cocos2d: FiniteTimeAction#reverse: Implement me");return NULL;
}//
// Speed
//
CCSpeed::~CCSpeed()
{CC_SAFE_RELEASE(m_pInnerAction);//释放内部动作的资源
}CCSpeed* CCSpeed::create(CCActionInterval* pAction, float fSpeed)
{CCSpeed *pRet = new CCSpeed();//创建一个实例if (pRet && pRet->initWithAction(pAction, fSpeed))//初始化{pRet->autorelease();//放入内存回收池return pRet;}CC_SAFE_DELETE(pRet);//初始化失败,释放资源return NULL;
}bool CCSpeed::initWithAction(CCActionInterval *pAction, float fSpeed)
{CCAssert(pAction != NULL, "");pAction->retain();//引用计数加1,不然在下一帧动作执行时会出错m_pInnerAction = pAction;//设置内部动作m_fSpeed = fSpeed;//设置速度return true;
}CCObject *CCSpeed::copyWithZone(CCZone *pZone)
{//拷贝,过程跟CCAction类似CCZone* pNewZone = NULL;CCSpeed* pRet = NULL;if(pZone && pZone->m_pCopyObject) //in case of being called at sub class{pRet = (CCSpeed*)(pZone->m_pCopyObject);}else{pRet = new CCSpeed();pZone = pNewZone = new CCZone(pRet);}CCAction::copyWithZone(pZone);pRet->initWithAction( (CCActionInterval*)(m_pInnerAction->copy()->autorelease()) , m_fSpeed );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);//新的内部动作的引用计数加1}
}//
// Follow
//
CCFollow::~CCFollow()
{CC_SAFE_RELEASE(m_pobFollowedNode);
}CCFollow* CCFollow::create(CCNode *pFollowedNode, const CCRect& rect/* = CCRectZero*/)
{CCFollow *pRet = new CCFollow();//创建一个新的跟随动作if (pRet && pRet->initWithTarget(pFollowedNode, rect))//初始化{pRet->autorelease();//放入内存回收池return pRet;}CC_SAFE_DELETE(pRet);//初始化失败,释放资源return NULL;
}bool CCFollow::initWithTarget(CCNode *pFollowedNode, const CCRect& rect/* = CCRectZero*/)
{CCAssert(pFollowedNode != NULL, "");pFollowedNode->retain();//被跟随的节点引用计数加1m_pobFollowedNode = pFollowedNode;//设置跟随动作if (rect.equals(CCRectZero))//如果参数2无效,无边界{m_bBoundarySet = false;}else//参数2有效,有边界{m_bBoundarySet = true;}m_bBoundaryFullyCovered = false;CCSize winSize = CCDirector::sharedDirector()->getWinSize();//窗口大小m_obFullScreenSize = CCPointMake(winSize.width, winSize.height);//绑定全屏的大小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);//计算底部边界//假设屏幕的大小是480*320,设置的边界矩形是(0,0,720,480),那么计算后的结果是://最左边是(0 + 720) - 480 = 240,也就是可以向右移动最多240个单位,因为是向右移,所以取负数,即最终的结果是-240,代表最多向右移动240个单位,其余三个边界类似if(m_fRightBoundary < m_fLeftBoundary)//如果右边界大于左边界,即移动过头了,则将左右边界都设置为中间位置{// screen width is larger than world's boundary width//set both in the middle of the worldm_fRightBoundary = m_fLeftBoundary = (m_fLeftBoundary + m_fRightBoundary) / 2;}if(m_fTopBoundary < m_fBottomBoundary)//如果顶部边界大于底部边界,即移动过头了,则将顶部底部边界都设置为中间位置{// screen width is larger than world's boundary width//set both in the middle of the worldm_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)
{//拷贝,过程跟CCAction类似CCZone *pNewZone = NULL;CCFollow *pRet = NULL;if(pZone && pZone->m_pCopyObject) //in case of being called at sub class{pRet = (CCFollow*)(pZone->m_pCopyObject);}else{pRet = new CCFollow();pZone = pNewZone = new CCZone(pRet);}CCAction::copyWithZone(pZone);// copy member datapRet->m_nTag = m_nTag;CC_SAFE_DELETE(pNewZone);return pRet;
}void CCFollow::step(float dt)
{CC_UNUSED_PARAM(dt);if(m_bBoundarySet)//如果设置类边界限制{// whole map fits inside a single screen, no need to modify the position - unless map boundaries are increasedif(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() );//如果跟随节点不在运行中,返回true
}void CCFollow::stop()
{m_pTarget = NULL;//执行目标重置为空CCAction::stop();//调用父类的停止动作方法
}

实现都较为简单。

那么接下来便分析几个常见的动作的具体实现。

每个cocos2d-x的开发者都很清楚,如果某个节点要执行某个动作都是调用节点的runAction方法来执行动作的。所以节点肯定是封装了常用的动作的操作,隐藏了动作类的真正细节,其思路跟定时器的思路一模一样:

首先在构造中每个节点都会从导演中获取动作管理器供自己管理动作。

CCNode::CCNode(void)...
{// set default scheduler and actionManagerCCDirector *director = CCDirector::sharedDirector();m_pActionManager = director->getActionManager();m_pActionManager->retain();...
}

然后再通过这个管理器间接的调用动作的各个方法:

void CCNode::setActionManager(CCActionManager* actionManager)//设置一个新的动作管理器
{if( actionManager != m_pActionManager ) {//如果传进来新的动作管理器与当前的动作管理器不相等this->stopAllActions();//先停止所有的动作调用CC_SAFE_RETAIN(actionManager);//将新的管理器的引用计数加1,防止被回收池回收CC_SAFE_RELEASE(m_pActionManager);//删掉上一个的动作管理器m_pActionManager = actionManager;//设置新的动作管理器}
}CCActionManager* CCNode::getActionManager()
{return m_pActionManager;//返回当前的动作管理器
}CCAction * CCNode::runAction(CCAction* action)//运行动作
{CCAssert( action != NULL, "Argument must be non-nil");m_pActionManager->addAction(action, this, !m_bRunning);//将要运行的动作加到动作管理器中,动作管理器会在合适的时间调用它return action;//返回运行的动作
}void CCNode::stopAllActions()
{m_pActionManager->removeAllActionsFromTarget(this);//移除掉该节点的所有动作
}void CCNode::stopAction(CCAction* action)
{m_pActionManager->removeAction(action);//从动作管理器中移除该动作
}void CCNode::stopActionByTag(int tag)
{CCAssert( tag != kCCActionTagInvalid, "Invalid tag");m_pActionManager->removeActionByTag(tag, this);//根据标记位移除动作
}CCAction * CCNode::getActionByTag(int tag)
{CCAssert( tag != kCCActionTagInvalid, "Invalid tag");return m_pActionManager->getActionByTag(tag, this);//根据标记位获取动作
}unsigned int CCNode::numberOfRunningActions()
{return m_pActionManager->numberOfRunningActionsInTarget(this);//获取当前节点的正在运行的动作的数量
}

可以看到调用runAction其实就是将其加入到动作管理器中,然后动作管理器会自动的在合适的时间去调用它。

下面再看两个拥有大量子类的动作的实现:

1)瞬时动作:

class CC_DLL CCActionInstant : public CCFiniteTimeAction //<NSCopying>
{
public:/***  @js ctor*///构造CCActionInstant();/***  @js NA*  @lua NA*///析构virtual ~CCActionInstant(){}// CCAction methods/***  @js NA*  @lua NA*///重载父类方法virtual CCObject* copyWithZone(CCZone *pZone);virtual bool isDone(void);virtual void step(float dt);virtual void update(float time);//CCFiniteTimeAction methodvirtual CCFiniteTimeAction * reverse(void);
};
CCActionInstant::CCActionInstant() {
}CCObject * CCActionInstant::copyWithZone(CCZone *pZone) {//拷贝操作CCZone *pNewZone = NULL;CCActionInstant *pRet = NULL;if (pZone && pZone->m_pCopyObject) {pRet = (CCActionInstant*) (pZone->m_pCopyObject);} else {pRet = new CCActionInstant();pZone = pNewZone = new CCZone(pRet);}CCFiniteTimeAction::copyWithZone(pZone);CC_SAFE_DELETE(pNewZone);return pRet;
}bool CCActionInstant::isDone() {//是否执行完return true;
}void CCActionInstant::step(float dt) {CC_UNUSED_PARAM(dt);update(1);//执行调度
}void CCActionInstant::update(float time) {//空实现,子类实现CC_UNUSED_PARAM(time);// nothing
}CCFiniteTimeAction * CCActionInstant::reverse() {//返回一个复制动作return (CCFiniteTimeAction*) (copy()->autorelease());
}

以上是瞬时动作的基本实现,那么下面便用它的一个子类来分析如何去使用它。

class CC_DLL CCHide : public CCActionInstant//继承瞬时动作类CCActionInstant
{
public:/***  @js ctor*  @lua NA*///构造CCHide(){}/***  @js NA*  @lua NA*///析构virtual ~CCHide(){}//super methods/***  @lua NA*///重写父类方法virtual void update(float time);virtual CCFiniteTimeAction * reverse(void);/***  @js NA*  @lua NA*/virtual CCObject* copyWithZone(CCZone *pZone);
public:/** Allocates and initializes the action */static CCHide * create();//创建一个具体的瞬时动作类
};
CCHide * CCHide::create()
{CCHide *pRet = new CCHide();//创建一个即时隐藏节点的动作if (pRet) {pRet->autorelease();}return pRet;
}void CCHide::update(float time) {CC_UNUSED_PARAM(time);m_pTarget->setVisible(false);//设置节点为不可见
}CCFiniteTimeAction *CCHide::reverse() {//它的反动作是显示动作,也就是CCShow,所以之创建了一个CCShow类后直接返回return (CCFiniteTimeAction*) (CCShow::create());
}CCObject* CCHide::copyWithZone(CCZone *pZone) {//拷贝CCZone *pNewZone = NULL;CCHide *pRet = NULL;if (pZone && pZone->m_pCopyObject) {pRet = (CCHide*) (pZone->m_pCopyObject);} else {pRet = new CCHide();pZone = pNewZone = new CCZone(pRet);}CCActionInstant::copyWithZone(pZone);CC_SAFE_DELETE(pNewZone);return pRet;
}

可以看到如果要自定义一个瞬时动作,便可以按照以上的方式来实现,首先要继承瞬时动作类,然后从写拷贝方法,使拷贝出来的类确定是自定义的类,然后再update中实现自定义动作真正要执行的逻辑,如果有必要,也可以重写它的反向动作的实现。

2)持续动作:

class CC_DLL CCActionInterval : public CCFiniteTimeAction
{
public:/** how many seconds had elapsed since the actions started to run. *///返回记录了已执行的时间inline float getElapsed(void) { return m_elapsed; }/** initializes the action *///初始化动作bool initWithDuration(float d);/** returns true if the action has finished *///返回是否已执行完毕virtual bool isDone(void);/***  @js NA*  @lua NA*///重写父类方法virtual CCObject* copyWithZone(CCZone* pZone);virtual void step(float dt);virtual void startWithTarget(CCNode *pTarget);/** returns a reversed action */virtual CCActionInterval* reverse(void);public:/** creates the action */static CCActionInterval* create(float d);//创建持续动作public://extension in CCGridAction //与表格特效相关void setAmplitudeRate(float amp);float getAmplitudeRate(void);protected:float m_elapsed;//记录已累计的执行时间bool   m_bFirstTick;//是否是第一次运算
};
CCActionInterval* CCActionInterval::create(float d)
{CCActionInterval *pAction = new CCActionInterval();//创建新的持续动作类pAction->initWithDuration(d);//初始化pAction->autorelease();//放入内存回收池return pAction;
}bool CCActionInterval::initWithDuration(float d)
{m_fDuration = d;//记录时长// prevent division by 0// This comparison could be in step:, but it might decrease the performance// by 3% in heavy based action games.if (m_fDuration == 0)//如果时长为0,那么为了防止计算时除数为0,将时长设置一个值{m_fDuration = FLT_EPSILON;}m_elapsed = 0;m_bFirstTick = true;return true;
}CCObject* CCActionInterval::copyWithZone(CCZone *pZone)
{//拷贝操作CCZone* pNewZone = NULL;CCActionInterval* pCopy = NULL;if(pZone && pZone->m_pCopyObject) {//in case of being called at sub classpCopy = (CCActionInterval*)(pZone->m_pCopyObject);}else{pCopy = new CCActionInterval();pZone = pNewZone = new CCZone(pCopy);}CCFiniteTimeAction::copyWithZone(pZone);CC_SAFE_DELETE(pNewZone);pCopy->initWithDuration(m_fDuration);return pCopy;
}bool CCActionInterval::isDone(void)
{return m_elapsed >= m_fDuration;//如果以累积的时间长度大于执行时长,则返回true
}void CCActionInterval::step(float dt)
{if (m_bFirstTick)//如果是第一次执行运算{m_bFirstTick = false;//非第一次运算m_elapsed = 0;//将累计时间置0,开始计算}else{m_elapsed += dt;}//调用跟新函数,执行动作逻辑this->update(MAX (0,                                  // needed for rewind. elapsed could be negativeMIN(1, m_elapsed /MAX(m_fDuration, FLT_EPSILON)   // division by 0)));
}void CCActionInterval::setAmplitudeRate(float amp)
{CC_UNUSED_PARAM(amp);// Abstract class needs implementationCCAssert(0, "");
}float CCActionInterval::getAmplitudeRate(void)
{// Abstract class needs implementationCCAssert(0, "");return 0;
}void CCActionInterval::startWithTarget(CCNode *pTarget)
{CCFiniteTimeAction::startWithTarget(pTarget);//设置执行动作的目标节点m_elapsed = 0.0f;m_bFirstTick = true;
}CCActionInterval* CCActionInterval::reverse(void)
{//空实现,子类实现(有的子类有实现,有的没有,一般是相对型的动作有实现)CCAssert(false, "CCIntervalAction: reverse not implemented.");return NULL;
}

以上是持续动作的基本实现,那么下面便用它的两个子类来分析如何去使用它。

class CC_DLL CCRotateBy : public CCActionInterval
{
public:/** creates the action */static CCRotateBy* create(float fDuration, float fDeltaAngle);//创建旋转动作/** initializes the action */bool initWithDuration(float fDuration, float fDeltaAngle);//初始化static CCRotateBy* create(float fDuration, float fDeltaAngleX, float fDeltaAngleY);//创建旋转动作bool initWithDuration(float fDuration, float fDeltaAngleX, float fDeltaAngleY);//初始化/***  @js NA*  @lua NA*///重写父类构造方法virtual CCObject* copyWithZone(CCZone* pZone);virtual void startWithTarget(CCNode *pTarget);virtual void update(float time);virtual CCActionInterval* reverse(void);protected:float m_fAngleX;//x轴的旋转角度float m_fStartAngleX;//初始的x轴角度float m_fAngleY;//y轴的旋转角度float m_fStartAngleY;//初始的y轴角度
};
CCRotateBy* CCRotateBy::create(float fDuration, float fDeltaAngle)
{CCRotateBy *pRotateBy = new CCRotateBy();//创建一个新的动作pRotateBy->initWithDuration(fDuration, fDeltaAngle);//初始化pRotateBy->autorelease();//放入内存回收池return pRotateBy;
}bool CCRotateBy::initWithDuration(float fDuration, float fDeltaAngle)
{if (CCActionInterval::initWithDuration(fDuration))//调用父类的初始化时长{m_fAngleX = m_fAngleY = fDeltaAngle;//初始化要旋转的角度return true;}return false;
}CCRotateBy* CCRotateBy::create(float fDuration, float fDeltaAngleX, float fDeltaAngleY)
{CCRotateBy *pRotateBy = new CCRotateBy();//创建一个新的动作pRotateBy->initWithDuration(fDuration, fDeltaAngleX, fDeltaAngleY);//初始化pRotateBy->autorelease();//放入内存回收池return pRotateBy;
}bool CCRotateBy::initWithDuration(float fDuration, float fDeltaAngleX, float fDeltaAngleY)
{if (CCActionInterval::initWithDuration(fDuration))//调用父类的初始化时长{m_fAngleX = fDeltaAngleX;//初始化要旋转的x轴角度m_fAngleY = fDeltaAngleY;//初始化要旋转的y轴角度return true;//返回执行成功}//返回执行失败return false;
}CCObject* CCRotateBy::copyWithZone(CCZone *pZone)
{//拷贝操作CCZone* pNewZone = NULL;CCRotateBy* pCopy = NULL;if(pZone && pZone->m_pCopyObject) {//in case of being called at sub classpCopy = (CCRotateBy*)(pZone->m_pCopyObject);}else{pCopy = new CCRotateBy();pZone = pNewZone = new CCZone(pCopy);}CCActionInterval::copyWithZone(pZone);pCopy->initWithDuration(m_fDuration, m_fAngleX, m_fAngleY);CC_SAFE_DELETE(pNewZone);return pCopy;
}void CCRotateBy::startWithTarget(CCNode *pTarget)
{CCActionInterval::startWithTarget(pTarget);//设置目标节点m_fStartAngleX = pTarget->getRotationX();//初始化节点的初始x轴角度m_fStartAngleY = pTarget->getRotationY();//初始化节点的初始y轴角度
}void CCRotateBy::update(float time)
{// XXX: shall I add % 360if (m_pTarget)//如果存在执行节点{m_pTarget->setRotationX(m_fStartAngleX + m_fAngleX * time);//设置新的x轴角度m_pTarget->setRotationY(m_fStartAngleY + m_fAngleY * time);//设置新的y轴角度}
}CCActionInterval* CCRotateBy::reverse(void)
{return CCRotateBy::create(m_fDuration, -m_fAngleX, -m_fAngleY);//创建反向动作
}

实现一个持续动作的过程跟瞬时动作的过程类似,只不过这里在初始化的时候要调用父类的initWithDuration方法来设置调用时长,这里也调用父类的startWithTarget来绑定待执行的目标节点。

总结:由于动作管理器是受控于定时器,所以当控制定时器和动过的暂停和恢复工作时是统一的一个操作,没有任何的同步问题。

cocos2d-x中的动作分析相关推荐

  1. 动作分析在企业生产中具体起到什么作用

    动作分析是指在企业生产的正常操作过程中,通过观察数据总结出现场作业人员的赘余动作或者人体本身各种操作时动作的浪费情况.从而来将操作流程尽可能的简化,减少操作人员的疲劳度.制定出规范的操作流程,也为企业 ...

  2. CVPR 2020满分论文 | FineGym:面向细粒度动作分析的层级化高质量数据集

    机器之心发布 作者:邵典等 本文介绍了一个大规模.高质量.层级化标注的细粒度人体动作数据集「FineGym」,研究者来自香港中文大学,目前这项研究已被 CVPR 2020 接收为 oral 论文. 论 ...

  3. qtabwidget切换tab事件_某超超临界机组初压/限压切换过程中扰动原因分析

    严寒夕  浙江浙能台州第二发电有限责任公司 [摘要]某火电厂汽轮机在初压/限压切换过程中出现负荷瞬时上升问题.从初压/限压切换的逻辑及切换过程中主要参数的变化分析,确定原因为压力控制器指令上升瞬间和转 ...

  4. SparkSQL中UDAF案例分析

    SparkSQL中UDAF案例分析 1.统计单词的个数 package com.bynear.spark_sql; import org.apache.spark.sql.Row; import or ...

  5. 可靠性技术在医学仪器中的应用前景分析

    可靠性技术在医学仪器中的应用前景分析 1 引言 可靠性研究起源于武器系统,经过近半个世纪的发展,已成为一门遍及各学科各行业的工程技术学科,已经从电子产品的可靠性发展到机械和非电子产品的可靠性,从硬件的 ...

  6. 拉班动作分析在计量心理学与人工智能领域的应用

    摘要 本文立足于北京航空航天大学的<动作语言分析与应用>课程,从拉班动作分析的基本理论切入,总结出拉班动作分析理论在计量信息学和人工智能领域如何应用. [关键词]:拉班动作分析:计量心理学 ...

  7. 游戏策划笔记:角色动作分析

    游戏策划笔记:角色动作分析 今天对角色动作进行分析. 首先分解非战斗动作.这里的非战斗是指"不会因为hit box碰撞造成伤害"的动作. 非战斗状态的移动动作个人觉得需要和战斗状态 ...

  8. 关于那些羞羞的不可描述的动作分析,一个正经的机器学习项目

    译者 | czh912019784 出品 | AI科技大本营 现在,机器学习已经应用在各行各业中,开发工程师队伍越发壮大,其中有一类工程师的工作内容在外行人眼里似乎更"丰富多彩", ...

  9. 第02章 一个实例初识WorkBench分析流程-卡扣结构的动作分析

    第02章 一个实例初识WorkBench分析流程-卡扣结构的动作分析 1问题描述,关心的结果 2. 建模的介绍,模型改如何简化 3. 复杂特征的网格初步试划分 4. 网格再次的划分及调整 5. 材料的 ...

最新文章

  1. 江苏省对口单招计算机原理,江苏省对口单招计算机原理教案.doc
  2. PHP | 检查字符串中是否存在特定的单词/子字符串
  3. Github | Facebook人工智能实验室出品Pythia模块框架
  4. python socket 主动断开_Python网络编程tcp详解(基础篇十四)
  5. 浙江法院智能语音识别系统全面上线
  6. 李宏毅机器学习——结构化学习(一)
  7. 再也不怕重装eclipse! 让你的eclipse插件只下载一次
  8. Python算法、经典面试常见案例题大分享!!!
  9. python除法运算定律有哪些_运算定律有哪些
  10. 六个免费网站状态监控服务
  11. 请每一个孝顺的子女耐心的看下去!
  12. JVM常用参数与工具
  13. TC297 Memory Maps 内存映射
  14. python3 get爬取网页标题、链接和链接的数字ID
  15. python爬取新浪微博数据中心_Python爬虫框架Scrapy实战之批量抓取招聘信息
  16. 谏太宗十思疏 魏征(原文/译文)
  17. 华为mate40pro鸿蒙冷散热,华为mate40pro曝光,2K屏+麒麟9000+鸿蒙系统+双6400万,售价感人...
  18. 又一百度杰出科学家离职,百度研究院成中国AI的黄埔军校
  19. 如何获取(GET)一杯咖啡——星巴克REST案例分析
  20. PHP实验报告 点餐系统,点餐系统软件工程实验报告.doc

热门文章

  1. Python爬取冰冰的第一条vlog并进行数据分析
  2. 关于单元测试框架GoogleTest——参考《百度文库》、大量博客
  3. placeholder文字居中
  4. 隐藏域hidden的使用-HTML入门基础(024)
  5. 孩子学习机怎么买?3款爆款学习机详细测评
  6. 【办公-Word-VB】Word中VB控制ActiveX控件转换人民币大写并填充-源码带完整注释
  7. 桂林电子科技大学计算机学院二院,桂林电子科技大学计算机与信息安全学院来我院调研交流...
  8. Mybatis使用Druid连接池
  9. java linux pdf2swf_Linux PDF转换为SWF
  10. 声音识别入门经典模型实践-基于大数据训练CNN14网络实现食物咀嚼声音识别