cocos2d-x lua mvc模式



MVC简介

MVC,即Model View Controller。 Model(模型),一般负责数据的处理View(视图),一般负责界面的显示;Controller(控制器), 一般负责前端的逻辑处理。拿一款手机游戏来说,界面UI的显示、布局等就是View负责;点击了按钮,手势的滑动等操作由Controller来处理;游戏中需要的数据资源就交给Model。 \
其中cocos、Controller、Model、View这个不用多说, Event里面保存的全局消息类型Managers是用于管理游戏中的东东的,比如管理资源,管理各种场景切换,层的切换等等。 Utilities提供一些工具类,比如字符串的处理等。大家也可以根据自己的需求来定制目录,比如定义一个NetCenter文件夹,专门用于处理网络的。本例子中没有用到数据操作和工具类,所以这两个文件夹为空。

流程实例

我们以游戏的运行流程为线索来展开说明。运行项目,进入到main.lua文件,来看看main函数:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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这个类:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
--场景管理器
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管理器:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
--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 o
end
  
function LayerManager:getInstance()
     if self.instance == nil then
         self.instance = self: new ()
     end
      
     return self.instance
end
  
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
     end
end
看看gotoLayerByType这个函数,首先切换层的时候,看看当前层是否为空,不为空就删掉。然后根据传递过来的参数来判断要切换到哪个层。 这里出现MVC中的Controller部分,看看是什么情况。这里调用了类MainLayerController中的create函数
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function MainLayerC:create()
     local layer = MainLayerC: new ()
     return layer
end
  
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这个类。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
local eventDispatcher = cc.Director:getInstance():getEventDispatcher()
  
local MainLayerV = class (MainLayerView,function()
     return cc.Layer:create()
end)
  
function MainLayerV:createUI()
     local mainLayer = MainLayerV: new ()
     return mainLayer
end
  
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文件很简单:
?
1
2
3
4
5
6
7
8
--资源管理器
ResManager = {}
  
--主界面
ResManager.main_bg = bg_big.png
ResManager.main_btn1 = cell.png
ResManager.main_btn2 = cell2.png
ResManager.main_btn3 = cell3.png
这样做的好处是,如果图片改了名字或者换了路径等,只需要在这里改一次就可以了。
可以看到我们给三个按钮注册了 响应函数menuCallback,在这个函数中,就是MVC中的V和C之间的“沟通”了。我们定义了一个自定义事件EVENT_CLICK_MENU_MAIN,并给这个事件添加了一个附带参数_usedata,这个参数保存的是三个按钮的tag。然后将这个事件发送给他的监听者。这里大家应该明白了,我们在对应的Controller中注册了EVENT_CLICK_MENU_MAIN的监听,但有这个事件发过来时,我们就响应。根据事件携带的参数_usedata,我们就知道了在View中,玩家点击了哪个按钮,这样做的好处是, 保证了每个界面只有一个消息,我们只需要根据这个消息携带的附加参数来判断具体的事件,从而减少了消息个数,这样有助于游戏的效率。另外,我们在响应这个消息的时候,也会做一定的优化,来看看类MainLayerController的响应函数:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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。

?
1
require( "app.MyApp" ). new ():run()

看MyApp.lua文件:

1. require(“app.MyApp”)
这里执行的MyApp.lua的代码是:

?
1
2
local MyApp = class ( "MyApp" , cc.mvc.AppBase)  -- 继承cc.mvc.AppBase
return MyApp
这时候,你得到了MyApp这个类(lua关于类的实现网上很多)
2. require(“app.MyApp”).new()
MyApp.new()执行后,执行的代码是:
?
1
2
3
function MyApp:ctor()
     MyApp. super .ctor(self)
end
为什么new()了之后会执行MyApp:ctor()?请看function.lua下的function class(classname, super)方法:
?
1
2
3
4
5
6
7
8
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 instance
end
可以看到,在class的实现方法里面,给每个创建的类声明了一个new()方法,方法里面调用了ctor()构造方法(ctor只是个名字,所以不是有些人认为的new了之后,当然会调用构造方法,lua没有类,只是我们模仿了类)
3. require(“app.MyApp”).new():run()
这时候调用了
?
1
2
3
4
function MyApp:run()
     CCFileUtils:sharedFileUtils():addSearchPath( "res/" )
     self:enterScene( "MainScene" )
end
所以进到了MainScene.lua。
对于MyApp.lua文件,如果我修改成下面的样子,是不是你就理解了上面所做的事情:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-- 声明类
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()
修改为:
?
1
2
require( "app.MyApp" )
MyApp:create()
这样你是不是更容易理解了,哈哈。
4. MainScene.lua
enterScene(“MainScene”) 为什么可以切换场景?
我们看下MyApp的父类AppBase里面:
?
1
2
3
4
5
6
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 视图文件
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值