cocos2dx之lua项目开发中MVC框架的简单应用

****************************************************************************

时间:2015-03-31

作者:Sharing_Li

转载注明出处:http://blog.csdn.net/sharing_li/article/details/44658317

****************************************************************************

最近的游戏项目中使用了lua脚本来开发,项目中用到了MVC框架,最近有朋友问我怎么弄,在这里简单分享一下思路和一些开发中的技巧。

先简单说说MVC,即Model View Controller。Model(模型),一般负责数据的处理;View(视图),一般负责界面的显示;Controller(控制器),一般负责前端的逻辑处理。拿一款手机游戏来说,界面UI的显示、布局等就是View负责;点击了按钮,手势的滑动等操作由Controller来处理;游戏中需要的数据资源就交给Model。

接下来,看看在游戏开发中怎么用,这里用Lua(环境使用cocos code ide)给大家说说。

先来看看项目的目录结构:

其中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 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函数:

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这个类。

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文件很简单:

--资源管理器
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的响应函数:

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差不多。

到这里,MVC应用的简单介绍就结束啦,免费下载代码

时间: 2024-10-05 04:28:02

cocos2dx之lua项目开发中MVC框架的简单应用的相关文章

项目开发中常用的PHP函数

日期操作 为了便于存储.比较和传递,我们通常需要使用strtotime()函数将日期转换成UNIX时间戳,只有在显示给用户看的时候才使用date()函数将日期转换成常用的时间格式. strtotime()  函数将任何英文文本的日期时间描述解析为 Unix 时间戳 eg: <?php echo(strtotime("now")); echo(strtotime("3 October 2005")); echo(strtotime("+5 hours&

逆向思维在项目开发中真的很重要

最近一直和我的小组开发一个投资类型的网站,网站的整体已经完成得差不多了,客户今天突然提出了一个要求,希望能够在所有人退出当前聊天大厅后,后面进入的人不能够看到之前用户的聊天记录(原来是可以看到的).由于聊天室代码是别人写的,且基本算木有注释,也木有相关的文档.研究源码太耗时间了,由于聊天数据比较小,聊天室窗口是从messages表中读取的数据,所以打算当用户退出时,执行某一机制将Ajax_chat_messages表中对应聊天室的数据copy到一个新的Chat_messages表中,同时清除Aj

在复杂的项目开发中使用结对编程

在复杂的项目开发中使用结对编程 卢占辉译 在开发软件项目时,不仅写出相应功能的模块很重要:确保写出的模块的易维护性(bug 修复,代码重构)也同样重要. 主打互联网技术和门户网站的Perpetuum 手机公司曾开展了一个长期的软件项目,以研制出一套基于web的内容管理软件.Perpetuum公司的许多开发者都参与了这个项目.项目中大部分复杂的模块都是完全靠个人开发的(非多人协作完成).维护这些模块(即非多人协作完成的模块)非常困难.因为开发某一模块的人还需要开发新的模块或者维护另一既有模块,这需

SSH项目开发中,将jsp页面放在WEB-INF的原因解析

 在一些安全型要求比较高的项目开发中,我们经常看到jsp页面都被放在WEB-INF下面了.这是出于对安全性的考虑, 是为了代码的安全.这样实现起来虽然麻烦了点,而且页面跳转很不方便.但是整个项目的安全性就提高上去了!所 以还是建议在一些安全性要求比较高的项目里使用这种做法! 这是我写的一个例子,我就是将所有的JSP页面都放在WEB-INF下面,然后按照模块进行分配,course文件夹就是放和 课程信息管理相关的JSP页面.假如我们输入https://localhost:8080/项目名称/p

项目开发中db设计

项目开发中db设计 0.根据原型分析出数据的由来和数据间的关系(实体关系); 1.提取字段,通过powerDesigner设计表; 2.先不加约束,先只建立数据上的单向关联,有需要时在建立双向关联或中间表;3.也可以先建立外键关系,最后删除外键关系;(方便查看表的关系);4.对实体对象通常会补充的字段:     id         主键    entity        关联实体/自己    isDelete varchar(1)  Null    #是否删除    CreateEmpId v

项目开发中对设计模式的思考

前言: 做项目的时候经常会这样的体会:我的代码实现需求了,代码重用性也可以.由于前期需求分析不彻底,只考虑到一种情况,做出来的东西给用户测试的时候,发现又需要改动,这个时候又会觉得前期的设计太过复杂,改动也比较麻烦.当然问题的根本原因是需求分析不彻底,或者对业务敏感度不够.面向对象的封装特性的核心是封装变化点,由于没有察觉到业务变化点,也就无法封装变化点.基于这个问题,我总结的方法是(1)多考虑用户的潜在需求 (2)无法感知用户潜在需求的情况下,代码设计尽量简单,不要做过多设计和封装,在重构的时

项目开发中自定义字段设计原则

在开发系统过程中,做到自定义字段策略设置,目前这种功能是很多系统的标准配置,这样子可以简化后续增加字段的难度,并对自定义字段做管理. 自定义字段功能要注意到以下几点: 1.批量规划好要自定义字段的数据表.2.对自定义字段存放的表字典表做设计3.对自定义字段做不同的属性设计4.自定义字段的扩展设计 1.明确是哪个表需要自定义字段.如果是开发一套易用的系统,做开发的时候对用到的主表做统一的自定义字段设计.这样子方便在以后的开发应用中直接操作自定义功能就能增加字段.很多程序员在初写程序的时候,增加字段

记录在一次前后端分离的项目开发中遇到的坑

问题如下图: 在一次实际的项目开发中,我负责前端开发,使用的是vue+axios,后台使用的是php,由于我们的开发环境处于不同的域名和端口下, 所以出现了跨域问题,当然php服务端 可以直接解决这个问题,但是后端设置之后,前端每次都会先发出options请求,然后再post请求, 这说白了,就是每个接口请求两次.我们来分析下原因: 出于安全考虑,并不是所有域名访问后端服务都可以.其实在正式跨域之前,浏览器会根据需要发起一次预检(也就是option请求),用来让服务端返回允许的方法(如get.p

Android中网络框架的简单封装

个人博客 http://www.milovetingting.cn Android中网络框架的简单封装 前言 Android作为一款主要应用在移动终端的操作系统,访问网络是必不可少的功能.访问网络,最基本的接口有:HttpUrlConnection,HttpClient,而在后续的发展中,出现了Volley,OkHttp,Retrofit等网络封装库.由于各种原因,在实际的项目开发中,我们可能会需要在项目的版本迭代中,切换网络框架.如果对于网络框架没有好的封装,那么当需要切换网络框架时,可能就会