Cocos2d-x 3.1 Director ActionManger Scheduler初步分析

##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()等方法,_transformUpdatedtrue。 這就是爲何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;
  1. timers是定時器數組,target每調用一次schedule,那麼timers就會添加一個定時器。
  2. target 目標對象指針
  3. timerIndex timers的下標
  4. currentTimer 目前的timer
  5. currentTimerSalvaged 當前的timer是否保留,用來防止timer未完成工做但被刪除了。
  6. paused 是否暫停
  7. hh 哈希表節點

####分析一下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();
}

一、從哈希表中以targetkey查找,若是沒有佔到對應的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來進行動做的更新。

相關文章
相關標籤/搜索