找回密码
 立即注册
查看: 320|回复: 0

XLua的MVVM框架

[复制链接]
发表于 2021-8-10 13:57 | 显示全部楼层 |阅读模式
Loxodon.Framework.XLua 是一个XLua的开源的MVVM框架,它作为我的Unity3D的MVVM框架Loxodon.Framework的插件来使用。使用这个框架,可以做到完全使用Lua来编写游戏逻辑,并且遵循MVVM的开发习惯,支持数据绑定,有感兴趣的朋友可以去github下载。
下面是代码示例

    数据绑定示例
require("framework.System")

local Context = CS.Loxodon.Framework.Contexts.Context
local LuaBindingServiceBundle = CS.Loxodon.Framework.Binding.LuaBindingServiceBundle
local ObservableObject = require("framework.ObservableObject")
local ObservableDictionary = require("framework.ObservableDictionary")

---
--创建一个Account子视图模型
--@module Account
local Account = class("Account",ObservableObject)

function Account:ctor(t)
        --执行父类ObservableObject的构造函数,这个重要,否则无法监听数据改变
        Account.super.ctor(self,t)
       
        if not (t and type(t)=="table") then
                self.id = 0
                self.username = ""
                self.Password = ""
                self.email = ""
                self.birthday = os.time({year =1970, month = 00, day =00, hour =00, min =00, sec = 00})
                self.address = ""
        end
end

---
--创建一个数据绑定示例的视图模型
--@module DatabindingViewModel
local DatabindingViewModel = class("DatabindingViewModel",ObservableObject)

function DatabindingViewModel:ctor(t)
        --执行父类ObservableObject的构造函数,这个重要,否则无法监听数据改变
        DatabindingViewModel.super.ctor(self,t)
       
        if not (t and type(t)=="table") then
                self.account = Account()
                self.remember = false
                self.username = ""
                self.email = ""
                self.errors = ObservableDictionary()
        end
       
end

function DatabindingViewModel:submit()
        if #self.username < 1 then
                --注意C#字典类型的使用方式,通过set_Item或者get_Item 访问
                self.errors:set_Item("errorMessage","Please enter a valid username.")
                return
        end
       
        if #self.email < 1 then
                --注意C#字典类型的使用方式,通过set_Item或者get_Item 访问
                self.errors:set_Item("errorMessage","Please enter a valid email.")
                return
        end
       
        self.errors:Clear()
       
        self.account.username = self.username
        self.account.email = self.email
        self.account.remember = self.remember
end

---
--创建一个数据绑定视图,扩展DatabindingExample.cs 对象,这里的target是从C#脚本传过来的
--@module DatabindingExample
local M = class("DatabindingExample",target)

function M:awake()
        local context = Context.GetApplicationContext()
        local container = context:GetContainer()
       
        --初始化Lua的数据绑定服务,一般建议在游戏的C#启动脚本创建
        local bundle = LuaBindingServiceBundle(container)
        bundle:Start();
end

function M:start()
        --初始化Account子视图模型
        local account = Account({
                        id = 1,
                        username = "test",
                        password = "test",
                        email = "yangpc.china@gmail.com",
                        birthday = os.time({year =2000, month = 03, day =03, hour =00, min =00, sec = 00}),
                        address = "beijing",
                        remember = true
                })
       
        --初始化视图模型
        self.viewModel = DatabindingViewModel({
                        account = account,
                        username = "",
                        email = "",
                        remember = true,
                        errors = ObservableDictionary()
                })
       
        self:BindingContext().DataContext = self.viewModel
       
        --进行数据绑定
        local bindingSet = self:CreateBindingSet();
       
        bindingSet:Bind(self.username):For("text"):To("account.username"):OneWay()
        bindingSet:Bind(self.password):For("text"):To("account.password"):OneWay()
        bindingSet:Bind(self.email):For("text"):To("account.email"):OneWay()
        bindingSet:Bind(self.remember):For("text"):To("account.remember"):OneWay()
        bindingSet:Bind(self.birthday):For("text"):ToExpression(function(vm)
                        return os.date("%Y-%m-%d",vm.account.birthday)
                end ,"account.birthday"):OneWay()
        bindingSet:Bind(self.address):For("text"):To("account.address"):OneWay()
        bindingSet:Bind(self.errorMessage):For("text"):To("errors['errorMessage']"):OneWay()       
        bindingSet:Bind(self.usernameInput):For("text","onEndEdit"):To("username"):TwoWay()
        bindingSet:Bind(self.emailInput):For("text","onEndEdit"):To("email"):TwoWay()
        bindingSet:Bind(self.rememberInput):For("isOn","onValueChanged"):To("remember"):TwoWay()
        bindingSet:Bind(self.submit):For("onClick"):To("submit"):OneWay()
       
        bindingSet:Build()
end

return M
    在Lua中使用协程的示例
require("framework.System")

local util = require("xlua.util")
local Executors = CS.Loxodon.Framework.Execution.Executors
local ProgressResult = CS.Loxodon.Framework.Asynchronous["ProgressResult`1[System.Single]"]

---
-- 在Lua中使用协程的例子
-- 将一个Lua函数通过util.cs_generator包装成一个C#的迭代器IEnumerator,然后用Executors调用
--@module CoroutineExample
local M=class("CoroutineExample",target)

function M:start()
        --[[--
        在C#脚本LuaBehaviour中定义的属性或者是通过Variables配置而虚拟出来的属性,都可以在Lua脚本中通过self.xxx 来访问
        如下示例,通过self.startButton self.stopButton 访问启动和停止按钮,通过self.slider 访问滑动条
        ]]       
        self.startButton.onClick:AddListener(function()
                        print("onClick Start")
                        self.loadResult = self:Load()
                        self.loadResult:Callbackable():OnProgressCallback(function(progress)
                                        --printf("progress:%0.2f",progress) -- 打印进度,%0.2f 是小数点2位的浮点数
                                        self.slider.value = progress
                                end)
                end)
       
        self.stopButton.onClick:AddListener(function()
                        if self.loadResult then
                                self.loadResult:Cancel()
                                self.loadResult = nil
                        end
                        print("onClick Stop")
                end)
       
        print("lua start...")
end

---
-- 加载
function M:Load()
        local result = ProgressResult(true)
        Executors.RunOnCoroutineNoReturn(util.cs_generator(function() self:doLoad(result) end))
        return result
end

---
-- 模拟一个加载任务
function M:doLoad(promise)
        print("task start")
       
        for i = 1, 50 do
                --如果有取消请求,即调用了ProgressResult的Cancel()函数,则终止任务
                if promise.IsCancellationRequested then
                        break
                end
               
                promise:UpdateProgress(i/50) --更新任务进度               
               
                --这里coroutine.yield中可以不传入参数,则表示是每帧执行一次,
                --也可以传入所有继承了YieldInstruction的参数,如:UnityEngine.WaitForSeconds(0.1)
                --还可以传入一个IEnumerator对象,如:AsyncResult.WaitForDone()
                coroutine.yield(CS.UnityEngine.WaitForSeconds(0.1))--等待0.1秒
        end       
        promise:UpdateProgress(1)
        promise:SetResult()        --设置任务执行完成
        print("task end")
end

return M
    创建窗口视图的示例
LoginViewModel.lua
require("framework.System")

local Context = CS.Loxodon.Framework.Contexts.Context
local SimpleCommand = CS.Loxodon.Framework.Commands.SimpleCommand
local AsyncTask = CS.Loxodon.Framework.Asynchronous["AsyncTask`1[System.Object]"]

local ObservableObject = require("framework.ObservableObject")
local ObservableDictionary = require("framework.ObservableDictionary")
local InteractionRequest = require("framework.InteractionRequest")

---
--模块
--@module LoginViewModel
local M=class("LoginViewModel",ObservableObject)

--[[--
构造函数
@param #table self
@param #table t 初始化参数
]]
function M:ctor(t)
        M.super.ctor(self,t)

        self.username = self.globalPreferences:GetString("LAST_USERNAME", "");
        self.password = ""
        self.account = nil
        self.errors = ObservableDictionary()

        self.loginCommand = SimpleCommand(function() self:login() end,true)
        self.cancelCommand = SimpleCommand(function() self.interactionFinished:Raise(nil) end,true)

        self.interactionFinished = InteractionRequest(self)
        self.toastRequest = InteractionRequest(self)
end

function M:validateUsername()
        if not self.username or self.username == '' or not string.gmatch(self.username, "^[a-zA-Z0-9_-]{4,12}$") then
                self.errors:set_Item("username",self.localization:GetText("login.validation.username.error", "Please enter a valid username."))
                return false
        else
                self.errors:Remove("username");
                return true
        end
end

function M:validatePassword()
        if not self.password or self.password == '' or not string.gmatch(self.password, "^[a-zA-Z0-9_-]{4,12}$") then
                self.errors:set_Item("password",self.localization:GetText("login.validation.password.error", "Please enter a valid password."))
                return false
        else
                self.errors:Remove("password");
                return true
        end
end

function M:login()
        self.account = nil;
    self.loginCommand.Enabled = false --by databinding, auto set button.interactable = false.

        local task = nil

        if not (self:validateUsername() and self:validatePassword()) then
                task = AsyncTask(function() return nil end)
        else
                task = AsyncTask(function()
            local result = self.accountService:Login(self.username, self.password)
            local account = result:Synchronized():WaitForResult()
            if account then            
                Context.GetApplicationContext():GetMainLoopExcutor():RunOnMainThread(function()
                    self.globalPreferences:SetString("LAST_USERNAME", self.username)
                    self.globalPreferences:Save()
                end)
            end
            return account
        end)
        end

        task:OnPostExecute(function(account)
        if account then
            --login success

            self.account = account
            self.interactionFinished:Raise(nil) --Interaction completed, request to close the login window
        else
            --Login failure

            local tipContent = self.localization:GetText("login.failure.tip", "Login failure.")
            self.toastRequest:Raise(tipContent) --show toast
        end
    end):OnError(function(e)
        local tipContent = self.localization:GetText("login.exception.tip", "Login exception.")
        self.toastRequest:Raise(tipContent) --show toast
    end):OnFinish(function()
        self.loginCommand.Enabled = true --by databinding, auto set button.interactable = true.
    end):Start()
end

return M
LoginWindow.lua
require("framework.System")

local Loading = CS.Loxodon.Framework.Views.Loading
local Toast = CS.Loxodon.Framework.Views.Toast

---
--模块
--@module LoginWindow
local M=class("LoginWindow",target)

function M:onCreate(bundle)
        local bindingSet = self:CreateBindingSet();

        bindingSet:Bind():For("onInteractionFinished"):To("interactionFinished")
    bindingSet:Bind():For("onToastShow"):To("toastRequest")

        bindingSet:Bind(self.username):For ("text", "onEndEdit"):To ("username"):TwoWay ()
        bindingSet:Bind(self.usernameErrorPrompt):For ("text"):To ("errors['username']"):OneWay ()
        bindingSet:Bind(self.password):For ("text","onEndEdit"):To ("password"):TwoWay ()
        bindingSet:Bind(self.passwordErrorPrompt):For ("text"):To ("errors['password']"):OneWay ()
        bindingSet:Bind(self.confirmButton):For ("onClick"):To ("loginCommand")
        bindingSet:Bind(self.cancelButton):For ("onClick"):To ("cancelCommand")
        bindingSet:Build()
end

function M:onInteractionFinished(sender, args)
    self:Dismiss()
end

function M:onToastShow(sender, args)
    local notification = args.Context
    if not notification then
        return
        end

    Toast.Show(self, notification, 2);
end

return M下载和安装

请在github下载项目 Loxodon.Framework,在Docs/XLua目录下面有个插件包Loxodon.Framework.XLua,安装之后,就可以完全使用Lua来开发游戏,安装步骤请看Docs/XLua/Readme 文件,如果疑问,请加技术支持群,在项目介绍页面能看到。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 05:05 , Processed in 0.089627 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表