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

功能:
用戶輸入賬號密碼–>點擊登錄–>請求服務器–>服務器返回數據–>顯示玩家信息。

在這裏插入圖片描述
在這裏插入圖片描述
代碼:

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章