Unity + XLua 简单框架结构
一.unity内创建lua脚本
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.ProjectWindowCallback;
using UnityEngine;
public class GenLua
{
[MenuItem("Assets/Create/Lua Script", false, 80)]
public static void CreatNewLua()
{
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0,
ScriptableObject.CreateInstance<MyDoCreateScriptAsset>(),
GetSelectedPathOrFallback() + "/New Lua.lua",
null,
"Assets/Editor/Template/NewLua.txt");
}
public static string GetSelectedPathOrFallback()
{
string path = "Assets";
foreach (UnityEngine.Object obj in Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets))
{
path = AssetDatabase.GetAssetPath(obj);
if (!string.IsNullOrEmpty(path) && File.Exists(path))
{
path = Path.GetDirectoryName(path);
break;
}
}
return path;
}
}
class MyDoCreateScriptAsset : EndNameEditAction
{
public override void Action(int instanceId, string pathName, string resourceFile)
{
UnityEngine.Object o = CreateScriptAssetFromTemplate(pathName, resourceFile);
ProjectWindowUtil.ShowCreatedAsset(o);
}
internal static UnityEngine.Object CreateScriptAssetFromTemplate(string pathName, string resourceFile)
{
string fullPath = Path.GetFullPath(pathName);
StreamReader streamReader = new StreamReader(resourceFile);
string text = streamReader.ReadToEnd();
streamReader.Close();
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(pathName);
text = Regex.Replace(text, "#NAME#", fileNameWithoutExtension);
bool encoderShouldEmitUTF8Identifier = true;
bool throwOnInvalidBytes = false;
UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier, throwOnInvalidBytes);
bool append = false;
StreamWriter streamWriter = new StreamWriter(fullPath, append, encoding);
streamWriter.Write(text);
streamWriter.Close();
AssetDatabase.ImportAsset(pathName);
return AssetDatabase.LoadAssetAtPath(pathName, typeof(UnityEngine.Object));
}
}
代码转载自雨松MoMo
将此段代码放入unity的Editor目录下,然后在相应目录下创建NewLua.txt模板,即可像创建C#脚本那样创建lua脚本
NewLua.txt 模板
local #NAME# = class()
-- 自定义数据
-- 构造函数
function #NAME#:Ctor(params)
end
-- 开始函数
function #NAME#:Start()
end
return #NAME#
在unity中新建一个LuaScript文件夹,用于保存lua脚本
二.创建 LuaManager 脚本
可以创建lua脚本后,直接进入正题。在C#中执行Lua脚本,我们可以new一个LuaEnv环境变量来使用Dostring方法来执行lua脚本。LuaManager用于更方便的执行lua脚本。代码如下
public class ST_LuaMannager : MonoBehaviour
{
private static readonly object padlock = new object();
private static ST_LuaMannager instance;
//统一管理Update
public event Action luaUpdates;
//单例类
public static ST_LuaMannager Instance
{
get
{
if (instance == null)
{
lock (padlock)
{
if (instance == null)
{
GameObject go = new GameObject("LuaManager");
DontDestroyOnLoad(go);
instance = go.AddComponent<ST_LuaMannager>();
}
}
}
return instance;
}
}
LuaEnv luaEnv;
float lastGcTime = 0;
//luaGC回收时间见隔
const float GCInterval = 1;
//全局唯一lua环境
public LuaEnv MyLuaEnv
{
get
{
if (luaEnv == null)
{
luaEnv = new LuaEnv();
luaEnv.AddLoader(LuaLoader);
//加载lua全局变量
luaEnv.DoString("require 'ST/ST_LuaEnv'");
//luaEnv.DoString("require 'TestClass'");
}
return luaEnv;
}
}
//自定义Loader
public static byte[] LuaLoader(ref string filename)
{
byte[] byArrayReturn = null;
string luaPath = Application.dataPath + "/LuaScripts/" + filename.Replace(".","/") + ".lua";
string strLuaConent = File.ReadAllText(luaPath);
byArrayReturn = System.Text.Encoding.UTF8.GetBytes(strLuaConent);
return byArrayReturn;
}
//封装dostring 方法
public object[] Dostring(string str, string chunkName = "chunk")
{
var ret = MyLuaEnv.DoString(str, chunkName);
return ret;
}
private void Update()
{
//定时GC
if (Time.time - lastGcTime > GCInterval)
{
MyLuaEnv.Tick();
lastGcTime = Time.time;
}
//管理luaUpdate
if(luaUpdates != null)
{
luaUpdates();
}
}
}
这里采用自定义Loader的方法加载Lua,并使用全局唯一的lua环境,ST_LuaEnv 里面主要包含lua环境的公共变量和函数。Update方法里实现lua定时垃圾回收,执行绑定lua 脚本的Update方法。
测试
分别创建ST_LuaEnv.lua,Tests.lua,TestEnv.cs脚本
ST_LuaEnv
TestValue = 2
function Add(a,b)
return a + b
end
Tests.lua
print(Add(TestValue,12))
TestEnv.cs
public class TestEnv : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
ST_LuaMannager.Instance.Dostring("require 'Tests'");
}
}
创建空节点,运行Unity,结果如下
三.Lua面向对象
让Lua脚本具有面向对象特性可以使我们编程更加方便,在ST_LuaEnv里面添加面向对象方法代码如下
function clone(object)
local lookup_table = {}
local function copyObj(object)
if type(object) ~= "table" then
return object
elseif lookup_table[object] then
return lookup_table[object]
end
local new_table = {}
lookup_table[object] = new_table
for key, value in pairs(object) do
new_table[copyObj(key)] = copyObj(value)
end
return setmetatable(new_table, getmetatable(object))
end
return copyObj(object)
end
local _class = {}
--[[
@desc: class
author:{}
time:2022-01-24 17:47:45
--@super: 父类
@return:返回一个类
]]
function class(super)
local class_type = {}
class_type.Ctor = false
class_type.super = super
class_type.New = function(...)
local obj = {}
do
local create
create = function(c, ...)
if c.super then
create(c.super, ...)
end
if c.Ctor then
c.Ctor(obj, ...)
end
end
create(class_type, ...)
end
--setmetatable(obj,{ __index=_class[class_type] })(原)
setmetatable(obj, {__index = clone(_class[class_type])})
return obj
end
local vtbl = {}
_class[class_type] = vtbl
setmetatable(class_type, {
__newindex = function(t, k, v)
vtbl[k] = v
end
})
if super then
setmetatable(vtbl, {
__index = function(t, k)
local ret = _class[super][k]
vtbl[k] = ret
return ret
end
})
end
return class_type
end
这里的面向对象方法引用自云风的个人空间 并对代码做了一定的修改,满足我们的要求,测试代码如下
local TestClass = class()
-- 自定义数据
TestClass.ta = {x = 1}
local a1 = TestClass.New();
local s = a1.ta;
print(a1.ta.x)
s.x = 55
local a2 = TestClass.New();
print(a2.ta.x)
原代码输出如下(可以看到,修改a1实列的ta.x值后基类的ta.x值也一并修改了)
修改后
四.GameObject绑定lua脚本
创建ST_LuaBehaviours.cs 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
using System;
using DG.Tweening;
[LuaCallCSharp]
public class ST_LuaBehaviours : MonoBehaviour
{
//文件在LuaScript下的路径
public string LuaFile;
//需要绑定的对象数组
public Injection[] injections;
//绑定的luaTable
LuaTable luaTable;
//Lua调C#方法
private Action<LuaTable> luaStart;
private Action<LuaTable> luaUpdate;
private Action<LuaTable> luaOnDestroy;
//Update方法代理
private Action luaUpadteDe;
private void Awake()
{
object[] kRet = ST_LuaMannager.Instance.Dostring($"return require '{LuaFile}'.New()", LuaFile);
if (null == kRet)
{
Debug.LogError("Luafile need return local table");
return;
}
luaTable = kRet[0] as LuaTable;
//设置绑定的对象
foreach (var injection in injections)
{
luaTable.Set(injection.name, injection.value);
}
//设置luaCom
luaTable.Set("luaCom", this);
//设置gameObject
luaTable.Set("gameObject", gameObject);
//设置transform
luaTable.Set("transform", transform);
Action luaAwake = luaTable.Get<Action>("awake");
//自动调用方法
luaTable.Get("Start", out luaStart);
luaTable.Get("Update", out luaUpdate);
luaTable.Get("OnDestroy", out luaOnDestroy);
//调用luaAwake方法
if (luaAwake != null)
{
luaAwake();
}
}
//脚本生效时注册Update
private void OnEnable()
{
if (luaUpdate != null)
{
luaUpadteDe = () => { luaUpdate(luaTable); };
ST_LuaMannager.Instance.luaUpdates += luaUpadteDe;
}
}
//脚本失效时注销Update
private void OnDisable()
{
if (luaUpadteDe != null)
{
ST_LuaMannager.Instance.luaUpdates -= luaUpadteDe;
}
}
void Start()
{
//执行luaStart
if (luaStart != null)
{
luaStart(luaTable);
}
}
//对象销毁时
private void OnDestroy()
{
//调用luaOnDestroy方法
if (luaOnDestroy != null)
{
luaOnDestroy(luaTable);
}
luaOnDestroy = null;
luaUpdate = null;
luaStart = null;
injections = null;
luaUpadteDe = null;
}
}
[Serializable]
public class Injection
{
public string name;
public GameObject value;
}
测试
创建TestMono.lua文件
local TestMono = class()
-- 自定义数据
-- 构造函数
function TestMono:Ctor(params)
end
-- 开始函数
function TestMono:Start()
end
function TestMono:Update()
self.transform:Rotate(CS.UnityEngine.Vector3(0,1,0));
self.capsule.transform:Rotate(CS.UnityEngine.Vector3(1,0,0))
end
return TestMono
在场景中创建一个Cube,和一个Capsule,并将ST_LuaBehaviours挂到Cube上,如图:
运行游戏,可以看到Cube,和Capsule旋转,那么测试通过。