lua puremvc UI框架(unity)

Unity中使用lua来做UI部分开发时,如果项目规模较大的话,整一套合适的UI框架,提高共同开发效率和保证代码质量,统一规范开发人员的代码还是有必要的。避免各自关门按自己的风格行事,导致项目代码风格各异,模块功能代码冗余杂乱,诡异bug增加。

puremvc就是mvc框架中的一种,是前辈大佬们多年经验的结果,也发展了很多个编程语言版本,但一直没有lua版,所以使用lua按照puremvc框架思路实现了一番。
官网地址:https://www.baidu.com/link?url=1KDH29xY7ZKB2C_3p_8gxNp3FnevtWrq_TCbdk94apS&wd=&eqid=da63df06003d5a57000000035e00cc53

puremvc中使用了很多设计模式:
单例模式
观察者模式
命令模式
中介模式
代理模式
外观模式
框架大体设计目的直指向高复用模块化,低耦合代码目标。
puremvc文档官网上已经有了,而且介绍的很详细了,所以这里不再介绍,主要是lua实现样例

下面用Unity结合xlua实现了lua的puremvc UI框架。
但因为lua动态编译语言的特性,所以在lua版puremvc中定义接口意义不大,省略掉了接口的定义。
这里只实现了简要的功能,但也应用到了puremvc核心的功能,描述了puremvc在项目中UI框架中的大体使用样例。

项目源码地址:https://github.com/HengyuanLee/UnityLuaPuremvc.git

功能:
用户输入账号密码–>点击登录–>请求服务器–>服务器返回数据–>显示玩家信息。

lua puremvc UI框架(unity)_第1张图片
lua puremvc UI框架(unity)_第2张图片
代码:

Main.lua

require 'PureMVC/PureMVC'
require 'StartupMCmd'
require 'AppFacade'
require 'Network'

--游戏lua入口函数
function Main()
    print('start Main ...')
    AppFacade:GetInstance():Startup()
end

AppFacade.lua


AppFacade = class('AppFacade',PureMVC.Facade)
AppFacade.APP_STARTUP = 'APP_STARTUP'
local module = AppFacade

--ctor等同于class的构造方法,在new()时会被调用。
function module:ctor()
    self.super:ctor()
    --注册启动命令StartupMCmd,这是个宏命令。
    --注册为一个函数原因是实现命令的无状态性,需要时才被实例化出来,省内存。
    self:RegisterCommand(AppFacade.APP_STARTUP, function() return StartupMCmd.new() end)
end

--发出启动游戏命令。
function module:Startup()
    self:SendNotification(AppFacade.APP_STARTUP)
end

StartupMCmd.lua

require 'Login/LoginInitCmd'
require 'PlayerInfo/PlayerInfoInitCmd'

StartupMCmd = class('StartupMCmd', PureMVC.MacroCommand)

--这个类以MCmd后缀结尾是一个MacroCommand,
--内部添加了多个子Cmd,按添加顺序执行,
--宏Cmd一次性使用,执行完之后子Cmd就会被清空。
function StartupMCmd:InitializeMacroCommand()
    --添加2个UI系统模块的初始化Cmd
    self:AddSubCommand(function() return LoginInitCmd.new() end)
    self:AddSubCommand(function() return PlayerInfoInitCmd.new() end)
end

Network.lua

require 'PlayerInfo/LoginSuccessCmd'

--模拟与服务器交互
Network = {}
Network.CMD_LOGIN_SUCCESS = 'CMD_LOGIN_SUCCESS'
local module = Network


AppFacade:GetInstance():RegisterCommand(Network.CMD_LOGIN_SUCCESS, function() return LoginSuccessCmd.new() end)

--登录
function module.Login(username, password)
    if password == nil then
        print(debug.traceback())
    end
    print('请求登陆信息 username: '..username..' password: '..password)
    module.onLoginSuccess()
end
--模拟登陆成功
function module.onLoginSuccess()
    --假设数据
    local data = {
        id = 4634536,
        nickname = '小强',
        guildName = '武当山',
        level = 12,
        vip = 1,
        hp = 100,
        mp = 100
    }
    --获取服务器数据data成功后发出通知
    AppFacade:GetInstance():SendNotification(module.CMD_LOGIN_SUCCESS, data)
end

Login/LoginInitCmd.lua

require 'Login/LoginPanel'
require 'Login/LoginMediator'
require 'Login/LoginClickCmd'

LoginInitCmd = class('LoginInitCmd', PureMVC.SimpleCommand)

--Login功能模块初始化cmd,
--这里是有view 的Mediator实例化,model的Proxy参考PlayerInfo系统模块
function LoginInitCmd:Execute(notification)
    local panel = LoginPanel.new(LoginPanel.NAME)
    local mediator = LoginMediator.new(LoginPanel.NAME, panel)
    AppFacade:GetInstance():RegisterMediator(mediator)
    AppFacade:GetInstance():RegisterCommand(LoginClickCmd.CMD, function () return LoginClickCmd.new() end)
end

Login/LoginPanel.lua

--以Panel为后缀的类名对应Unity 里制作的UI预设
--如这里对应LoginPanel.prefab
--panel类等同于一个view,panel里只提供获取控件的接口,供mediator访问,panel内部不做复杂操作。
LoginPanel = class('LoginPanel')
LoginPanel.NAME = 'LoginPanel'
local module = LoginPanel

module.gameObject = nil
module.transform = nil

module.inputUsername = nil
module.inputPassword = nil
module.btnLogin = nil

function module:ctor(panelName)
    self.panelName = panelName
    self:Load(panelName)
end
--加载unity 里的LoginPanel.prefab预设,
--并且获取组件
function module:Load(panelName)
    self.gameObject = CS.ResourcesLoader.Instance:LoadPanel(panelName)
    self.transform = self.gameObject.transform
    self.gameObject:SetActive(false)
    self.inputUsername = self.transform:Find('InputUsername'):GetComponent(typeof(CS.UnityEngine.UI.InputField))
    self.inputPassword = self.transform:Find('InputPassword'):GetComponent(typeof(CS.UnityEngine.UI.InputField))
    self.btnLogin = self.transform:Find('BtnLogin'):GetComponent(typeof(CS.UnityEngine.UI.Button))
end
function module:Show()
    self.gameObject:SetActive(true)
end
function module:Hide()
    self.gameObject:SetActive(false)
end

Login/LoginMediator.lua

--用于对LoginPanel的各种操作,事件处理,数据填充等。
--PureMVC.Mediator同时继承了Notifier类,所以能够SendNotification
--PureMVC.Mediator实现了观察者功能,提供ListNotificationInterests()方法来填写感兴趣的Cmd,并在HandleNotification()回调。
--这说明了Mediator其实也是一个Cmd,以观察者模式集成了Cmd的功能。
LoginMediator = class('LoginMediator', PureMVC.Mediator)
local module = LoginMediator

function module:ctor(mediatorName, view)
    --覆盖了父类的构造方法,显示调用父类构造方法,
    --和其它语言有点不同,lua语法特性和class的定义决定了这样写
    module.super.ctor(self, mediatorName, view)
    self:init()
end
function module:init()
    self.View:Show()
    self.View.btnLogin.onClick:AddListener(
        function()
           self:onLoginClick() 
        end
    )
end
--点击登录事件
function module:onLoginClick()
    if self.View.inputUsername == nil then
        print(debug.traceback())
        print(self.View.__cname)
    end
    local username = self.View.inputUsername.text
    local password = self.View.inputPassword.text
    local body = {
        username = username,
        password = password
    }
    self:SendNotification(LoginClickCmd.CMD, body)
end
--指定方法ListNotificationInterests()中填写感兴趣的Cmd
function module:ListNotificationInterests()
    return {
        Network.CMD_LOGIN_SUCCESS
    }
end
--感兴趣的Cmd 在HandleNotification(notification)中收到回调
function module:HandleNotification(notification)
    if notification.Name == Network.CMD_LOGIN_SUCCESS then
        self.View:Hide()
    end
end

LoginClickCmd.lua

LoginClickCmd = class('LoginClickCmd', PureMVC.SimpleCommand)
local module = LoginClickCmd
module.CMD = 'LoginClickCmd'

--处理点击登录界面“登录”按钮的事件,向服务器发送登录消息。
function module:Execute(notification)
    local username = notification.Body.username
    local password = notification.Body.password
    Network.Login(username, password)
end

PlayerInfo/LoginSuccessCmd.lua

LoginSuccessCmd = class('LoginSuccessCmd', PureMVC.SimpleCommand)
local module = LoginSuccessCmd

--有这个类的存在,只是为了更新PlayerInfoProxy的数据,
--因为proxy和mediator不一样,数据模型proxy为了解耦不接收任何通知,
--但UI mediator需要作为观察者接收通知与用户进行互动。
function module:Execute(notification)
    local gameInfoProxy = AppFacade:GetInstance():RetrieveProxy(PlayerInfoPanel.NAME)
    gameInfoProxy:Refresh(notification.Body)
end

PlayerInfo/PlayerInitCmd.lua

require 'PlayerInfo/PlayerInfoPanel'
require 'PlayerInfo/PlayerInfoMediator'
require 'PlayerInfo/PlayerInfoProxy'

PlayerInfoInitCmd = class('PlayerInfoInitCmd', PureMVC.SimpleCommand)
local module = PlayerInfoInitCmd

--PlayerInfo系统模块初始化,包括mediator和proxy。
function module:Execute(notification)
    local panel = PlayerInfoPanel.new(PlayerInfoPanel.NAME)
    local mediator = PlayerInfoMediator.new(PlayerInfoPanel.NAME, panel)
    local proxy = PlayerInfoProxy.new(PlayerInfoPanel.NAME)
    AppFacade:GetInstance():RegisterMediator(mediator)
    AppFacade:GetInstance():RegisterProxy(proxy)
end

PlayerInfo/PlayerInfoMediator.lua

PlayerInfoMediator = class('PlayerInfoMediator', PureMVC.Mediator)
local module = PlayerInfoMediator

function module:ctor(mediatorName, View)
    module.super.ctor(self, mediatorName, View)
    self.View = View
end

function module:ListNotificationInterests()
    return {
        Network.CMD_LOGIN_SUCCESS,
        PlayerInfoProxy.CMD_REFRESH
    }
end
function module:HandleNotification(notification)
    if notification.Name == Network.CMD_LOGIN_SUCCESS then
        --登录成功时显示玩家信息
        self.View:Show()
    elseif notification.Name == PlayerInfoProxy.CMD_REFRESH then
        --PlayerInfoProxy的数据刷新时,重新填充显示的玩家数据
        local proxy = AppFacade:GetInstance():RetrieveProxy(PlayerInfoPanel.NAME)
        local data = proxy:GetData()
        if data ~= nil then
            local info = string.format(
                " 登录成功!\n 玩家信息 \n id: %d\n 昵称:%s\n 公会:%s \n 等级:%d \n vip:%d \n hp:%s \n mp:%d",
                data.id, 
                data.nickname, 
                data.guildName, 
                data.level, 
                data.vip,
                data.hp,
                data.mp)
            self.View.text.text = info
        end
    end
end

PlayerInfo/PlayerInfoPanel.lua

PlayerInfoPanel = class('PlayerInfoPanel')
PlayerInfoPanel.NAME = 'PlayerInfoPanel'
local module = PlayerInfoPanel

function module:ctor(panelName)
    self.panelName = panelName
    self:Load(panelName)
end

function module:Load(panelName)
    self.gameObject = CS.ResourcesLoader.Instance:LoadPanel(panelName)
    self.transform = self.gameObject.transform
    self.gameObject:SetActive(false)
    self.text = self.transform:Find('Text'):GetComponent(typeof(CS.UnityEngine.UI.Text))
end
function module:Show()
    self.gameObject:SetActive(true)
end
function module:Hide()
    self.gameObject:SetActive(false)
end

PlayerInfo/PlayerInfoProxy.lua

--Proxy继承了Notifer接口,可以发送命令SendNotification(),
--但不监听任何命令。
PlayerInfoProxy = class('PlayerInfoProxy', PureMVC.Proxy)
PlayerInfoProxy.CMD_REFRESH = 'PlayerInfoProxy'
local module = PlayerInfoProxy

function module:GetData()
    return self.Data
end
--提供修改数据的方法,只应该提供给服务器数据来时修改用。
function module:Refresh(data)
    self.Data = data
    self:SendNotification(PlayerInfoProxy.CMD_REFRESH)
end

你可能感兴趣的:(Unity开发)