Xlua是騰訊研發的一款Lua開源插件,為Unity、 .Net、 Mono等C#環境增加Lua腳本編程的能力,借助xLua,這些Lua代碼可以方便的和C#相互調用,在游戲中,該技術多用於熱更新。可以在GitHub上搜索XLua進行下載,如果網速太慢,也可以在gitee上下載
C#執行Lua腳本
我們在學習每一個課程的時候,最先接觸的都是“Hello world”,在XLua中,如何執行該語句呢?將下載的Xlua項目打開,並且創建一個新的腳本,引入XLua命名空間,並掛載到游戲物體。
//聲明一個Lua虛擬機
public static LuaEnv lua;
private void Start()
{
//對虛擬機進行實例化
lua = new LuaEnv();
//用C#的UnityEngine.Debug.Log打印日志
lua.DoString("CS.UnityEngine.Debug.Log('Hello world')");
lua.Dispose();
}
此時,運行項目,可以看到在控制台中打印出了 Hello world
Tips💁♂:一個LuaEnv實例對應Lua虛擬機,出於開銷的考慮,建議全局唯一。
通過Lua.DoString
函數,我們可以在Lua語言中快速執行C#代碼,但是當Lua腳本內容較多時,將不是很方便執行,這時可以將腳本寫在Lua文件中,並C#中執行.
首先在Unity中創建Resources
文件夾,並在里面創建一個txt文本文件,命名為"Demo1.lua.txt"(其要求的是一個文本文檔,文件名中的.lua並沒有其他含義,僅僅是起標識作用),在文本中加入CS.UnityEngine.Debug.Log("Hello world,load file")
,接下來加載該文件
private void Start()
{
//對虛擬機進行實例化
lua = new LuaEnv();
//方式一:通過TextAsset加載
TextAsset asset = Resources.Load<TextAsset>("LuaDemo1.lua");
lua.DoString(asset.text);
//方式二:通過Require加載(常用),只能使用Resources與內置的路徑的lua文件,不能自定義路徑
lua.DoString("require 'LuaDemo1'");
//方式三:自定義Loader加載,使用Require加載有不能自定義路徑等缺點,通過自定義Loader則可以解決上述問題
lua.AddLoader(DemoLoader);
lua.DoString("require 'LuaDemo1'");
}
//自定義Loader函數,執行DoString時會自動調用該函數,里面可以實現自己的邏輯
private byte[] DemoLoader(ref string filepath)
{
string path = filepath + ".lua";
TextAsset asset = Resources.Load<TextAsset>(path);
return asset.bytes;
}
獲取Lua中的變量
首先我們修改lua文本文檔LuaDemo1
中的內容,定義下面兩個變量
x = 123;
y = "456"
接下來在C#中獲取x和y的值,Global是Lua中的一個全局表,里面包含了所有的全局變量
lua.DoString("require 'LuaDemo1'");
//獲取Lua中的值
int x = lua.Global.Get<int>("x");
string y = lua.Global.Get<string>("y");
Debug.Log(x + " , " + y); //打印 123 , 456
//修改Lua中的值
lua.Global.Set("x", 321);
Debug.Log(lua.Global.Get<int>("x")); //打印 321
獲取Lua中的Table
如果Lua腳本中包含Table,又該怎么調用呢?我們在Lua文本中添加Table,如下所示
x = 123;
y = "456"
--定義person表
person =
{
name = "slayer",
age = 8,
"only string",
999,
speak = function()
print("this is a function in Person")
end
}
--通過.的方式為person表添加函數
function person.sayName(self)
print(self.name)
end
--通過:的方式為person表添加函數 等價於 person.getAge(self)
function person:getAge()
print(self.age)
end
C#若要獲取Lua中Table值,可以通過類或者接口建立映射,然后獲取其值,下面展示如何使用接口獲取
private void Start()
{
lua.DoString("require 'LuaDemo1'");
//上面person表中,對於"only string",999這樣單獨的值,可以通過List建立映射
List<object> list = new List<object>();
list = lua.Global.Get<List<object>>("person");
foreach (var val in list)
{
Debug.Log(val); //分別打印"only string",999
}
IPersonal person = lua.Global.Get<IPersonal>("person");
Debug.Log(person.name); //輸出 "slayer"
Debug.Log("Sum:" + person.getSum(12, 34)); //輸出 46
}
//定義IPersonal接口,注意這里必須使用 CSharpCallLua 標簽
//並且里面的屬性和方法要和Lua腳本中的person表對應
[CSharpCallLua]
public interface IPersonal
{
string name { get; set; }
string age { get; set; }
void sayName();
int getSum(int num1, int num2);
}
獲取Lua中的函數
我們先在Lua腳本中定義兩個函數,如下所示:
function m_Print(msg)
print(msg)
end
function Add(a,b)
return a + b
end
要在C#中調用這兩個函數,需要使用委托,對於沒有返回值的,可以使用C#自帶的Action,有返回值則使用Func,當然也可以自定義委托
lua.DoString("require 'LuaDemo1'");
//通過Action獲取Lua中的映射,並執行該函數
Action<string> mPrint = lua.Global.Get<Action<string>>("m_Print");
mPrint("Hello world");
Func<int, int, int> add = lua.Global.Get<Func<int, int, int>>("Add");
int ans = add(1, 2);
Debug.Log("Add ans:" + ans); //輸出 3
錯誤信息:InvalidCastException: This type must add to CSharpCallLua:XXXX
執行上述代碼時,可能會出現上述錯誤,該錯誤表面當前使用的類型沒有加上CSharpCallLua
標簽,此時我們需要將該標簽添加到對應的地方,正常情況下錯誤將會消失.但有時候,明明添加了該標簽,還是提示這個錯誤,這時通過下面三個步驟一般都可以解決.
-
方式一:在Unity的Player Setting中將API Compatibility Level設置為 .NET 4.X
-
方式二:點擊XLua -> Clear Generated Code -> Generated Code
-
方式三:在Assets文件夾中,找到XLua -> Examples,找到ExampleGenConfig.cs文件,在CSharpCallLua中按照格式添加你要調用的方法
再不行就只能換一個版本重試了.
XLua調用C#靜態方法
在上一節中我們學習了怎么通過C#調用XLua打印“Hello world”,那么XLua如何調用C#呢?一行代碼輕松搞定
首先創建一個txt文件保存XLua腳本內容,我們命名為“LuaDemo2.lua.txt”
,里面的內容如下
--調用C#中Debug.Log函數
--Lua通過CS來訪問C#的代碼,后面的UnityEngine為命名空間
--CS.UnityEngine.Debug.Log("Hello world")
--在寫代碼時經常需要調用CS.UnityEngine里面的函數,為了方便我們可以定義一個變量將其保存起來
--使用local關鍵字定義一個局部變量 下面代碼實現的效果與上面相同
local cu = CS.UnityEngine
cs.Debug.Log("Hello world")
--調用其他方法、字段也與之類似,例如
--print(cu.Time.deltaTime)
要執行這行代碼,我們創建一個C#腳本,並執行Lua文件
lua.DoString("require 'LuaDemo2'"); //打印Hello world
XLua訪問自定義的C#類
在C#中定義一個名為People的類,內容如下:
public class People
{
public string name;
public int age;
public void Show()
{
Debug.Log(name + ":" + age);
}
}
接下來在XLua中對其進行訪問
--找到People類並對其進行實例化,等價於:
--local people = CS.People
--people = people()
local people = CS.People()
people.name = "taylor"
people.age = 23
people:Show() --打印 taylor:23
XLua查找游戲對象
在unity場景中創建一個Cube,怎么在Lua中修改它的名字呢?
local cug = CS.UnityEngine.GameObject
local cube = cug.Find("Cube")
cube.name = "Change"
調用腳本后可以看到,場景中的Cube
成功改名為Change
XLua實現Unity內置函數,如Awake,Update...
先在Lua腳本中定義一個start函數,函數名稱隨意
function luaStart()
print("this is lua start")
end
在C#中調用它,對於lua元表,簡單說明一下,點擊查看
Lua 查找一個表元素時的規則,其實就是如下 3 個步驟:
1.在表中查找,如果找到,返回該元素,找不到則繼續
2.判斷該表是否有元表,如果沒有元表,返回 nil,有元表則繼續。
3.判斷元表有沒有 index 方法,如果 index 方法為 nil,則返回 nil;如果 index 方法是一個表,則重復 1、2、3;如果 __index 方法是一個函數,則返回該函數的返回值。
private void Start()
{
//加載上面的lua腳本
asset = Resources.Load<TextAsset>("LuaDemo2.lua");
//定義一個table
LuaTable table = lua.NewTable();
//為table設置元表
LuaTable metaTable = lua.NewTable();
metaTable.Set("__index", lua.Global);
table.SetMetaTable(metaTable);
metaTable.Dispose();
table.Set("self", this);
lua.DoString(asset.text, "LuaDemo2.lua", table);
//通過Action獲取lua中的函數
Action _luaStart = table.Get<Action>("luaStart");
//執行,Awake和Update類似,只是在不同的地方調用,例如如果實現了_luaUpdate則在Update函數中調用
_luaStart();
}
XLua控制UI事件
修改前面的luaStart函數為下所示,(注意,C#調用的腳本需要掛在Button按鈕上,因為文中使用了self:GetComponent)
--通過lua為unity中的ui添加事件
function luaStart()
print("This is lua Start")
input = CUG.Find("InputField")
--查找到按鈕並且為按鈕添加事件
self:GetComponent("Button").onClick:AddListener(function()
val = input:GetComponent("InputField").text
if(val == "")
then
--print("Can't input nil")
input:GetComponent("InputField").text = "Can't input nil"
else
--print("clicked, you input is '" ..val .."'")
input:GetComponent("InputField").text = "clicked, you input is '" ..val .."'"
end
end)
end