[cocos2dx]cocos2dx-lua中的架構MVC,cocos2dxluamvc
MVC簡介MVC,即Model View Controller。Model(模型),一般負責資料的處理;View(視圖),一般負責介面的顯示;Controller(控制器),一般負責前端的邏輯處理。拿一款手機遊戲來說,介面UI的顯示、布局等就是View負責;點擊了按鈕,手勢的滑動等操作由Controller來處理;遊戲中需要的資料資源就交給Model。
其中cocos、Controller、Model、View這個不用多說,Event裡面儲存的全域訊息類型,Managers是用於管理遊戲中的東東的,比如管理資源,管理各種情境切換,層的切換等等。Utilities提供一些工具類,比如字串的處理等。大家也可以根據自己的需求來定製目錄,比如定義一個NetCenter檔案夾,專門用於處理網路的。本例子中沒有用到資料操作和工具類,所以這兩個檔案夾為空白。流程執行個體我們以遊戲的運行流程為線索來展開說明。運行項目,進入到main.lua檔案,來看看main函數:
local function main() collectgarbage(collect) -- avoid memory leak collectgarbage(setpause, 100) collectgarbage(setstepmul, 5000) -- initialize director local director = cc.Director:getInstance() --turn on display FPS director:setDisplayStats(true) --set FPS. the default value is 1.0/60 if you don't call this director:setAnimationInterval(1.0 / 60) cc.Director:getInstance():getOpenGLView():setDesignResolutionSize(320, 480, 1) --create scene local scene = require(GameScene) local gameScene = scene:startGame() end
我們最後調用了GameScene類中的startGame函數,來看看GameScene這個類:
require(Managers.SceneManager)require(Managers.LayerManager) local GameScene = class(GameScene)local scene = nil function GameScene:startGame() --初始化 scene = cc.Scene:create() if cc.Director:getInstance():getRunningScene() then cc.Director:getInstance():replaceScene(scene) else cc.Director:getInstance():runWithScene(scene) end SceneManager:initLayer(scene) self:enterGame()end function GameScene:enterGame() LayerManager:getInstance():gotoLayerByType(LAYER_TYPE_MAIN)end return GameScene
在startGame函數中,我們建立了一個空情境,然後調用SceneManager情境管理器來初始化情境。最後調用enterGame函數正式進入遊戲主介面,其中enterGame函數中又有一個LayerManager層管理器。我們來看看這兩個管理器是如何工作的。先看看SceneManager:
--情境管理器SceneManager = {} --背景層bgLayer = nil--遊戲層gameLayer = nil--彈窗層panelLayer = nil function SceneManager:initLayer(scene) bgLayer = cc.Layer:create() scene:addChild(bgLayer) gameLayer = cc.Layer:create() scene:addChild(gameLayer) panelLayer = cc.Layer:create() scene:addChild(panelLayer)end很簡單,按順序初始化了三個空Layer。再來看看LayerManager管理器:
--Layer管理器LayerManager = {} LAYER_TYPE_MAIN = LAYER_TYPE_MAIN local curLayer = nil function LayerManager:new(o) o = o or {} setmetatable(o,self) self.__index = self return oend function LayerManager:getInstance() if self.instance == nil then self.instance = self:new() end return self.instanceend function LayerManager:gotoLayerByType(type) if curLayer ~= nil then curLayer:destroy() end if type == LAYER_TYPE_MAIN then local layer = require(Controller.MainLayerController):create() curLayer = layer endend看看gotoLayerByType這個函數,首先切換層的時候,看看當前層是否為空白,不為空白就刪掉。然後根據傳遞過來的參數來判斷要切換到哪個層。
這裡出現MVC中的Controller部分,看看是什麼情況。這裡調用了類MainLayerController中的create函數:
function MainLayerC:create() local layer = MainLayerC:new() return layerend function MainLayerC:ctor() self:createUI()--建立介面 self:addBtnEventListener()--添加按鈕監聽end function MainLayerC:createUI() local layer = require(View.MainLayerView) self.mainLayer = layer:createUI() gameLayer:addChild(self.mainLayer)end
這裡我們又發現了MVC中的View,在createUI函數中,我們調用了類MainLayerView的createUI函數,並將其添加到情境的遊戲層中。我們來看看MainLayerView這個類。
local eventDispatcher = cc.Director:getInstance():getEventDispatcher() local MainLayerV = class(MainLayerView,function() return cc.Layer:create()end) function MainLayerV:createUI() local mainLayer = MainLayerV:new() return mainLayerend function MainLayerV:ctor() self:initUI()end function MainLayerV:initUI() local winSize = cc.Director:getInstance():getWinSize() self.bg = cc.Sprite:create(ResManager.main_bg) self.bg:setPosition(winSize.width / 2,winSize.height / 2) self:addChild(self.bg) local function menuCallback(tag,menuItem) local event = cc.EventCustom:new(EVENT_CLICK_MENU_MAIN) event._usedata = tag eventDispatcher:dispatchEvent(event) end self.btnItem1 = cc.MenuItemImage:create(ResManager.main_btn1,ResManager.main_btn1,ResManager.main_btn1) self.btnItem1:setPosition(winSize.width / 2,winSize.height / 3) self.btnItem1:setTag(1) self.btnItem1:registerScriptTapHandler(menuCallback) self.btnItem2 = cc.MenuItemImage:create(ResManager.main_btn2,ResManager.main_btn2) self.btnItem2:setPosition(winSize.width / 2,winSize.height / 2) self.btnItem2:setTag(2) self.btnItem2:registerScriptTapHandler(menuCallback) self.btnItem3 = cc.MenuItemImage:create(ResManager.main_btn3,ResManager.main_btn3) self.btnItem3:setPosition(winSize.width / 2,winSize.height / 3 * 2) self.btnItem3:setTag(3) self.btnItem3:registerScriptTapHandler(menuCallback) --建立菜單 self.menu = cc.Menu:create(self.btnItem1,self.btnItem2,self.btnItem3) self.menu:setPosition(0,0) self:addChild(self.menu)end return MainLayerV
可以看到,我們在主介面中添加了一張背景圖和三個按鈕。我們是通過資源管理員ResManager來管理遊戲中的素材的,ResManager檔案很簡單:
--資源管理員ResManager = {} --主介面ResManager.main_bg = bg_big.pngResManager.main_btn1 = cell.pngResManager.main_btn2 = cell2.pngResManager.main_btn3 = cell3.png這樣做的好處是,如果圖片改了名字或者換了路徑等,只需要在這裡改一次就可以了。
可以看到我們給三個按鈕註冊了
響應函數menuCallback,在這個函數中,就是MVC中的V和C之間的“溝通”了。我們定義了一個自訂事件EVENT_CLICK_MENU_MAIN,並給這個事件添加了一個附帶參數_usedata,這個參數儲存的是三個按鈕的tag。然後將這個事件發送給他的監聽者。這裡大家應該明白了,我們在對應的Controller中註冊了EVENT_CLICK_MENU_MAIN的監聽,但有這個事件發過來時,我們就響應。根據事件攜帶的參數_usedata,我們就知道了在View中,玩家點擊了哪個按鈕,這樣做的好處是,
保證了每個介面只有一個訊息,我們只需要根據這個訊息攜帶的附加參數來判斷具體的事件,從而減少了訊息個數,這樣有助於遊戲的效率。另外,我們在響應這個訊息的時候,也會做一定的最佳化,來看看類MainLayerController的響應函數:
function MainLayerC:addBtnEventListener() --按鈕事件處理 local function eventBtnListener(event) local eventNum = event._usedata local switch = { [1] = function() print(Btn one) end, [2] = function() print(Btn two) end, [3] = function() print(Btn three) end } switch[eventNum]() end --註冊事件處理 self._eventBtnListener = cc.EventListenerCustom:create(EVENT_CLICK_MENU_MAIN,eventBtnListener) eventDispatcher:addEventListenerWithSceneGraphPriority(self._eventBtnListener,self.mainLayer)end可以看到實際情況,我們並不需要對傳遞過來的參數進行判斷,而是定義了一個函數數組,直接根據下標來調用對應的訊息響應。之後繼續通過各種管理器來對遊戲內容進行變化,方式和MainLayerController和MainLayerView差不多。cocos2dx-lua架構流程
很多學習者甚至不知道enterScene(“MainScene”) 為什麼裡面可以是個字串?
建立cocos2dx-Lua工程之後,你首先看到的main.lua啟動到MyApp.lua。
require("app.MyApp").new():run()
看MyApp.lua檔案:
1. require(“app.MyApp”)
這裡執行的MyApp.lua的代碼是:
local MyApp = class("MyApp", cc.mvc.AppBase) -- 繼承cc.mvc.AppBasereturn MyApp這時候,你得到了MyApp這個類(lua關於類的實現網上很多)
2. require(“app.MyApp”).new()
MyApp.new()執行後,執行的代碼是:
function MyApp:ctor() MyApp.super.ctor(self)end
為什麼new()了之後會執行MyApp:ctor()?請看function.lua下的function class(classname, super)方法:
function cls.new(...) local instance = cls.__create(...) -- copy fields from class to native object for k,v in pairs(cls) do instance[k] = v end instance.class = cls instance:ctor(...) return instanceend
可以看到,在class的實現方法裡面,給每個建立的類聲明了一個new()方法,方法裡面調用了ctor()構造方法(ctor只是個名字,所以不是有些人認為的new了之後,當然會調用構造方法,lua沒有類,只是我們模仿了類)
3. require(“app.MyApp”).new():run()
這時候調用了
function MyApp:run() CCFileUtils:sharedFileUtils():addSearchPath("res/") self:enterScene("MainScene")end所以進到了MainScene.lua。
對於MyApp.lua檔案,如果我修改成下面的樣子,是不是你就理解了上面所做的事情:
-- 聲明類MyApp = class("MyApp", cc.mvc.AppBase) --- 類構造方法--function MyApp:ctor() MyApp.super.ctor(self)end --- 對應cpp版的static create()方法--function MyApp:create() local myApp = MyApp.new() myApp:init()end --- 你自己的方法-- @param self --local function launchMainScene(self) CCFileUtils:sharedFileUtils():addSearchPath("res/") self:enterScene("MainScene")end --- init 方法--function MyApp:init() -- add code here launchMainScene(self)end對應的main.lua將原來的require(“app.MyApp”).new():run()
修改為:
require("app.MyApp")MyApp:create()這樣你是不是更容易理解了,哈哈。
4. MainScene.lua
enterScene(“MainScene”) 為什麼可以切換情境?
我們看下MyApp的父類AppBase裡面:
function AppBase:enterScene(sceneName, args, transitionType, time, more) local scenePackageName = self. packageRoot .. ".scenes." .. sceneName local sceneClass = require(scenePackageName) local scene = sceneClass.new(unpack(totable(args))) display.replaceScene(scene, transitionType, time, more)end
這樣你能理解了為什麼連require檔案都沒有就能調用MainScene,當然你要留意下,它require時候的檔案路徑,scene預設寫的app/scenes檔案夾。好了,其他的應該按照上面的思路基本都能知道為什麼了
Lua的MVC架構Sailor
Sailor 是一個 Lua 語言的 MVC 編程架構。支援跨平台,相容 mod_lua 或者 mod_pLua, Nginx 的 ngx_lua, 或者任何支援 CGI 的 Web 服務器,如 Civetweb 或者 Mongoose, 前提是必須有 CGILua。使用 Sailor 開發應用的目錄結構如下:
- /conf - 存放設定檔
- /controllers - 控制器
- /layouts - 布局檔案
- /models - 模型
- /pub - 靜態檔案
- /runtime - 運行時產生的臨時檔案
- /views - .lp 視圖檔案