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
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 文件,如果疑问,请加技术支持群,在项目介绍页面能看到。
原创声明,本文系作者授权云+社区发表,未经许可,不得转载。
如有侵权,请联系 yunjia_community@tencent.com 删除。
我来说两句