##Director遊戲主循環顯示Node ###DisplayLinkDirector繼承Director override瞭如下方法node
virtual void mainLoop() override; virtual void setAnimationInterval(double value) override; virtual void startAnimation() override; virtual void stopAnimation() override;
mainLoop()
是遊戲主循環,經過setAnimationInterval
設置主循環每秒的調用次數。 mainLoop()
的代碼:數組
void DisplayLinkDirector::mainLoop() { if (_purgeDirectorInNextLoop) { _purgeDirectorInNextLoop = false; purgeDirector(); } else if (! _invalid) { drawScene(); // release the objects PoolManager::getInstance()->getCurrentPool()->clear(); } }
在drawScene()
方法中,會動用scene的visit
方法。ide
// draw the scene if (_runningScene) { _runningScene->visit(_renderer, Mat4::IDENTITY, false); _eventDispatcher->dispatchEvent(_eventAfterVisit); }
在visit
方法中,若是node不可見,直接返回,而後判斷Node是否須要變形。函數
bool dirty = _transformUpdated || parentTransformUpdated; if(dirty) _modelViewTransform = this->transform(parentTransform); _transformUpdated = false;
當調用node的setScale()、setPosition()等方法,_transformUpdated
爲true
。 這就是爲何cocos2d-x中設置了node的屬性,node會自動變化。oop
接着,Node會調用children的visit
方法。this
if(!_children.empty()) { sortAllChildren(); // draw children zOrder < 0 for( ; i < _children.size(); i++ ) { auto node = _children.at(i); if ( node && node->_localZOrder < 0 ) node->visit(renderer, _modelViewTransform, dirty); else break; } // self draw this->draw(renderer, _modelViewTransform, dirty); for(auto it=_children.cbegin()+i; it != _children.cend(); ++it) (*it)->visit(renderer, _modelViewTransform, dirty); } else { this->draw(renderer, _modelViewTransform, dirty); }
##ActionManager管理動做 Node裏面包含一個ActionManager的成員變量_actionManager
用於管理動做。spa
ActionManger提供addAction
等方法來管理動做,調用Node的runAction()
方法,其實是調用ActionManager的addAction
把動做放到ActionManger裏,並以node
爲key放到哈希表裏。線程
ActionManager是單例,在Director的init
函數裏,調用_scheduler->scheduleUpdate():指針
_actionManager = new ActionManager(); _scheduler->scheduleUpdate(_actionManager, Scheduler::PRIORITY_SYSTEM, false);
Scheduler每一幀都會動用ActionManager的update函數。code
在ActionManager的update函數裏,遍歷全部動做,並調用action->step(dt)
來設置動做往前一步。
用action->isDone()
來判斷動做時候執行完,若是執行完,就把動做從ActionManager中移除。
##Scheduler調度器 Scheduler用來定時觸發回調函數。
Node裏面也有一個Scheduler
的指針_scheduler
,調用Node的schedule
等方法實際上是調用Scheduler對應的方法,把Node和回調函數添加到Scheduler的鏈表裏。
Scheduler定了了兩個優先級,系統優先級和非系統最低優先級。
// Priority level reserved for system services. const int Scheduler::PRIORITY_SYSTEM = INT_MIN; // Minimum priority level for user scheduling. const int Scheduler::PRIORITY_NON_SYSTEM_MIN = PRIORITY_SYSTEM + 1;
在Scheduler的構造函數看到了做者的註釋:
// I don't expect to have more than 30 functions to all per frame
全部,每一幀不要超過30個定時回調函數。
在Director的drawScene
函數中會調用scheduler的update
方法。
if (! _paused) { _scheduler->update(_deltaTime); _eventDispatcher->dispatchEvent(_eventAfterUpdate); }
Scheduler的update
方法至關於Scheduler的main loop。
Scheduler要處理兩種select or
函數,一種是默認的update(float dt)
,另外一種是用戶自定義的,有時間間隔或者調用次數的selector
函數。
Scheduler用鏈表來存放第一種selector
,用哈希表存放第二種selector
。
默認的update selector
函數每一幀都調用一次,這裏主要分析一下用戶自定義的selector
函數。
下面的結構體用來保存一個自定義的selector
// Hash Element used for "selectors with interval" typedef struct _hashSelectorEntry { ccArray *timers; void *target; int timerIndex; Timer *currentTimer; bool currentTimerSalvaged; bool paused; UT_hash_handle hh; } tHashTimerEntry;
####分析一下Node的schedule函數
void Node::schedule(SEL_SCHEDULE selector, float interval, unsigned int repeat, float delay) { CCASSERT( selector, "Argument must be non-nil"); CCASSERT( interval >=0, "Argument must be positive"); _scheduler->schedule(selector, this, interval , repeat, delay, !_running); }
####再看看Scheduler對應的schedule方法
void Scheduler::schedule(SEL_SCHEDULE selector, Ref *target, float interval, unsigned int repeat, float delay, bool paused) { CCASSERT(target, "Argument target must be non-nullptr"); tHashTimerEntry *element = nullptr; HASH_FIND_PTR(_hashForTimers, &target, element); if (! element) { element = (tHashTimerEntry *)calloc(sizeof(*element), 1); element->target = target; HASH_ADD_PTR(_hashForTimers, target, element); // Is this the 1st element ? Then set the pause level to all the selectors of this target element->paused = paused; } else { CCASSERT(element->paused == paused, ""); } if (element->timers == nullptr) { element->timers = ccArrayNew(10); } else { for (int i = 0; i < element->timers->num; ++i) { TimerTargetSelector *timer = static_cast<TimerTargetSelector*>(element->timers->arr[i]); if (selector == timer->getSelector()) { CCLOG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->getInterval(), interval); timer->setInterval(interval); return; } } ccArrayEnsureExtraCapacity(element->timers, 1); } TimerTargetSelector *timer = new TimerTargetSelector(); timer->initWithSelector(this, selector, target, interval, repeat, delay); ccArrayAppendObject(element->timers, timer); timer->release(); }
一、從哈希表中以target
爲key
查找,若是沒有佔到對應的value
,就建立一個tHashTimerEntry
對象,而後添加到哈希表中。
二、若是element的timers數組爲空,就分配10個空間給timers。跳到4。
三、若是element的timers數組不爲空,遍歷timer數組,判斷是否已經存在,若是selector
已存在,跳到5。
四、新建一個TimerTargetSelector對象,添加到timers
數組裏面。
五、結束。
PS:這裏暫時不分析這個TimerTargetSelector類。
####最後就是看Scheduler的update函數了。 這裏只關注自定義selector
的部分代碼。
// main loop void Scheduler::update(float dt) { // Iterate over all the custom selectors for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; ) { _currentTarget = elt; _currentTargetSalvaged = false; if (! _currentTarget->paused) { // The 'timers' array may change while inside this loop for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex)) { elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]); elt->currentTimerSalvaged = false; elt->currentTimer->update(dt); if (elt->currentTimerSalvaged) { // The currentTimer told the remove itself. To prevent the timer from // accidentally deallocating itself before finishing its step, we retained // it. Now that step is done, it's safe to release it. elt->currentTimer->release(); } elt->currentTimer = nullptr; } } // elt, at this moment, is still valid // so it is safe to ask this here (issue #490) elt = (tHashTimerEntry *)elt->hh.next; // only delete currentTarget if no actions were scheduled during the cycle (issue #481) if (_currentTargetSalvaged && _currentTarget->timers->num == 0) { removeHashElement(_currentTarget); } } }
遍歷_hashForTimers
哈希表。而後依次調用timer的update
方法。在timer的update方法中,會累加dt,若是累加的時間大於以前設置的interval
,那麼就觸發設置的selector
方法。
void Timer::update(float dt) { // 省略 if (_runForever && !_useDelay) {//standard timer usage _elapsed += dt; if (_elapsed >= _interval) { trigger(); _elapsed = 0; } } // 省略 }
####trigger函數
void TimerTargetSelector::trigger() { if (_target && _selector) { (_target->*_selector)(_elapsed); } }
一個自定義schedule的流程就是這樣。
##最後總結一下
Cocos2d-x引擎是單線程的,Director類的mainLoop
是遊戲的主循環函數,每次循環,都會調用scene的visit
函數去顯示或者更新界面上的元素。
Scheduler類是Cocos2d-x的調度類,用來定時觸發回調函數,回調函數有默認的updateselector
函數和用戶自定義的selector
函數,mainLoop
中會調用Scheduler的update
函數,而後Scheduler再去調用其餘的selector
函數。
ActionManager類是管理遊戲中所有的動做。ActionManger是依賴Scheduler來進行動做的更新。