亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》
  • 首頁 > 
  • 教程 > 
  • 開發技術 > 
  • 18、Cocos2dx 3.0游戲開發找小三之cocos2d-x,請問你是怎么調度的咩

18、Cocos2dx 3.0游戲開發找小三之cocos2d-x,請問你是怎么調度的咩

發布時間:2020-06-28 16:52:04 來源:網絡 閱讀:323 作者:danielzzu 欄目:開發技術
重開發者的勞動成果,轉載的時候請務必注明出處:http://blog.csdn.net/haomengzhu/article/details/30478251

Cocos2d 的一大特色就是提供了事件驅動的游戲框架,
引擎會在合適的時候調用事件處理函數,我們只需要在函數中添加對各種游戲事件的處理,
就可以完成一個完整的游戲了。
例如,為了實現游戲的動態變化,Cocos2d 提供了兩種定時器事件;
為了響應用戶輸入,Cocos2d 提供了觸摸事件和傳感器事件;
此外,Cocos2d 還提供了一系列控制程序生命周期的事件。

Cocos2d 的調度原理管理著所有的事件,Cocos2d 已經為我們隱藏了游戲主循環的實現。

首先來看看游戲實現的原理:
游戲乃至圖形界面的本質是不斷地繪圖,然而繪圖并不是隨意的,任何游戲都需要遵循一定的規則來呈現出來,這些規則就體現為游戲邏輯。游戲邏輯會控制游戲內容,使其根據用戶輸入和時間流逝而改變。
因此,游戲可以抽象為不斷地重復以下動作:
處理用戶輸入 ;
處理定時事件 ;
繪圖 ;

游戲主循環就是這樣的一個循環,它會反復執行以上動作,保持游戲進行下去,直到玩家退出游戲。
在 Cocos2d-x 3.0 中,以上的動作包含在 Director 的某個方法之中,而引擎會根據不同的平臺設法使系統不斷地調用這個方法,從而完成了游戲主循環。

在cocos2d-x 3.0中,Director 包含一個管理引擎邏輯的方法,它就是 Director::mainLoop()方法,
這個方法負責調用定時器,繪圖,發送全局通知,并處理內存回收池。
該方法按幀調用, 每幀調用一次,而幀間間隔取決于兩個因素,一個是預設的幀率,默認為 60 幀每秒;
另一個是每幀的計算量大小。
當邏輯 處理與繪圖計算量過大時,設備無法完成每秒 60 次繪制,此時幀率就會降低。 

mainLoop()方法會被定時調用,然而在不同的平臺下它的調用者不同。
通常 Application 類負責處理平臺相關的任務,其中就包含了對 mainLoop()的調用;
不同的平臺具體實現也不相同,具體可參考cocos\2d\platform目錄;

mainLoop()方法是定義在 Director 中的抽象方法,它的實現位于同一個文件中的 DisplayLinkDirector類中;
virtual void mainLoop() = 0;
具體實現是:
void DisplayLinkDirector::mainLoop() {     if (_purgeDirectorInNextLoop)     {         _purgeDirectorInNextLoop = false;         purgeDirector();     }     else if (! _invalid)     {         drawScene();               // release the objects         //釋放資源對象         PoolManager::getInstance()->getCurrentPool()->clear();     } }
上述代碼主要包含如下 3 個步驟。
1、判斷是否需要釋放 Director,如果需要,則刪除 Director 占用的資源。通常,游戲結束時才會執行這個步驟。
2、調用 drawScene()方法,繪制當前場景并進行其他必要的處理。
3、彈出自動回收池,使得這一幀被放入自動回收池的對象全部釋放。

mainLoop()把內存管理以外的操作都交給了 drawScene()方法,因此關鍵的步驟都在 drawScene()方法之中;
再來看看drawScene方法:
void Director::drawScene() {     // calculate "global" dt    //計算全局幀間時間差 dt     calculateDeltaTime();          // skip one flame when _deltaTime equal to zero.     if(_deltaTime < FLT_EPSILON)     {         return;     }      if (_openGLView)     {         _openGLView->pollInputEvents();     }      //tick before glClear: issue #533     if (! _paused)     {         _scheduler->update(_deltaTime);         _eventDispatcher->dispatchEvent(_eventAfterUpdate);     }      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);      /* to avoid flickr, nextScene MUST be here: after tick and before draw.      XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */     if (_nextScene)     {         setNextScene();     }      kmGLPushMatrix();      // global identity matrix is needed... come on kazmath!     kmMat4 identity;     kmMat4Identity(&identity);      // draw the scene    //繪制場景     if (_runningScene)     {         _runningScene->visit(_renderer, identity, false);         _eventDispatcher->dispatchEvent(_eventAfterVisit);     }      // draw the notifications node    //處理通知節點     if (_notificationNode)     {         _notificationNode->visit(_renderer, identity, false);     }      if (_displayStats)     {         showStats();     }      _renderer->render();     _eventDispatcher->dispatchEvent(_eventAfterDraw);      kmGLPopMatrix();      _totalFrames++;      // swap buffers    //交換緩沖區     if (_openGLView)     {         _openGLView->swapBuffers();     }      if (_displayStats)     {         calculateMPF();     } }
可以分析出:
在主循環中,我們主要進行了以下 3 個操作。
1、調用了定時調度器的 update 方法,引發定時器事件。
2、如果場景需要被切換,則調用 setNextStage 方法,在顯示場景前切換場景。
3、調用當前場景的 visit 方法,繪制當前場景。

在游戲主循環 drawScene 方法中,我們可以看到每一幀引擎都會調用 _scheduler的 update 方法。
【Scheduler *_scheduler;】_scheduler 是 Scheduler 類型的對象,是一個定時調度器。
所謂定時調度器,就是一個管理所有節點定時器的對象, 
它負責記錄定時器,并在合適的時間觸發定時事件。

再來分析一下定時器的情況:
Cocos2d-x 提供了兩種定時器,分別是:
update 定時器,每一幀都被觸發,使用 scheduleUpdate 方法來啟用;
schedule 定時器,可以設置觸發的間隔,使用 schedule 方法來啟用。
看下Node中的實現:
void Node::scheduleUpdateWithPriority(int priority) {     _scheduler->scheduleUpdate(this, priority, !_running); }  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是 Scheduler 對象。
可以看到,這兩個方法的內部除去檢查參數是否合法,只是調用了 Scheduler提供的方法。
換句話說,Node 提供的定時器只是對 Scheduler 的包裝而已。
不僅這兩個方法如此,其他定時器相關的方法也都是這樣。

Scheduler的分析
經過上面的分析,我們已經知道 Node 提供的定時器不是由它本身而是由 Scheduler管理的。
因此,我們把注意力轉移到定時調度器上。
顯而易見,定時調度器應該對每一個節點維護一個定時器列表,在恰當的時候就會觸發其定時事件。

Scheduler的主要成員請查看:cocos\2d\CCScheduler.h

為了注冊一個定時器,開發者只要調用調度器提供的方法即可。
同時調度器還提供了一系列對定時器的控制接口,例如暫停和恢復定時器。
在調度器內部維護了多個容器,用于記錄每個節點注冊的定時器;
同時,調度器會接受其他組件(通常 與平臺相關)的定時調用,隨著系統時間的改變驅動調度器。 

調度器可以隨時增刪或修改被注冊的定時器。
具體來看,調度器將 update 定時器與普通定時器分別處理:
當某個節點注冊 update 定時器時,調度器就會把節點添加到 Updates 容器中,
即struct _hashUpdateEntry *_hashForUpdates里面;
為了提高調度器效率,Cocos2d-x 使用了散列表與鏈表結合的方式來保存定時器信息;
當某個節點注冊普通定時器時,調度器會把回調函數和其他信息保存到 Selectors 散列表中,
即struct _hashSelectorEntry *_hashForTimers里面。

在游戲主循環中,我們已經見到了 update 方法。
可以看到,游戲主循環會不停地調用 update 方法。
該方法包含一個實型參數,表示兩次調用的時間間隔。
在該方法中,引擎會利用兩次調用的間隔來計算何時觸發定時器。
我們再來分析下 update 方法的工作流程
// main loop void Scheduler::update(float dt) {     _updateHashLocked = true;      //a.預處理     if (_timeScale != 1.0f)     {         dt *= _timeScale;     }      //     // Selector callbacks     //      // Iterate over all the Updates' selectors     //b.枚舉所有的 update 定時器     tListEntry *entry, *tmp;      // updates with priority < 0     //優先級小于 0 的定時器     DL_FOREACH_SAFE(_updatesNegList, entry, tmp)     {         if ((! entry->paused) && (! entry->markedForDeletion))         {             entry->callback(dt);         }     }      // updates with priority == 0     //優先級等于 0 的定時器     DL_FOREACH_SAFE(_updates0List, entry, tmp)     {         if ((! entry->paused) && (! entry->markedForDeletion))         {             entry->callback(dt);         }     }      // updates with priority > 0     //優先級大于 0 的定時器     DL_FOREACH_SAFE(_updatesPosList, entry, tmp)     {         if ((! entry->paused) && (! entry->markedForDeletion))         {             entry->callback(dt);         }     }      // Iterate over all the custom selectors     //c.枚舉所有的普通定時器     for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; )     {         _currentTarget = elt;         _currentTargetSalvaged = false;          if (! _currentTarget->paused)         {             // The 'timers' array may change while inside this loop             //枚舉此節點中的所有定時器            //timers 數組可能在循環中改變,因此在此處需要小心處理             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);         }     }      // delete all updates that are marked for deletion     // updates with priority < 0     //d.清理所有被標記了刪除記號的 update 方法    //優先級小于 0 的定時器     DL_FOREACH_SAFE(_updatesNegList, entry, tmp)     {         if (entry->markedForDeletion)         {             this->removeUpdateFromHash(entry);         }     }      // updates with priority == 0     //優先級等于 0 的定時器     DL_FOREACH_SAFE(_updates0List, entry, tmp)     {         if (entry->markedForDeletion)         {             this->removeUpdateFromHash(entry);         }     }      // updates with priority > 0     //優先級大于 0 的定時器     DL_FOREACH_SAFE(_updatesPosList, entry, tmp)     {         if (entry->markedForDeletion)         {             this->removeUpdateFromHash(entry);         }     }      _updateHashLocked = false;     _currentTarget = nullptr;  #if CC_ENABLE_SCRIPT_BINDING     //     // Script callbacks     //      // Iterate over all the script callbacks     //e.處理腳本引擎相關的事件     if (!_scriptHandlerEntries.empty())     {         for (auto i = _scriptHandlerEntries.size() - 1; i >= 0; i--)         {             SchedulerScriptHandlerEntry* eachEntry = _scriptHandlerEntries.at(i);             if (eachEntry->isMarkedForDeletion())             {                 _scriptHandlerEntries.erase(i);             }             else if (!eachEntry->isPaused())             {                 eachEntry->getTimer()->update(dt);             }         }     } #endif     //     // Functions allocated from another thread     //      // Testing size is faster than locking / unlocking.     // And almost never there will be functions scheduled to be called.     if( !_functionsToPerform.empty() ) {         _performMutex.lock();         // fixed #4123: Save the callback functions, they must be invoked after '_performMutex.unlock()', otherwise if new functions are added in callback, it will cause thread deadlock.         auto temp = _functionsToPerform;         _functionsToPerform.clear();         _performMutex.unlock();         for( const auto &function : temp ) {             function();         }              } }
借助注釋,能夠看出 update 方法的流程大致如下所示。
1、參數 dt 乘以一個縮放系數,以改變游戲全局的速度,其中縮放系數可以由 Scheduler的TimeScale屬性設置。
2、分別枚舉優先級小于 0、等于 0、大于 0 的 update 定時器。
如果定時器沒有暫停,也沒有被標記為即將刪除,則觸發定時器。
3、枚舉所有注冊過普通定時器的節點,再枚舉該節點的定時器,調用定時器的更新方法,從而決定是否觸發該定時器。
4、再次枚舉優先級小于 0、等于 0、大于 0 的 update 定時器,移除前幾個步驟中被標記了刪除記號的定時器。
我們暫不關心腳本引擎相關的處理。 

對于 update 定時器來說,每一節點只可能注冊一個定時器,
因此調度器中存儲定時器數據的結構體tListEntry *entry主要保存了注冊者與優先級。
對于普通定時器來說,每一個節點可以注冊多個定時器,
引擎使用回調函數(選擇器)來區分同一節點下注冊的不同定時器。
調度器為每一個定時器創建了一個 Timer 對象,
它記錄了定時器的目標、回調函數、觸發周期、重復觸發還是僅觸發一次等屬性。

Timer 也提供了 update 方法,它的名字和參數都與 Scheduler 的 update 方法一樣,
而且它們也都需要被定時調用。
同的是,Timer 的 update 方法會把每一次調用時接收的時間間隔 dt 積累下來,
如果經歷的時間達到了周期就會引發定時器的定時事件。 
第一次引發了定時事件后,如果是僅觸發一次的定時器,
則 update 方法會中止,否則定時器會重新計時,從而反復地觸發定時事件。

來看看Timer的update方法:
void Timer::update(float dt) {     if (_elapsed == -1)     {         _elapsed = 0;         _timesExecuted = 0;     }     else     {         if (_runForever && !_useDelay)         {//standard timer usage             _elapsed += dt;             if (_elapsed >= _interval)             {                 trigger();                  _elapsed = 0;             }         }             else         {//advanced usage             _elapsed += dt;             if (_useDelay)             {                 if( _elapsed >= _delay )                 {                     trigger();                                          _elapsed = _elapsed - _delay;                     _timesExecuted += 1;                     _useDelay = false;                 }             }             else             {                 if (_elapsed >= _interval)                 {                     trigger();                                          _elapsed = 0;                     _timesExecuted += 1;                  }             }              if (!_runForever && _timesExecuted > _repeat)             { //unschedule timer                 cancel();             }         }     } }

再次回到 Scheduler 的 update 方法上來。
在步驟 c 中,程序首先枚舉了每一個注冊過定時器的對象,然后再枚舉對象中定時 器對應的 Timer 對象,
調用 Timer 對象的 update 方法來更新定時器狀態,以便觸發定時事件。

至此,我們可以看到事件驅動的普通定時器調用順序為:
系統的時間事件驅動游戲主循環,游戲主循環調用 Scheduler 的 update 方法,Scheduler 調用普通定時器對應的 Timer 對象的 update 方法,Timer 類的 update 方法調用定時器 對應的回調函數。

對于 update 定時器,調用順序更為簡單,因此前面僅列出了普通定時器的調用順序。 
同時,我們也可以看到,在定時器被觸發的時刻,Scheduler 類的 update 方法正在迭代之中,
開發者完全可能在定時器 事件中啟用或停止其他定時器。
18、Cocos2dx 3.0游戲開發找小三之cocos2d-x,請問你是怎么調度的咩
18、Cocos2dx 3.0游戲開發找小三之cocos2d-x,請問你是怎么調度的咩
不過,這么做會導致 update 方法中的迭代被破壞。
Cocos2d-x 的設計已經考慮到了這個問題,采用了一些技巧避免迭代被破壞。
例如,update 定時器被刪除時,不會直接刪除,而是標記為將要刪除,在定時器迭代完畢后再清理被標記的定時器,這樣即可保證迭代的正確性。

Cocos2d-x 的設計使得很多離散在各處的代碼通過事件聯系起來,在每一幀中起作用。
基于事件驅動的游戲框架易于掌握,使用靈活,而且所有事件串行地在同一線程中執行,不會出現線程同步的問題。

可以看到,Cocos2d-x是多么的強大!!!
小伙伴們,知道cococs2d-x是怎么調度了咩!!
咩!!

郝萌主友情提示:
多看看源碼,你就能更了解cocos2d-x了、、、

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

库尔勒市| 玉田县| 台中县| 潢川县| 禄丰县| 璧山县| 沾化县| 承德市| 隆昌县| 浏阳市| 富裕县| 米易县| 泰兴市| 北海市| 彭水| 陈巴尔虎旗| 乌什县| 阜城县| 高州市| 汉中市| 保德县| 农安县| 九台市| 南京市| 澳门| 班戈县| 娄烦县| 夏河县| 泾阳县| 西昌市| 南通市| 山东| 理塘县| 商水县| 龙江县| 芷江| 广宁县| 尼勒克县| 新泰市| 策勒县| 化隆|