日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

18、Cocos2dx 3.0游戏开发找小三之cocos2d-x,请问你是怎么调度的咩

發(fā)布時(shí)間:2025/3/17 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 18、Cocos2dx 3.0游戏开发找小三之cocos2d-x,请问你是怎么调度的咩 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
重開發(fā)者的勞動(dòng)成果,轉(zhuǎn)載的時(shí)候請務(wù)必注明出處:http://blog.csdn.net/haomengzhu/article/details/30478251

Cocos2d 的一大特色就是提供了事件驅(qū)動(dòng)的游戲框架, 引擎會(huì)在合適的時(shí)候調(diào)用事件處理函數(shù),我們只需要在函數(shù)中添加對各種游戲事件的處理, 就可以完成一個(gè)完整的游戲了。 例如,為了實(shí)現(xiàn)游戲的動(dòng)態(tài)變化,Cocos2d 提供了兩種定時(shí)器事件; 為了響應(yīng)用戶輸入,Cocos2d 提供了觸摸事件和傳感器事件; 此外,Cocos2d 還提供了一系列控制程序生命周期的事件。
Cocos2d 的調(diào)度原理管理著所有的事件,Cocos2d 已經(jīng)為我們隱藏了游戲主循環(huán)的實(shí)現(xiàn)。
首先來看看游戲?qū)崿F(xiàn)的原理: 游戲乃至圖形界面的本質(zhì)是不斷地繪圖,然而繪圖并不是隨意的,任何游戲都需要遵循一定的規(guī)則來呈現(xiàn)出來,這些規(guī)則就體現(xiàn)為游戲邏輯。游戲邏輯會(huì)控制游戲內(nèi)容,使其根據(jù)用戶輸入和時(shí)間流逝而改變。 因此,游戲可以抽象為不斷地重復(fù)以下動(dòng)作: 處理用戶輸入 ; 處理定時(shí)事件 ; 繪圖 ;
游戲主循環(huán)就是這樣的一個(gè)循環(huán),它會(huì)反復(fù)執(zhí)行以上動(dòng)作,保持游戲進(jìn)行下去,直到玩家退出游戲。 在 Cocos2d-x 3.0 中,以上的動(dòng)作包含在 Director 的某個(gè)方法之中,而引擎會(huì)根據(jù)不同的平臺(tái)設(shè)法使系統(tǒng)不斷地調(diào)用這個(gè)方法,從而完成了游戲主循環(huán)。
在cocos2d-x 3.0中,Director 包含一個(gè)管理引擎邏輯的方法,它就是 Director::mainLoop()方法, 這個(gè)方法負(fù)責(zé)調(diào)用定時(shí)器,繪圖,發(fā)送全局通知,并處理內(nèi)存回收池。 該方法按幀調(diào)用,?每幀調(diào)用一次,而幀間間隔取決于兩個(gè)因素,一個(gè)是預(yù)設(shè)的幀率,默認(rèn)為 60 幀每秒; 另一個(gè)是每幀的計(jì)算量大小。 當(dāng)邏輯 處理與繪圖計(jì)算量過大時(shí),設(shè)備無法完成每秒 60 次繪制,此時(shí)幀率就會(huì)降低。?
mainLoop()方法會(huì)被定時(shí)調(diào)用,然而在不同的平臺(tái)下它的調(diào)用者不同。 通常 Application 類負(fù)責(zé)處理平臺(tái)相關(guān)的任務(wù),其中就包含了對 mainLoop()的調(diào)用; 不同的平臺(tái)具體實(shí)現(xiàn)也不相同,具體可參考cocos\2d\platform目錄;
mainLoop()方法是定義在 Director 中的抽象方法,它的實(shí)現(xiàn)位于同一個(gè)文件中的?DisplayLinkDirector類中; virtual void mainLoop() = 0; 具體實(shí)現(xiàn)是: void DisplayLinkDirector::mainLoop() { if (_purgeDirectorInNextLoop) { _purgeDirectorInNextLoop = false; purgeDirector(); } else if (! _invalid) { drawScene(); // release the objects //釋放資源對象 PoolManager::getInstance()->getCurrentPool()->clear(); } } 上述代碼主要包含如下 3 個(gè)步驟。 1、判斷是否需要釋放 Director,如果需要,則刪除 Director 占用的資源。通常,游戲結(jié)束時(shí)才會(huì)執(zhí)行這個(gè)步驟。 2、調(diào)用 drawScene()方法,繪制當(dāng)前場景并進(jìn)行其他必要的處理。 3、彈出自動(dòng)回收池,使得這一幀被放入自動(dòng)回收池的對象全部釋放。
mainLoop()把內(nèi)存管理以外的操作都交給了 drawScene()方法,因此關(guān)鍵的步驟都在 drawScene()方法之中; 再來看看drawScene方法: void Director::drawScene() { // calculate "global" dt //計(jì)算全局幀間時(shí)間差 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 //處理通知節(jié)點(diǎn) if (_notificationNode) { _notificationNode->visit(_renderer, identity, false); } if (_displayStats) { showStats(); } _renderer->render(); _eventDispatcher->dispatchEvent(_eventAfterDraw); kmGLPopMatrix(); _totalFrames++; // swap buffers //交換緩沖區(qū) if (_openGLView) { _openGLView->swapBuffers(); } if (_displayStats) { calculateMPF(); } } 可以分析出: 在主循環(huán)中,我們主要進(jìn)行了以下 3 個(gè)操作。 1、調(diào)用了定時(shí)調(diào)度器的 update 方法,引發(fā)定時(shí)器事件。 2、如果場景需要被切換,則調(diào)用 setNextStage 方法,在顯示場景前切換場景。 3、調(diào)用當(dāng)前場景的 visit 方法,繪制當(dāng)前場景。
在游戲主循環(huán) drawScene 方法中,我們可以看到每一幀引擎都會(huì)調(diào)用?_scheduler的 update 方法。 【Scheduler *_scheduler;】_scheduler 是 Scheduler 類型的對象,是一個(gè)定時(shí)調(diào)度器。 所謂定時(shí)調(diào)度器,就是一個(gè)管理所有節(jié)點(diǎn)定時(shí)器的對象,? 它負(fù)責(zé)記錄定時(shí)器,并在合適的時(shí)間觸發(fā)定時(shí)事件。
再來分析一下定時(shí)器的情況: Cocos2d-x 提供了兩種定時(shí)器,分別是: update 定時(shí)器,每一幀都被觸發(fā),使用 scheduleUpdate 方法來啟用; schedule 定時(shí)器,可以設(shè)置觸發(fā)的間隔,使用 schedule 方法來啟用。 看下Node中的實(shí)現(xiàn): 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 對象。 可以看到,這兩個(gè)方法的內(nèi)部除去檢查參數(shù)是否合法,只是調(diào)用了 Scheduler提供的方法。 換句話說,Node 提供的定時(shí)器只是對 Scheduler 的包裝而已。 不僅這兩個(gè)方法如此,其他定時(shí)器相關(guān)的方法也都是這樣。
Scheduler的分析 經(jīng)過上面的分析,我們已經(jīng)知道 Node 提供的定時(shí)器不是由它本身而是由 Scheduler管理的。 因此,我們把注意力轉(zhuǎn)移到定時(shí)調(diào)度器上。 顯而易見,定時(shí)調(diào)度器應(yīng)該對每一個(gè)節(jié)點(diǎn)維護(hù)一個(gè)定時(shí)器列表,在恰當(dāng)?shù)臅r(shí)候就會(huì)觸發(fā)其定時(shí)事件。
Scheduler的主要成員請查看:cocos\2d\CCScheduler.h
為了注冊一個(gè)定時(shí)器,開發(fā)者只要調(diào)用調(diào)度器提供的方法即可。 同時(shí)調(diào)度器還提供了一系列對定時(shí)器的控制接口,例如暫停和恢復(fù)定時(shí)器。 在調(diào)度器內(nèi)部維護(hù)了多個(gè)容器,用于記錄每個(gè)節(jié)點(diǎn)注冊的定時(shí)器; 同時(shí),調(diào)度器會(huì)接受其他組件(通常 與平臺(tái)相關(guān))的定時(shí)調(diào)用,隨著系統(tǒng)時(shí)間的改變驅(qū)動(dòng)調(diào)度器。?
調(diào)度器可以隨時(shí)增刪或修改被注冊的定時(shí)器。 具體來看,調(diào)度器將 update 定時(shí)器與普通定時(shí)器分別處理: 當(dāng)某個(gè)節(jié)點(diǎn)注冊 update 定時(shí)器時(shí),調(diào)度器就會(huì)把節(jié)點(diǎn)添加到 Updates 容器中, 即struct _hashUpdateEntry *_hashForUpdates里面; 為了提高調(diào)度器效率,Cocos2d-x 使用了散列表與鏈表結(jié)合的方式來保存定時(shí)器信息; 當(dāng)某個(gè)節(jié)點(diǎn)注冊普通定時(shí)器時(shí),調(diào)度器會(huì)把回調(diào)函數(shù)和其他信息保存到 Selectors 散列表中, 即struct _hashSelectorEntry *_hashForTimers里面。
在游戲主循環(huán)中,我們已經(jīng)見到了 update 方法。 可以看到,游戲主循環(huán)會(huì)不停地調(diào)用 update 方法。 該方法包含一個(gè)實(shí)型參數(shù),表示兩次調(diào)用的時(shí)間間隔。 在該方法中,引擎會(huì)利用兩次調(diào)用的間隔來計(jì)算何時(shí)觸發(fā)定時(shí)器。 我們再來分析下?update 方法的工作流程 // main loop void Scheduler::update(float dt) { _updateHashLocked = true; //a.預(yù)處理 if (_timeScale != 1.0f) { dt *= _timeScale; } // // Selector callbacks // // Iterate over all the Updates' selectors //b.枚舉所有的 update 定時(shí)器 tListEntry *entry, *tmp; // updates with priority < 0 //優(yōu)先級小于 0 的定時(shí)器 DL_FOREACH_SAFE(_updatesNegList, entry, tmp) { if ((! entry->paused) && (! entry->markedForDeletion)) { entry->callback(dt); } } // updates with priority == 0 //優(yōu)先級等于 0 的定時(shí)器 DL_FOREACH_SAFE(_updates0List, entry, tmp) { if ((! entry->paused) && (! entry->markedForDeletion)) { entry->callback(dt); } } // updates with priority > 0 //優(yōu)先級大于 0 的定時(shí)器 DL_FOREACH_SAFE(_updatesPosList, entry, tmp) { if ((! entry->paused) && (! entry->markedForDeletion)) { entry->callback(dt); } } // Iterate over all the custom selectors //c.枚舉所有的普通定時(shí)器 for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; ) { _currentTarget = elt; _currentTargetSalvaged = false; if (! _currentTarget->paused) { // The 'timers' array may change while inside this loop //枚舉此節(jié)點(diǎn)中的所有定時(shí)器 //timers 數(shù)組可能在循環(huán)中改變,因此在此處需要小心處理 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.清理所有被標(biāo)記了刪除記號(hào)的 update 方法 //優(yōu)先級小于 0 的定時(shí)器 DL_FOREACH_SAFE(_updatesNegList, entry, tmp) { if (entry->markedForDeletion) { this->removeUpdateFromHash(entry); } } // updates with priority == 0 //優(yōu)先級等于 0 的定時(shí)器 DL_FOREACH_SAFE(_updates0List, entry, tmp) { if (entry->markedForDeletion) { this->removeUpdateFromHash(entry); } } // updates with priority > 0 //優(yōu)先級大于 0 的定時(shí)器 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.處理腳本引擎相關(guān)的事件 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、參數(shù) dt 乘以一個(gè)縮放系數(shù),以改變游戲全局的速度,其中縮放系數(shù)可以由 Scheduler的TimeScale屬性設(shè)置。 2、分別枚舉優(yōu)先級小于 0、等于 0、大于 0 的 update 定時(shí)器。 如果定時(shí)器沒有暫停,也沒有被標(biāo)記為即將刪除,則觸發(fā)定時(shí)器。 3、枚舉所有注冊過普通定時(shí)器的節(jié)點(diǎn),再枚舉該節(jié)點(diǎn)的定時(shí)器,調(diào)用定時(shí)器的更新方法,從而決定是否觸發(fā)該定時(shí)器。 4、再次枚舉優(yōu)先級小于 0、等于 0、大于 0 的 update 定時(shí)器,移除前幾個(gè)步驟中被標(biāo)記了刪除記號(hào)的定時(shí)器。 我們暫不關(guān)心腳本引擎相關(guān)的處理。?
對于 update 定時(shí)器來說,每一節(jié)點(diǎn)只可能注冊一個(gè)定時(shí)器, 因此調(diào)度器中存儲(chǔ)定時(shí)器數(shù)據(jù)的結(jié)構(gòu)體tListEntry *entry主要保存了注冊者與優(yōu)先級。 對于普通定時(shí)器來說,每一個(gè)節(jié)點(diǎn)可以注冊多個(gè)定時(shí)器, 引擎使用回調(diào)函數(shù)(選擇器)來區(qū)分同一節(jié)點(diǎn)下注冊的不同定時(shí)器。 調(diào)度器為每一個(gè)定時(shí)器創(chuàng)建了一個(gè) Timer 對象, 它記錄了定時(shí)器的目標(biāo)、回調(diào)函數(shù)、觸發(fā)周期、重復(fù)觸發(fā)還是僅觸發(fā)一次等屬性。
Timer 也提供了 update 方法,它的名字和參數(shù)都與 Scheduler 的 update 方法一樣, 而且它們也都需要被定時(shí)調(diào)用。 同的是,Timer?的 update 方法會(huì)把每一次調(diào)用時(shí)接收的時(shí)間間隔 dt 積累下來, 如果經(jīng)歷的時(shí)間達(dá)到了周期就會(huì)引發(fā)定時(shí)器的定時(shí)事件。? 第一次引發(fā)了定時(shí)事件后,如果是僅觸發(fā)一次的定時(shí)器, 則 update 方法會(huì)中止,否則定時(shí)器會(huì)重新計(jì)時(shí),從而反復(fù)地觸發(fā)定時(shí)事件。
來看看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 中,程序首先枚舉了每一個(gè)注冊過定時(shí)器的對象,然后再枚舉對象中定時(shí)?器對應(yīng)的 Timer 對象, 調(diào)用 Timer 對象的 update 方法來更新定時(shí)器狀態(tài),以便觸發(fā)定時(shí)事件。
至此,我們可以看到事件驅(qū)動(dòng)的普通定時(shí)器調(diào)用順序為: 系統(tǒng)的時(shí)間事件驅(qū)動(dòng)游戲主循環(huán),游戲主循環(huán)調(diào)用 Scheduler 的 update 方法,Scheduler 調(diào)用普通定時(shí)器對應(yīng)的 Timer 對象的 update 方法,Timer 類的 update 方法調(diào)用定時(shí)器 對應(yīng)的回調(diào)函數(shù)。
對于 update 定時(shí)器,調(diào)用順序更為簡單,因此前面僅列出了普通定時(shí)器的調(diào)用順序。? 同時(shí),我們也可以看到,在定時(shí)器被觸發(fā)的時(shí)刻,Scheduler 類的 update 方法正在迭代之中, 開發(fā)者完全可能在定時(shí)器?事件中啟用或停止其他定時(shí)器。 不過,這么做會(huì)導(dǎo)致 update 方法中的迭代被破壞。 Cocos2d-x 的設(shè)計(jì)已經(jīng)考慮到了這個(gè)問題,采用了一些技巧避免迭代被破壞。 例如,update 定時(shí)器被刪除時(shí),不會(huì)直接刪除,而是標(biāo)記為將要刪除,在定時(shí)器迭代完畢后再清理被標(biāo)記的定時(shí)器,這樣即可保證迭代的正確性。
Cocos2d-x 的設(shè)計(jì)使得很多離散在各處的代碼通過事件聯(lián)系起來,在每一幀中起作用。 基于事件驅(qū)動(dòng)的游戲框架易于掌握,使用靈活,而且所有事件串行地在同一線程中執(zhí)行,不會(huì)出現(xiàn)線程同步的問題。
可以看到,Cocos2d-x是多么的強(qiáng)大!!! 小伙伴們,知道cococs2d-x是怎么調(diào)度了咩!! 咩!!
郝萌主友情提示: 多看看源碼,你就能更了解cocos2d-x了、、、

轉(zhuǎn)載于:https://blog.51cto.com/haomengzhu/1664404

總結(jié)

以上是生活随笔為你收集整理的18、Cocos2dx 3.0游戏开发找小三之cocos2d-x,请问你是怎么调度的咩的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。