溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》
  • 首頁 > 
  • 教程 > 
  • 開發(fā)技術(shù) > 
  • 20、Cocos2dx 3.0游戲開發(fā)找小三之Cocos2d-x的動作機(jī)制:嘻,善哉!技蓋至此乎?

20、Cocos2dx 3.0游戲開發(fā)找小三之Cocos2d-x的動作機(jī)制:嘻,善哉!技蓋至此乎?

發(fā)布時間:2020-07-22 12:55:39 來源:網(wǎng)絡(luò) 閱讀:614 作者:danielzzu 欄目:開發(fā)技術(shù)
重開發(fā)者的勞動成果,轉(zhuǎn)載的時候請務(wù)必注明出處:http://blog.csdn.net/haomengzhu/article/details/30480379

庖丁為文惠君解牛,手之所觸,肩之所倚,足之所履,膝之所踦, 砉然向然,奏刀騞然,莫不中音。
-----先秦·莊周《莊子·養(yǎng)生主》

20、Cocos2dx 3.0游戲開發(fā)找小三之Cocos2d-x的動作機(jī)制:嘻,善哉!技蓋至此乎?
20、Cocos2dx 3.0游戲開發(fā)找小三之Cocos2d-x的動作機(jī)制:嘻,善哉!技蓋至此乎?

當(dāng)學(xué)習(xí)整套動作的用法之后,我不禁感覺很好奇,究竟動作機(jī)制在 Cocos2d-x 里是如何實現(xiàn)的?
那我們就如同庖丁解牛一般,一起來一步步揭開動作機(jī)制的神秘面紗吧。

Action動作類的結(jié)構(gòu)
首先,來分析一下 Action 及其子類(主要是 FiniteTimeAction 及其子類)的一些成員函數(shù)和成員變量,
我們將通過這 些變量和函數(shù)來分析動作的基本流程。
從 Action 的定義中可以看到:
/**  @brief Base class for Action objects.  */ class CC_DLL Action : public Ref, public Clonable { public:     /// Default tag used for all the actions     static const int INVALID_TAG = -1;     /**      * @js NA      * @lua NA      */     virtual std::string description() const;   /** returns a clone of action */  virtual Action* clone() const = 0;      /** returns a new action that performs the exactly the reverse action */  virtual Action* reverse() const = 0;      //! return true if the action has finished     virtual bool isDone() const;      //! called before the action start. It will also set the target.     virtual void startWithTarget(Node *target);      /**      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();      //! 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 1      For 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 Node* getTarget() const { return _target; }     /** The action will modify the target properties. */     inline void setTarget(Node *target) { _target = target; }          inline Node* getOriginalTarget() const { return _originalTarget; }     /** 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 ActionManager, you should NOT call this method.     The target is 'assigned', it is not 'retained'.     @since v0.8.2     */     inline void setOriginalTarget(Node *originalTarget) { _originalTarget = originalTarget; }      inline int getTag() const { return _tag; }     inline void setTag(int tag) { _tag = tag; }  protected:     Action();     virtual ~Action();      Node *_originalTarget;     /** 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'.     */     Node *_target;     /** The action tag. An identifier of the action */     int _tag;  private:     CC_DISALLOW_COPY_AND_ASSIGN(Action); };

繼承自 Action 的 FiniteTimeAction 主要新增了一個用于保存該動作總的完成時間的成員變量: 
float _duration。
對于FiniteTimeAction 的兩個子類 ActionInstant 和 ActionInterval,前者沒有新增任何函數(shù)和變量,
而后者增加了兩個成員變量    float_elapsed;和bool_firstTick;
其中 _elapsed是從動作開始起逝去的時間,而 _firstTick是一個控制變量。

再來看下動作的更新
當(dāng)我們對 Node 調(diào)用 runAction(Action* action)方法時,
動作管理類 Action Manager(它是一個單例對象)會將新的 Action 和對應(yīng)的目標(biāo)節(jié)點添加到其管理的動作表中。
在 ActionManager 的 addAction 方法中,我們將動作添加到動作隊列之后,
就會對該 Action 調(diào)用成員函數(shù)startWithTarget(Node* pTarget)來綁定該動作的執(zhí)行者。
而在 Action 的子類中(如 ActionInterval),還初始化了一些參數(shù):
來看ActionInterval的startWithTarget方法:
virtual void startWithTarget(Node *target) override; void ActionInterval::startWithTarget(Node *target) {     FiniteTimeAction::startWithTarget(target);     _elapsed = 0.0f;     _firstTick = true; }

當(dāng)這些準(zhǔn)備工作都完成后,
每一幀刷新屏幕時,系統(tǒng)都會在 ActionManager 中遍歷其動作表中的每一個動作,
并調(diào)用該動作的 step(float dt)方法。
step 方法主要負(fù)責(zé)計算 _elapsed 的值,并調(diào)用 update(float time)方法,相關(guān)代碼如下
void ActionInterval::step(float dt) {     if (_firstTick)     {         _firstTick = false;         _elapsed = 0;     }     else     {         _elapsed += dt;     }          this->update(MAX (0, // needed for rewind. elapsed could be negative                       MIN(1, _elapsed /                           MAX(_duration, FLT_EPSILON) // division by 0                           )                       )                  ); }
傳入 update 方法的 time 參數(shù)表示逝去的時間與動作完成需要的時間的比值,
是介于 0 和 1 之間的一個數(shù),即動作完成的 百分比。 

ActionInterval并沒有進(jìn)一步實現(xiàn)update方法。 
下面我們繼續(xù)以繼承自ActionInterval的RotateTo動作的update方法為例,
分析 update 函數(shù)是如何實現(xiàn)的,其實現(xiàn)代碼如下:
void RotateTo::update(float time) {     if (_target)     {         _target->setRotationSkewX(_startAngleX + _diffAngleX * time);         _target->setRotationSkewY(_startAngleY + _diffAngleY * time);     } }

到這里,我們已經(jīng)能看出 Cocos2d-x 的動作機(jī)制的整個工作流程了。
在 RotateTo 中,最終完成的操作是修改目標(biāo)節(jié)點 的 Rotation 屬性值,更新該目標(biāo)節(jié)點的旋轉(zhuǎn)屬性值。 
最后,在每一幀刷新結(jié)束后,在 ActionManager 類的 update 方法中都會檢查動作隊列中每一個動作的 isDone 函數(shù)是否返回 true。如果返回 true,則動作已完成,將其從隊列中刪除。

isDone函數(shù)的代碼如下:
virtual bool isDone(void) const override; bool ActionInterval::isDone(void) const {     return _elapsed >= _duration; }

對于不同的動作類,雖然整體流程大致都是先調(diào)用 step 方法,然后按照各個動作的具體定義來更新目標(biāo)節(jié)點的屬性,但是不同動作的具體實現(xiàn)會有所不同。
例如,RepeatForever 動作的 isDone 函數(shù)始終返回 false,因為它是永遠(yuǎn)在執(zhí)行的動作;
又如 ActionInstant 及其子類的 step 函數(shù)中,向 update 傳遞的參數(shù)值始終是 1,因為瞬時動作會在下一幀刷新后完成,不需要多次執(zhí)行 update。

ActionManager的工作原理
了解了Action 在每一幀中如何被更新之后,我們不妨回頭看看動作管理類 ActionManager 的工作原理。
在對Director 進(jìn)行初始化時,也會對 ActionManager 進(jìn)行初始化。
下面的代碼是 Director::init()方法中的一部分:
    // action manager     //動作管理器     _actionManager = new ActionManager();     _scheduler->scheduleUpdate(_actionManager, Scheduler::PRIORITY_SYSTEM, false);

可以看到,在 ActionManager 被初始化后,馬上就調(diào)用了定時調(diào)度器 Scheduler 的 scheduleUpdate 方法。
在 scheduleUpdate函數(shù)中,我們?yōu)?ActionManager 注冊了一個定期更新的服務(wù),這意味著動作的調(diào)度與定時
器的調(diào)度都統(tǒng)一受到 Scheduler 的控制。
具體地說,我們可以方便地同時暫?;蚧謴?fù)定時器與動作的運(yùn)行,而不必考慮它們不同步的問題。
Scheduler 在每一幀更新時,都會觸發(fā) ActionManager 注冊的 update 方法。
從下面給出的 ActionManager::update方法的代碼可以看到,
ActionManager 在這時對每一個動作都進(jìn)行了更新。
與調(diào)度器 Scheduler 類似的一點是,為了防止動作調(diào)度過程中所遍歷的表被修改, 
Cocos2d-x 對動作的刪除進(jìn)行了仔細(xì)地處理, 保證任何情況下都可以安全地刪除動作:
// main loop void ActionManager::update(float dt) {     //枚舉動作表中的每一個目標(biāo)節(jié)點     for (tHashElement *elt = _targets; elt != nullptr; )     {         _currentTarget = elt;         _currentTargetSalvaged = false;          if (! _currentTarget->paused)         {             // The 'actions' MutableArray may change while inside this loop.             //枚舉目標(biāo)節(jié)點對應(yīng)的每一個動作             //actions 數(shù)組可能會在循環(huán)中被修改,因此需要謹(jǐn)慎處理             for (_currentTarget->actionIndex = 0; _currentTarget->actionIndex < _currentTarget->actions->num;                 _currentTarget->actionIndex++)             {                 _currentTarget->currentAction = (Action*)_currentTarget->actions->arr[_currentTarget->actionIndex];                 if (_currentTarget->currentAction == nullptr)                 {                     continue;                 }                  _currentTarget->currentActionSalvaged = false;                  //觸發(fā)動作更新                 _currentTarget->currentAction->step(dt);                  if (_currentTarget->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.                     _currentTarget->currentAction->release();                 } else                 if (_currentTarget->currentAction->isDone())                 {                     _currentTarget->currentAction->stop();                      Action *action = _currentTarget->currentAction;                     // Make currentAction nil to prevent removeAction from salvaging it.                     _currentTarget->currentAction = nullptr;                     removeAction(action);                 }                  _currentTarget->currentAction = nullptr;             }         }          // 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 (_currentTargetSalvaged && _currentTarget->actions->num == 0)         {             deleteHashElement(_currentTarget);         }     }      // issue #635     _currentTarget = nullptr; }


郝萌主友情提示:
庖丁解牛,神乎其技,對于學(xué)習(xí),要知其然,更要知其所以然、、、


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI