天天看點

麒麟子Cocos Creator 3D研究筆記二:麒麟子慣用架構分享

前言

麒麟子在開發中搞出來的架構,都是遵守“大道至簡,實用至上”這兩個基本原則。

接觸一個引擎的第一件事,就是搞出一個實用的架構,友善在此基礎上做開發。

由于目前的引擎已經是對象+元件模式,是以在場景對象管理上,不需要花太多功夫了。我們主要集中在界面管理這塊。

一、常見的幾種遊戲類型。

既然我們的架構想要滿足日常開發,就不得不滿足星辰大海般的需求。從用戶端的角度,我們可以把遊戲分為三類。

1.1、純界面玩法

像一些SLG、卡牌、象棋等可以視作純界面玩法。(也有某些大作要求3D表現效果的,我們不作讨論),比如下面的這類遊戲。

動物餐廳

1.2、某些純界面+戰鬥場景

王者榮耀

1.3、從頭到尾都是3D場景

魔獸世界

1.4、小結

仔細分析以後我們可以發現,假如我們的架構始終支援 2D/3D場景 + 界面 這樣的能力。就可以了。 

二、單場景 vs. 多場景

單場景+Prefab 和 多場景 從Cocos 2d-x提供了replaceScene函數開始,就一直有人在争論這個話題。隻要引擎提供了“場景”這個定義的,都會遇上決策問題。

麒麟子看得很明白,所謂的引擎場景管理,就是給了你一次無腦銷毀所有結點的機會。

如果你是在兩個截然不同的邏輯之間切換,那其實是可以使用引擎的場景切換功能的。 如果邏輯相差不大,我建議依然采用單場景方式。

而麒麟子更推薦的是單場景+Prefab方式,這樣會迫使你自己更注重場景節點和資源的管理。

三、架構内容

3.1、程式啟動入口

程式啟動入口包含兩個部分。

3.1.1、最小場景

最小場景如果不出意外,我們隻需要一個Canvas節點,一個MainCamera節點,一個MainLight節點就好了。

3.1.2、App.ts

麒麟子喜歡以App.ts作為程式入口,将這個App.ts挂在Canvas節點上即可。App.ts的内容并不多,如下所示

import { _decorator, Component, Node } from 'cc';
import { UIMgr, UILayer } from './UIMgr';
import { HUD } from './HUD';
const { ccclass, property } = _decorator;

@ccclass('AppTs')
export class AppTs extends Component {
    start () {
        UIMgr.inst.setup(UILayer.NUM);
        UIMgr.inst.showUI(HUD);
    }
}      

我們可以清晰地看到,在這裡,麒麟子隻啟動了UIMgr。如果還有其他遊戲管理器需要一開始就初始化,那麼把它們放在這裡就好了。 

3.2、界面管理器

  • 界面管理器至少要包含幾個功能
  • 動态加載和銷毀界面
  • 界面層級管理
  • 界面事件自動管理機制
  • 界面與遊戲業務邏輯通信機制

上面說的這些,架構裡都自帶了。

四、分辨率自适應

很多小夥伴一定還在糾結是用fitWidth還是fitHeight吧。

如果使用fitWidth,遇上更細長的裝置,界面上面部分可能被裁剪。

如果使用fitHeight,遇上更短的裝置,界面左右部分可能被裁剪。

其實我們想要的隻有一句話:任何時候,都不要裁剪。

基于這個目标,我們制定出的政策就是。

a、在比設計分辨率更細長的裝置上,我們使用fitHeight,這樣一來,他長由他長,我們隻需要保證背景左右能夠填充就行。

b、在比設計分辨率更短的裝置上,我們使用fitWidth,這樣一來,他高由他高,我們隻需要保證背景上下能夠填充就行。上面的适配機制,已經被麒麟子寫成了一個函數。

public resize() {

        let dr = view.getDesignResolutionSize();
        var s = cc.view.getFrameSize();
        var rw = s.width;
        var rh = s.height;
        var finalW = rw;
        var finalH = rh;

        if((rw/rh) > (dr.width / dr.height)){
            //!#zh: 是否優先将設計分辨率高度撐滿視圖高度。 */
            //cvs.fitHeight = true;

            //如果更長,則用定高
            finalH = dr.height;
            finalW = finalH * rw/rh;
        }
        else{
            /*!#zh: 是否優先将設計分辨率寬度撐滿視圖寬度。 */
            //cvs.fitWidth = true;
            //如果更短,則用定寬
            finalW = dr.width;
            finalH = rh/rw * finalW;
        }

        view.setDesignResolutionSize(finalW,finalH,ResolutionPolicy.UNKNOWN);
        let cvs = find('Canvas').getComponent(UITransformComponent);
        cvs.node.width = finalW;
        cvs.node.height = finalH;
    }      

關于這個函數,有兩個地方要注意

注意1:在設定面闆裡面,fitWidth和fitHeight都得去掉,一個都不要勾,否則可能出現設定失效。

注意2:麒麟子在調用view.setDesignResolutionSize函數的時候,最後一個參數傳的是ResolutionPolicy.UNKNOWN。這樣一來,這個函數是可以重複調用且生效的。當我們處于微信浏覽器的時候,橫豎屏旋轉會導緻寬高比不一緻,這就需要再次調用這個函數來重新調整布局。

五、遊戲邏輯與界面通信機制

麒麟子這裡沒有MVC,也沒有MVVM,隻有以下幾個套路

1、資料、資料更新方法由邏輯管理器提供

2、資料變更,想讓界面産生改變,則通過事件傳遞

3、界面可以直接調用邏輯管理器

4、兩個界面之間,隻能通過事件傳遞(需要兩個界面關聯的情況非常少,一般情況下界面之間的邏輯都是互不幹擾的)

也就是說,邏輯管理器不知道界面的存在,但界面是知道邏輯管理器的存在的。

我舉一個關于個人資訊的例子。在這個例子中,我們會涉及到三個檔案 MyInfoMgr.ts(使用者資訊邏輯管理器 M) 、UIMyInfoController.ts(使用者資訊界面控制器 C) 、MyInfo.prefab(使用者資訊界面布局 V)。

如果非要用MVC來對應的話,就按我後面标記的字母來對應吧。

當使用者點選個人按鈕時,UIMgr會執行個體化UIMyInfoController類,UIMyInfoController類會加載MyInfo.prefab。

當加載成功後,UIMyInfoController會有一個onCreated回調,我們可以在回調裡初始化我們的顯示資訊。

UIMyInfoController需要監聽使用者資訊改變相關的事件,當收到事件的時候,需要對應地做顯示更新

MyInfoMgr持有使用者資料并提供通路接口以及資料修改接口,當資料産生修改時,會抛出修改事件。它不關心這個事件是否有人要用。無腦抛出就行。

六、總結

與其說是架構,不如說是麒麟子的慣用套路。這個套路沒有出色的地方,也不滿足很多學術性的依賴解耦标準。但這個套路陪着我走過了大大小小很多項目。

如果要給它下一個定義的話,我覺得就是兩個詞:“簡單、實用”。

繼續閱讀