Loxodon.Framework.XLua 是一個XLua的開源的MVVM框架,它作爲Unity3D的MVVM框架Loxodon.Framework的插件來使用。使用這個框架,能夠作到徹底使用Lua來編寫遊戲邏輯,而且遵循MVVM的開發習慣,支持數據綁定,有感興趣的朋友能夠去github下載。javascript
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.luajava
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.luagit
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 文件,若是疑問,請加技術支持羣,在項目介紹頁面能看到。github