1. 程式人生 > >使用cocos2d-js製作遊戲新手引導(一)

使用cocos2d-js製作遊戲新手引導(一)


    ​  
想到新手引導的功能時可能很多人都會覺得頭痛,難以下手。特別是在遊戲本身功能或需求還不穩定的情況,更是難以應付,本人就是在這種情況下接受了一個艱鉅的任務。在痛定思痛之後,開始了引導功能開發。在做的過程中一點點發現很多有意思的東西,想分享給大家。

一、痛點:新手引導製作的難點及弊端

  1. 需要在具有引導功能的程式碼單元插入引導程式碼或邏輯判斷,干擾正常流程。

  2. 引導程式碼的加入會影響原有的程式碼邏輯與流程,使程式碼變得複雜加大維護難度。

  3. 介面或需求發生變化後引導功能需要大幅修改或重新制作。

  4. 指引(手指提示)對應的矩形區定位麻煩,特別是需要適應不同尺寸螢幕的時候更加困難。

  5. 編寫引導配置檔案也很頭痛,需要策劃、程式的高度配合。

二、期望:新手引導程式設計體驗

筆者進入遊戲開發應該說是手機遊戲開發並不是很長時間,雖然參於過多個專案,但親自編寫新手引導這還是頭一次。當時接到新人引導任務時,我們的專案只完成了:登入->主介面->抽卡->佈陣->章節->關卡->戰鬥這樣一個基本流程,介面美術、功能需求都極不穩定。但在公司的硬性要求下,冒著九死一生的危險開始了新手引導功能開發。在瞭解到傳統的引導製作過程中的難點與弊端後,一直在思考沒有更好的實現方式,我心中的引導程式設計的方式有以下幾點:

  1. 不需要在每個單元中去插入引導程式碼,遊戲程式碼與引導程式碼應該儘量分離。本人很難忍受漂亮的程式碼被無情引導打亂,更難忍受本來糟糕的程式碼被引導弄得支離破碎。

  2. 介面只發生簡單UI位移、節點層次改變不需要修改引導程式碼。

  3. 定位指引矩形區應該儘量的簡單,且自適應不同尺寸螢幕。最好能做到策劃人員都可以來製作部分流程引導。

  4. 在引導需求明確、遊戲功能正常的情況下,製作一個常規的引導步驟應該是非常快捷的,不會超過3分鐘,快的話1分鐘內就應該搞定(不是筆者說大話,確實已經實現)。

三、思想:引導功能的設計思路

在描述引導功有設計思路之前,有個重要的前題:命名規範

    命名規範主要有兩個方面:

  1. cocostudio中的控制元件名字

  2. 程式碼中動態建立的控制元件名字,以及類成員變數的名字。

在筆者的專案中使用了sz.UILoader來管理cocostudio的UI命名和事件。 如不瞭解請參見我的另外一篇blog

我們這裡引入兩個概念:任務與任務組。任務:把引導中的一個最小步驟稱之為一個任務,比如提示點選某個按鈕。任務組:把一系列的任務放在一個任務組中,當這個任務組中的任務全部完成,我們會儲存一次任務進度。此時重新進入遊戲將不會再執行這個任務,而是執行它的下一個任務組中的任務。可以理解任務組是引導中的一個步驟。

    用json格式表示如:

     {

        1: [{任務1},{任務2},{任務3}]

        3: [{任務7},{任務8},{任務9}]

        2: [{任務4},{任務5},{任務6}]

     }

     當從一個任務組中的任務中斷後,再次進入引導 需要重新從這個任務組的第一個任務開始。

     見下圖演示了一個從主介面點選召喚->靈石召喚一次->點選獲得->確定->仙玉召喚一次->點選獲得->確定->點選空白退出召喚介面的流程。

 

上圖演示的引導我分成兩個任務組:靈石召喚、仙玉召喚。 任務配置如下:

"3":[
    {
        "name": "4.提示指向靈石召喚按鈕",
        "command": "手型提示",
        "tag": "_oneMoneyButton"
    },
    {
        "name": "儲存進度",
        "command": "儲存進度"
    },
    {
        "name": "5.提示指向角色確定按鈕",
        "command": "手型提示",
        "tag": "_UILotteryHero > _confirmBtn"
    },
    {
        "name": "6.提示指向角色圖示確定按鈕",
        "command": "手型提示",
        "tag": "_UILotteryTimes > _confirmBtn"
    }
],
"4":[
    {
        "name": "7.提示指向仙玉召喚按鈕",
        "command": "手型提示",
        "tag": "_oneGoldButton"
    },
    {
        "name": "儲存進度",
        "command": "儲存進度"
    },
    {
        "name": "8.提示指向角色確定按鈕",
        "command": "手型提示",
        "tag": "_UILotteryHero/Panel_33/Image_10/_confirmBtn"
    },
    {
        "name": "9.提示指向角色圖示確定按鈕",
        "command": "手型提示",
        "tag": "_UILotteryTimes/Panel_11/Image_1/_confirmBtn"
    },
],

其中每個任務中的name用於除錯列印的對引導本身無實際用處,在任務開始和結速都會有提示,如果出錯方便定位。 command這裡應該叫做指令,對應一段具體功能的程式碼或函式,我這裡設定了兩個:手型提示、儲存進度。

     手型提示:需要配合tag欄位的值,tag描述了一個當前任務狀態下的一個node節點的索引。具體tag的編寫方式請看下面一節"實現在節點樹中定位控制元件"。

     進度儲存:手動進度儲存是為了確保在任務中斷後,遊戲流程不受影響。 在招喚這個功能裡,是隻能召喚一次的,如果已經召喚成功了,伺服器已經更新資料 ,後面的引導都是客戶端的介面顯示、關閉引導。如果在召喚之後,做一次進度儲存,任務中斷後再次進入引導會跳過這個任務組中的任務。      

      在理解了任務的功能後,需要有一個上層框架來一個一個的執行這些任務。

      引導框架:在任務條件滿足時(比如:等級要達到多少或者無任何條件),指示使用者進行某項任務(比如按鈕的點選)。當任務完成後,執行下一個任務,直接到全部任務被完成。它需要具有以下幾點功能:

  1. 條件檢查:檢查是否該執行該任務,預設為無條件執行。這需要檢查任務是否有onTaskBegan函式 ,不存在或返回ture才能執行任務指令

  2. UI   定位:找到出當前任務中UI節點對應的矩形區。在指引任務中準確編寫UI定位描述,由框架去檢索UI節點,當檢索到節點後呼叫任務的onLocateNode函式,傳入節點對像,這可以讓整個引導可以有更多的擴充套件。

  3. 指引動畫:當定位成功後,引導框播放指引提示動畫,提示使用者操作該矩形區。

  4. 觸控限制:遮蔽定位節點矩形區外的操作全部。

  5. 事件檢查:矩形區對應的UI事件是否被執行。

  6. 任務完成:通知引導框架任務完成,進入下一個任務。

四、定位:實現在節點樹中定位控制元件

以上幾點中首要解決的是對UI控制元件的定位,對UI定位最直接有效的方法是在拿到這個UI控制元件物件,然後取出他的BoundingBox、錨點資訊,進行座標轉換。但如何才能拿到這個控制元件物件呢? 這裡有兩種實現方式:

    1. 遍歷場景樹,把它搜尋出來。

    2. 事先把這個控制元件物件註冊到你的引導框架中。

      我採取的是第一種方法來定位控制元件,因為我不想在到處程式碼中新增遊戲邏輯以外的東西。而且cocos2d-js中提供有現成的函式cc.helper.seekWidgetByName,如果你做的是手機遊戲是不能直接使用這個函式的。在HTML5上這個函式可以遍歷整個節點樹 ,在jsb上只是遍歷的Widget節點。 有兩種方法解決這個問題:

    1.把cc.helper.seekWidgetByName函式複製到自己程式碼檔案中,重新取個名字叫:xxx.helper.seekNodeByName。在html5和jsb上都使用這個函式。

    2.在c++ jsb上把cc.Helper.seekWidgetByName的引數修改成在Node節點上做遍歷,或都重新封裝一個jsb上的seekNodeByName函式 。

      我這裡偷懶還是使用第一種方法。通過上面的方法是否已經解決UI定位的問題呢?應該沒那麼簡單吧!通過這種方法定位控制元件,就不用在引導配置檔案裡填寫座標或矩形資料,那是極其愚蠢的辦法。

  1. 打住,他媽的還有問題!!! 如果一個場景樹中有兩個相同節點名字怎搞?

    這個問題確實問的很正確。因為我們經常會有名字相同的節點存在。比如下圖: 

  

如果他們名字都叫button,使用seekNodeByName是來定位控制元件的話只能找到其中一個。具體是那個是根據你addChild

的順序來決定的。這個問題如何解決?能唯一確定一個控制元件在場景樹中的方法就是他的“完整路徑”,

像這樣一下來描述兩個button:

   "招喚介面/靈石招喚/召喚一次"

   "招喚介面/仙玉招喚/召喚一次"

其實我們已經能定位到靈石招喚和仙玉招喚了(我這裡為了方便理解使用中文名字)只需要這樣寫:

   "靈石招喚/召喚一次"

   "仙玉招喚/召喚一次"

這樣也能精確定位到你想要的那個按鈕。

    在這裡我實現了一個簡易的定位器描述規則,我們以後通過以下方式在任務中定位一個控制元件 :

  1. 名字描述:在場景中有獨一無二的名字時,直接描述控制元件名如:'_loginButton'。

  2. 路徑名描述:在場景中需要定位的節點可能有重名時,找到其父節點,確保父節點不會有重名時使用:'parentName/button'。如果父節點也有重名,那就再向上使用其父節點名的父節點以此類推。

  3. js屬性描述: 有一種情況通過getChildByName無法直接訪問的節點,如ccui.ScollView容器中的節點。我定義了一種簡單的獲取方式,例如 'layer1.button' 通過“.”這個符號來定位layer1下的一個屬性為button。這種方式是在js中最為直接的方式。

  4. 子節點描述:使用完整路徑描述一個控制元件時,有時會覺得比較長,例如: 'mainLayer/layer1/button' 可以簡寫成 'mainLayer>button' 表示定位mainLayer下一個名字叫button的子節點,有可以是1級子節點,也可能為2、3、n級子節點。

     5. 複合描述:將以上幾個方式組合使用,來描述一個控制元件:'mainLayer>homeLayer/layer1.button' 。用人話翻譯下就是:mainLayer下有一個homeLayer子節點(不管是幾級)下的一級子節點layer1下的一個變數名為button的節點.

描述符號總結 :     

     ​/   : 表示一級子節點

     ​>  : 表示一級~n級子節點

     .   : 表示屬性名

       這裡就體現了為什麼要注意名命規範的問題。有web開發經驗的人一眼就能看出這裡有一點css選擇器的味道,呵呵!非常正確,正是借鑑了css選擇器的思想,實現一個十分簡單的選擇器,我們這裡可以稱之為“定位器”,因為我們只需要定位出一個節點。

(未完待續)