使用cocos2d-js製作遊戲新手引導-源碼篇(三),cocos2d-js源碼
上一篇已經詳解了關於定位器的實現細節,本篇主要是講解實現引導的組成模組及整個引導流程,並給出整個引導的源碼及示範代碼。
線上示範:http://www.ixuexie.com/Guide/index.html
整個引導架構分為以下幾個部分:
- 引導配置
- 定位器
- 引導任務處理器
引導配置
請下看圖:
設計UI引導步驟:
UI操作的設計已經確定,控制項名字對於引導至關重要,我們首先要把這些準備工作做好。
這裡引入任務和步驟的概念:引導是由多個工作群組成,我們稱為tasks,其中一個任務稱為task,一個任務中又包含多個引導步驟稱為step。(這裡與上一篇中所講的工作群組、任務是一至的,只是換了一個說法而以。在編寫代碼時,我覺得使用任務與步驟來表達,讓代碼更清楚)
引導細節需求:
對應的引導配置:
var guideConfig = { tasks: { 1: [ { log: "關閉第一盞燈", command: sz.GuideCommand.GC_FINGER_HINT, locator:"_fire1" }, { log: "關閉第二盞燈", command: sz.GuideCommand.GC_FINGER_HINT, locator:"_fire2" }, { log: "儲存進度", command: sz.GuideCommand.GC_SAVE_PROGRESS }, { log: "點亮第一盞燈", command: sz.GuideCommand.GC_FINGER_HINT, locator:"_fire1" }, { log: "點亮第二盞燈", command: sz.GuideCommand.GC_FINGER_HINT, locator:"_fire2" }, ], 2: [ { log:'點擊home', command: sz.GuideCommand.GC_FINGER_HINT, locator:"_btnHome" } ], 3: [ { log:'點擊task', command: sz.GuideCommand.GC_FINGER_HINT, locator:"_btnTask" } ] }, locateNodeDurationTime: 0.1, fingerImage: 'res/finger.png'};//引導命令sz.GuideCommand = { GC_SET_PROPERTY: 1, //設定屬性 GC_FINGER_HINT: 2, //手型提示 GC_SAVE_PROGRESS: 3 //儲存進度};
目前sz.GuideCommand只內建了三個命令:設定屬性、手型提示、儲存進度
引導任務處理器
從上面的任務配置來看,任務步驟的處理是一個順序執行過程,但UI操作卻是一個非同步過程,只有UI操作完成後才能進行下一個任務。 對於javascript來說,處理非同步事件、回呼函數是最拿手的,但為了代碼可讀性、維護性比較好,我們這裡使用了大名頂頂的async庫來處理任務和步驟,這樣可以解決回呼函數的深度。
async
這裡只用到async兩個函數:
async.eachSeries(tasks, iterator, callback)
我們使用async.eachSeries來遍曆我們整個任務數組,iterator為迭代器函數,用於處理每一個任務,callback為任務全部處理完後,我們用來離開引導
async.series({}, [callback])
我們使用async.series處理一個步驟對象的幾個主要動作:步驟開始、步驟處理、步驟完畢
這裡核心的是步驟處理,步驟開始和結束我們可以用來擴充步驟對象,增加靈活性。
比如說,當一個步驟開始前,需要檢查金幣、等級這些,不滿足的話可以不觸發本步驟的進行。下一篇我會給大家做相關代碼的示範。
處理所有任務
//獲得任務數組 var tasks = [...] //使用eachSeries函數,遍曆所有任務對象 async.eachSeries(tasks, function(task, cb) { //遍曆任務中的步驟對象,stepHandle為步驟處理函數 async.eachSeries(task, stepHandle, function() { //當一個任務中的所有步驟完成為,自動儲存進度 self._guideLayer.save(true, cb); }); }, function() { //當所有任務都完成後,退出引導 self._exitGuide(); });//步驟處理var stepHandle = function(step, callback) { async.series({ //步驟開始 stepBegin: function(cb) { self._guideLayer._setLocateNode(null); if (step.onEnter) { step.onEnter.call(this._guideLayer, cb); } else { cb(); //執行cb函數觸發下面的stepProcess函數 } }, //步驟處理 stepProcess: function(cb) { if (step.delayTime) { self._guideLayer.scheduleOnce(function() { self._processStep(step, cb); }) } else { //執行具體的步驟處理操作,傳入setp對象,當步驟完成後自動執行cb函數,進入下面setpEnd函數 self._processStep(step, cb); } }, //步驟完畢 stepEnd: function() { if (step.onExit) { step.onExit.call(this._guideLayer, callback); } else { //callback為stepHandle的參數,執行callback函數本任務全部完成。 callback(); } } }); };
任務處理的核心是步驟的處理,步驟的處理最主要是正確執行步驟指令
_processStep: function(step, cb) { var self = this; //列印步驟日誌 if (step.log) { cc.log("guide: <" + step.log + ", step begin >"); } //產生步驟結束函數,統一執行此函數在此列印步驟日誌 var finish = function() { if (step.log) { cc.log("guide: <" + step.log + ", step finished >"); } if (step.delayTime) { setTimeout(cb, step.delayTime * 1000); } else { cb(); } }; //處理步驟指令 switch (step.command) { //設定屬性 case sz.GuideCommand.GC_SET_PROPERTY: this._guideLayer.locateNode(step.locator, function(node) { var property = step.args[0]; var args = step.args.slice(1); node[property].apply(node, args); }); break; //手型提示 case sz.GuideCommand.GC_FINGER_HINT: this._guideLayer.locateNode(step.locator, function(node) { self._guideLayer.fingerToNode(node, finish, true); if (step.onLocateNode) { step.onLocateNode.call(this._guideLayer, node); } }); break; //儲存進度 case sz.GuideCommand.GC_SAVE_PROGRESS: this._guideLayer.save(false, finish); break; default: cc.log("guide command is not define"); }}
關於定位區UI事件的檢測
上一篇中講到關於定位區UI事件的檢測是通過觀察者模式來實現,我在從原有項目中剝離引導架構時想到過大多數人可能會有自己的觀察者的具體實現。而且以觀察者來實現UI事件的檢測會讓代碼變的依賴性太強。所以在sz.Guide中沒有使用觀察者模式來檢測事件。
這裡UI事件的檢測主要使用了javascript語言的動態能力來攔截事件函數來實現,大概思路:
sz.Guide在事件管理上使用了sz.UILoader
//_onWidgetEvent為UILoader上的事件勾子函數,所有UI事件都會觸發它 var widgetEvent = sz.uiloader._onWidgetEvent; sz.uiloader._onWidgetEvent = function(sender, type) { //先執行原有事件函數 if (widgetEvent) { widgetEvent(sender, type); } //執行本類中的事件函數 self._onWidgetEvent(sender, type); }_onWidgetEvent: function(sender, type) { var locateNode = this._locateNode; //檢查定位節點是否為當前事件節點 if (locateNode && (sender === this._locateNode || sender.getName() === locateNode.getName() )) { this._setLocateNode(null); //執行步驟回呼函數,完成當前步驟 this._setpCallback(); } },
小結
sz.Guide目前實現了一個簡單、基本的引導功能,還有很多不全面的地方還需要繼續增加功能和改進。但總體思路是清晰的,在編寫引導任務也是非常容易,而且不破壞現有代碼的邏輯結構。完全可以在你的遊戲全部完成後,輕鬆將引導置入遊戲中去。
有興趣的朋友可以試著修改下,guideConfig.js中的配置,添加更多的引導步驟,試試引導用記依次點擊下面的功能按鈕
他們的控制項名字分別叫做:
_btnHome、_btnTask、_btnPlunder、_btnBag、_btnFriends、_btnSetting
源碼地址:https://github.com/ShawnZhang2015/Guide
線上示範:http://www.ixuexie.com/Guide/index.html