Cocos2dx源碼賞析(4)之Action動作
本篇,依然是通過閱讀源碼的方式來簡單賞析下Cocos2dx中Action動畫的執行過程。當然,這里也只是通過這種方式來總結下對Cocos2dx引擎的理解,還遠沒有達到舉一反三改造現有引擎或開發自己的游戲引擎的境界。但“千里之行,始於足下”,這點滴的積累都是更進一步的階梯。
傳送門:
Cocos2dx源碼賞析(1)之啟動流程與主循環
Cocos2dx源碼賞析(2)之渲染
Cocos2dx源碼賞析(3)之事件分發
1、Action說明
Action是作用於Node節點的所有動作的基類。通過下面這個類繼承層次關系圖,來了解下Action的具體作用:
(以上圖片來自Cocos官網)
下面簡述下Action子類的作用:
FiniteTimeAction:
有限時間動作類。包含即時動作和持續動作。
Follow:
跟隨節點的動作。可以使layer層跟隨一個節點的運動,而該節點絕對位置不變,layer層作為背景以相反方向移動。
Speed:
用來線性的改變一個action動作的運行速度。它包裝一個ActionInterval對象,當speed大於1時,動作持續的時間更長;當speed小於1時,動作持續的時間更短。
ActionInstant:
即時動作。繼承自FiniteTimeAction,顧名思義,繼承自該類的動作是在一幀內執行結束的動作。
ActionInterval:
持續動作。繼承自FiniteTimeAction,繼承自該類的動作執行過程會有開始時間和完成事件或者說有一定的持續時間(duration)。當然,它們也可以正常運行、逆向運行,也可以變速運行。
CallFunc:
用於執行回調的動作。即執行該動作時,會調用作參數傳遞過去的函數。具體的實現有分CallFunc和CallFuncN,CallFunc是執行不帶參數的函數,CallFuncN是執行一個帶Node參數的函數。
FlipX和FlipY:
將精靈沿X軸和Y軸翻轉。與設置精靈的FlipX和FlipY屬性相同,包裝成動作是為了便於與其他動作進行組合。
Hide和Show:
隱藏和顯示節點。作用與設置節點的visible屬性作用一樣。
Place:
將節點放置到某個指定位置,與設置節點的position屬性相同。
RemoveSelf:
移除節點。與調用節點的removeFromParentAndCleanup方法作用相同。
ReuseGrid:
重復網格動作。和GridAction關聯使用。
StopGrid:
停止網格動作。和GridAction關聯使用。
ToggleVisibility:
切換節點的可視屬性。
CCBSetSpriteFrame:
用坐標創建一個位置動作,用於設置Sprite的位置。
CCBSoundEffect:
可用來播放聲音效果。
AccelAmplitude和AccelDeccelAmplitude:
振幅動作。
ActionCamera:
攝像機動作。ActionCamera,不能直接調用,因為ActionCamera沒有重寫update方法,只能使用它的子類OrbitCamera。
ActionEase:
用於實現動作的速度由快到慢、速度隨事件改變的勻速運動。該動作又包含5類運動:
(1)指數緩沖:EaseExponentialIn、EaseExponentialOut、EaseExponentialInOut
(2)Sine緩沖:EaseSineIn、EaseSineOut、EaseSineInOut
(3)彈性緩沖:EaseElasticIn、EaseElasticOut、EaseElasticInOut
(4)跳躍緩沖:EaseBounceIn、EaseBounceOut、EaseBounceInOut
(5)回震緩沖:EaseBackIn、EaseBackOut、EaseBackInOut
其中,每類動作中都有In、Out和InOut三種運動方式:
In表示開始的時候加速
Out表示結束的時候加速
InOut表示開始和結束的時候加速
ActionTween:
補間動作。作用的目標必須繼承ActionTweenDelegate並實現updateTweenAction方法。例如:漸變、縮放、位移、旋轉等改變的。
Animate:
序列幀動畫動作。
BezierBy和BezierTo:
貝塞爾曲線動作。其中By是移動的間隔,To是移動到指定位置。
Blink:
閃爍動作。
CardinalSplineBy和CardinalSplineTo:
曲線路徑動作。參考Cardinal spline wikipedia
DeccelAmplitude:
振幅動作。
DelayTime:
延時動作。只能在復合動作內使用
FadeIn, FadeOut和FateTo:
淡入淡出效果和透明變化效果。即漸變動作。FadeIn的反轉動作(reverse)是FadeOut,FadeOut的反轉動作(reverse)是FadeIn,FateTo不支持反轉動作(reverse)。
GridAction:
網格(grid)動作的基類。
JumpBy和JumpTo:
使節點以一定的軌跡跳躍到指定位置。其中By是移動的間隔,To是移動到指定位置。
MoveBy和MoveTo:
移動動作。使節點做直線運動,設置了動作時間和終點位置,在規定時間內會移動到終點。
ProgressFromTo:
從一個百分比到另一個百分比的動作。
ProgressTo:
百分比進度。
Repeat和RepeatForever:
重復執行動作。RepeatForever是不停的重復。
ReverseTime:
反轉動作。
RotateBy和RotateTo:
旋轉動作。保證周長相等。
ScaleBy和ScaleTo:
縮放動作。
Sequence:
順序執行一系列動作。
SkewBy和SkewTo:
傾斜動作。保證面積相等。
Spawn:
同時執行多個動畫。
TargetedAction:
給動作指定一個運行的目標上。
TintBy和TintTo:
顏色漸變動作。
CCBRotateXTo、CCBRotateYTo、CCBRotateTo:
旋轉動作。
2、Action調用過程
在Cocos2dx中所有的Action動作的管理都是由ActionManager類來管理的。那么,ActionManager的初始化實在CCDirector類中:
bool Director::init(void)
{
_actionManager = new (std::nothrow) ActionManager();
_scheduler->scheduleUpdate(_actionManager, Scheduler::PRIORITY_SYSTEM, false);
}
這里,開啟了個定時器,來不停的更新ActionManager的邏輯:
void ActionManager::update(float dt)
{
for (tHashElement *elt = _targets; elt != nullptr; )
{
_currentTarget = elt;
_currentTargetSalvaged = false;
if (! _currentTarget->paused)
{
for (_currentTarget->actionIndex = 0; _currentTarget->actionIndex < _currentTarget->actions->num;
_currentTarget->actionIndex++)
{
_currentTarget->currentAction = static_cast<Action*>(_currentTarget->actions->arr[_currentTarget->actionIndex]);
if (_currentTarget->currentAction == nullptr)
{
continue;
}
_currentTarget->currentActionSalvaged = false;
_currentTarget->currentAction->step(dt);
if (_currentTarget->currentActionSalvaged)
{
_currentTarget->currentAction->release();
} else
if (_currentTarget->currentAction->isDone())
{
_currentTarget->currentAction->stop();
Action *action = _currentTarget->currentAction;
_currentTarget->currentAction = nullptr;
removeAction(action);
}
_currentTarget->currentAction = nullptr;
}
}
elt = (tHashElement*)(elt->hh.next);
if (_currentTargetSalvaged && _currentTarget->actions->num == 0)
{
deleteHashElement(_currentTarget);
}
else if (_currentTarget->target->getReferenceCount() == 1)
{
deleteHashElement(_currentTarget);
}
}
_currentTarget = nullptr;
}
這里會遍歷所有的Action,滿足條件的會執行Action的step方法,由於,幾乎所有的Action動作實例都是繼承自ActionInstant和ActionInterval,所以,這里直接看這兩個類的step方法的實現。
void ActionInstant::step(float /*dt*/)
{
float updateDt = 1;
#if CC_ENABLE_SCRIPT_BINDING
if (_scriptType == kScriptTypeJavascript)
{
if (ScriptEngineManager::sendActionEventToJS(this, kActionUpdate, (void *)&updateDt))
return;
}
#endif
update(updateDt);
}
void ActionInterval::step(float dt)
{
if (_firstTick)
{
_firstTick = false;
_elapsed = 0;
}
else
{
_elapsed += dt;
}
float updateDt = MAX (0,MIN(1, _elapsed / _duration));
if (sendUpdateEventToScript(updateDt, this)) return;
this->update(updateDt);
}
可以發現,在step方法中,會調用Action的update方法來執行具體的更新邏輯。這個update方法會交給Action的實例實現。
當要執行一個Action動作時,一般會調用Node的runAction方法:
Action * Node::runAction(Action* action)
{
CCASSERT( action != nullptr, "Argument must be non-nil");
_actionManager->addAction(action, this, !_running);
return action;
}
即這里會將實際要執行的Action實例添加到ActionManager中:
void ActionManager::addAction(Action *action, Node *target, bool paused)
{
CCASSERT(action != nullptr, "action can't be nullptr!");
CCASSERT(target != nullptr, "target can't be nullptr!");
if(action == nullptr || target == nullptr)
return;
tHashElement *element = nullptr;
Ref *tmp = target;
HASH_FIND_PTR(_targets, &tmp, element);
if (! element)
{
element = (tHashElement*)calloc(sizeof(*element), 1);
element->paused = paused;
target->retain();
element->target = target;
HASH_ADD_PTR(_targets, target, element);
}
actionAllocWithHashElement(element);
CCASSERT(! ccArrayContainsObject(element->actions, action), "action already be added!");
ccArrayAppendObject(element->actions, action);
action->startWithTarget(target);
}
以上,簡單梳理了下Cocos2dx的Action動作的執行過程。從源碼的角度來了解了下這種方式的動畫是如何實現的,真正深入到源碼的每個細節還是能學到不少東西的。