SLua学习前提:
1.熟悉Lua基本语法
2.熟悉Lua与C交互(C#交互类似)
3.熟悉Unity基本组件以及在
简单使用
1.Slua下载配置
2.导出Lua接口
All->导出/清空全部接口
Unity->导出UnityEngine或者UnityEngine.UI下类的接口。
Custom->自定义导出类
3rdDll->导出动态链接库
- test one 创建一个cube
在C#中如何创建呢?
void Start () {
GameObject.CreatePrimitive(PrimitiveType.Cube);
}
Lua中创建方式
import "UnityEngine" --导包
UnityEngine.GameObject.CreatePrimitive(UnityEngine.PrimitiveType.Cube)
同样很简单。
那么C#该如何调用Lua文件呢?
首先回忆一下C中调用方式
//初始化全局L
LuaState L = luaL_newstate();
//打开库
luaL_openlibs(L);
if (luaL_loadfile(L,fileName))
{
printf("error\n");
}
在C#中也类似调用
LuaSvr lua_svr = new LuaSvr ();
lua.init(null, () =>
{
lua.start("test");
});
LuaSvr将调用方式稍微做了封装,原理类似。
- test two 修改text文字
C#写法:
void Start () {
Text textComponent= GameObject.Find("Canvas/Text").GetComponent();
textComponent.text = "slua";
}
Lua写法:
import "UnityEngine"
local textComponent= GameObject.Find("Canvas/Text"):GetComponent("Text")
textComponent.text="123"
原理浅析
- step one:跟踪C#接口导出
//LuaCodeGen.cs
[MenuItem("SLua/Unity/Make UnityEngine")]
//导出需要的lua文件接口(UnityEngine下的)
static public void Generate()
{
if (IsCompiling) {
return;
}
//反射UnityEngine类
Assembly assembly = Assembly.Load("UnityEngine");
//获取此程序集中定义的公共类型,这些公共类型在程序集外可见。
Type[] types = assembly.GetExportedTypes();
List uselist;
List noUseList;
CustomExport.OnGetNoUseList(out noUseList);
CustomExport.OnGetUseList(out uselist);
// Get use and nouse list from custom export.
object[] aCustomExport = new object[1];
InvokeEditorMethod("OnGetUseList", ref aCustomExport);
if (null != aCustomExport[0])
{
if (null != uselist)
{
uselist.AddRange((List)aCustomExport[0]);
}
else
{
uselist = (List)aCustomExport[0];
}
}
aCustomExport[0] = null;
InvokeEditorMethod("OnGetNoUseList", ref aCustomExport);
if (null != aCustomExport[0])
{
if ((null != noUseList))
{
noUseList.AddRange((List)aCustomExport[0]);
}
else
{
noUseList = (List)aCustomExport[0];
}
}
List exports = new List();
string path = GenPath + "Unity/";
foreach (Type t in types)
{
//Generate(t, path)遍历导出每个类 后面跟踪一下
if (filterType(t, noUseList, uselist) && Generate(t, path))
exports.Add(t);
}
//以上代码导出所有系统类
//调用static void GenerateBind(List list, string name, int order,string path)跟踪一下
GenerateBind(exports, "BindUnity", 0, path);
if(autoRefresh)
AssetDatabase.Refresh();
Debug.Log("Generate engine interface finished");
}
//跟踪一
static bool Generate(Type t, string path)
{
return Generate(t, null, path);
}
static bool Generate(Type t, string ns, string path)
{
if (t.IsInterface)
return false;
CodeGenerator cg = new CodeGenerator();
cg.givenNamespace = ns;
cg.path = path;
return cg.Generate(t);
}
//最终调用
public bool Generate(Type t)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
if (!t.IsGenericTypeDefinition && (!IsObsolete(t) && t != typeof(YieldInstruction) && t != typeof(Coroutine))
|| (t.BaseType != null && t.BaseType == typeof(System.MulticastDelegate)))
{
if (t.IsEnum)
{
StreamWriter file = Begin(t);
WriteHead(t, file);
RegEnumFunction(t, file);
End(file);
}
else if (t.BaseType == typeof(System.MulticastDelegate))
{
if (t.ContainsGenericParameters)
return false;
string f = DelegateExportFilename(path, t);
StreamWriter file = new StreamWriter(f, false, Encoding.UTF8);
file.NewLine = NewLine;
WriteDelegate(t, file);//跟踪一下
file.Close();
return false;
}
else
{
funcname.Clear();
propname.Clear();
directfunc.Clear();
StreamWriter file = Begin(t);
WriteHead(t, file);
WriteConstructor(t, file);
WriteFunction(t, file,false);
WriteFunction(t, file, true);
WriteField(t, file);
RegFunction(t, file);
End(file);
if (t.BaseType != null && t.BaseType.Name.Contains("UnityEvent`"))
{
string basename = "LuaUnityEvent_" + _Name(GenericName(t.BaseType)) + ".cs";
string f = path + basename;
string checkf = LuaCodeGen.GenPath + "Unity/" + basename;
if (!File.Exists(checkf)) // if had exported
{
file = new StreamWriter(f, false, Encoding.UTF8);
file.NewLine = NewLine;
WriteEvent(t, file);
file.Close();
}
}
}
return true;
}
return false;
}
//有点长,可以大概过一下
void WriteDelegate(Type t, StreamWriter file)
{
string temp = @"
using System;
using System.Collections.Generic;
using LuaInterface;
namespace SLua
{
public partial class LuaDelegation : LuaObject
{
static internal int checkDelegate(IntPtr l,int p,out $FN ua) {
int op = extractFunction(l,p);
if(LuaDLL.lua_isnil(l,p)) {
ua=null;
return op;
}
else if (LuaDLL.lua_isuserdata(l, p)==1)
{
ua = ($FN)checkObj(l, p);
return op;
}
LuaDelegate ld;
checkType(l, -1, out ld);
if(ld.d!=null)
{
ua = ($FN)ld.d;
return op;
}
LuaDLL.lua_pop(l,1);
l = LuaState.get(l).L;
ua = ($ARGS) =>
{
int error = pushTry(l);
";
temp = temp.Replace("$TN", t.Name);
temp = temp.Replace("$FN", SimpleType(t));
MethodInfo mi = t.GetMethod("Invoke");
List outindex = new List();
List refindex = new List();
temp = temp.Replace("$ARGS", ArgsList(mi, ref outindex, ref refindex));
Write(file, temp);
this.indent = 4;
for (int n = 0; n < mi.GetParameters().Length; n++)
{
if (!outindex.Contains(n))
Write(file, "pushValue(l,a{0});", n + 1);
}
Write(file, "ld.pcall({0}, error);", mi.GetParameters().Length - outindex.Count);
int offset = 0;
if (mi.ReturnType != typeof(void))
{
offset = 1;
WriteValueCheck(file, mi.ReturnType, offset, "ret", "error+");
}
foreach (int i in outindex)
{
string a = string.Format("a{0}", i + 1);
WriteCheckType(file, mi.GetParameters()[i].ParameterType, i + offset, a, "error+");
}
foreach (int i in refindex)
{
string a = string.Format("a{0}", i + 1);
WriteCheckType(file, mi.GetParameters()[i].ParameterType, i + offset, a, "error+");
}
Write(file, "LuaDLL.lua_settop(l, error-1);");
if (mi.ReturnType != typeof(void))
Write(file, "return ret;");
Write(file, "};");
Write(file, "ld.d=ua;");
Write(file, "return op;");
Write(file, "}");
Write(file, "}");
Write(file, "}");
}
最后生成了每一个类的导出文件
//跟踪二
static void GenerateBind(List list, string name, int order,string path)
{
CodeGenerator cg = new CodeGenerator();
cg.path = path;
cg.GenerateBind(list, name, order);
}
// class CodeGenerator
public void GenerateBind(List list, string name, int order)
{
HashSet exported = new HashSet();
string f = System.IO.Path.Combine(path , name + ".cs");
StreamWriter file = new StreamWriter(f, false, Encoding.UTF8);
file.NewLine = NewLine;
Write(file, "using System;");
Write(file, "using System.Collections.Generic;");
Write(file, "namespace SLua {");
Write(file, "[LuaBinder({0})]", order);
Write(file, "public class {0} {{", name);
Write(file, "public static Action[] GetBindList() {");
Write(file, "Action[] list= {");
foreach (Type t in list)
{
WriteBindType(file, t, list, exported);
}
Write(file, "};");
Write(file, "return list;");
Write(file, "}");
Write(file, "}");
Write(file, "}");
file.Close();
}
上面步骤导出了BindUnity.cs 生成了GetBindList() 获取所有系统类导出清单
using System;
using System.Collections.Generic;
namespace SLua {
[LuaBinder(0)]
public class BindUnity {
public static Action[] GetBindList() {
Action[] list= {
Lua_UnityEngine_AsyncOperation.reg,
Lua_UnityEngine_AssetBundleCreateRequest.reg,
Lua_UnityEngine_AssetBundleRequest.reg,
Lua_UnityEngine_Object.reg,
Lua_UnityEngine_AssetBundle.reg,
Lua_UnityEngine_AssetBundleManifest.reg,
Lua_UnityEngine_SendMessageOptions.reg,
Lua_UnityEngine_PrimitiveType.reg,
Lua_UnityEngine_Space.reg,
Lua_UnityEngine_RuntimePlatform.reg,
//省略
}
那么生成的List是怎么使用的呢?
private void doBind(object state)
{
IntPtr L = (IntPtr)state;
List> list = new List>();
//省略
var assemblyName = "Assembly-CSharp";
Assembly assembly = Assembly.Load(assemblyName);
list.AddRange(getBindList(assembly,"SLua.BindUnity"));
list.AddRange(getBindList(assembly,"SLua.BindUnityUI"));
list.AddRange(getBindList(assembly,"SLua.BindDll"));
list.AddRange(getBindList(assembly,"SLua.BindCustom"));
//...省略
当LuaSvr将会把所有类加载到lua环境中,所以在lua代码中import后可以调用该类。
Slua导出类生成规则。以Object类为例:
static public void reg(IntPtr l) {
getTypeTable(l,"UnityEngine.Object");
//方法导出
addMember(l,GetInstanceID);
addMember(l,Destroy_s);
addMember(l,DestroyImmediate_s);
addMember(l,FindObjectsOfType_s);
addMember(l,DontDestroyOnLoad_s);
addMember(l,DestroyObject_s);
addMember(l,Instantiate_s);
addMember(l,FindObjectOfType_s);
addMember(l,op_Equality);
addMember(l,op_Inequality);
//变量导出(可参照api)
addMember(l,"name",get_name,set_name,true);
addMember(l,"hideFlags",get_hideFlags,set_hideFlags,true);
createTypeMetatable(l,constructor, typeof(UnityEngine.Object));
}
方法导出
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static public int GetInstanceID(IntPtr l) {
try {
UnityEngine.Object self=(UnityEngine.Object)checkSelf(l);
//调用C#中方法
var ret=self.GetInstanceID();
//返回值压栈
pushValue(l,true);
pushValue(l,ret);
return 2;
}
catch(Exception e) {
return error(l,e);
}
}
变量导出
由于Lua调用C以方法的形式调用,所以稍微包装了一下
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static public int get_name(IntPtr l) {
try {
UnityEngine.Object self=(UnityEngine.Object)checkSelf(l);
pushValue(l,true);
pushValue(l,self.name);
return 2;
}
catch(Exception e) {
return error(l,e);
}
}
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static public int set_name(IntPtr l) {
try {
UnityEngine.Object self=(UnityEngine.Object)checkSelf(l);
string v;
checkType(l,2,out v);
self.name=v;
pushValue(l,true);
return 1;
}
catch(Exception e) {
return error(l,e);
}
}
最后addMember方法
// LuaObject.cs
protected static void addMember(IntPtr l, LuaCSFunction func)
{
checkMethodValid(func);
pushValue(l, func);
string name = func.Method.Name;
if (name.EndsWith("_s"))
{
name = name.Substring(0, name.Length - 2);
LuaDLL.lua_setfield(l, -3, name);
}
else
LuaDLL.lua_setfield(l, -2, name);
}
protected static void addMember(IntPtr l, LuaCSFunction func, bool instance)
{
checkMethodValid(func);
pushValue(l, func);
string name = func.Method.Name;
LuaDLL.lua_setfield(l, instance ? -2 : -3, name);
}
protected static void addMember(IntPtr l, string name, LuaCSFunction get, LuaCSFunction set, bool instance)
{
checkMethodValid(get);
checkMethodValid(set);
int t = instance ? -2 : -3;
LuaDLL.lua_createtable(l, 2, 0);
if (get == null)
LuaDLL.lua_pushnil(l);
else
pushValue(l, get);
LuaDLL.lua_rawseti(l, -2, 1);
if (set == null)
LuaDLL.lua_pushnil(l);
else
pushValue(l, set);
LuaDLL.lua_rawseti(l, -2, 2);
LuaDLL.lua_setfield(l, t, name);
}
protected static void addMember(IntPtr l, int v, string name)
{
LuaDLL.lua_pushinteger(l, v);
LuaDLL.lua_setfield(l, -2, name);
}
所以在lua中调用C#方法形式为 :
local text_ui= GameObject.Find("Canvas/Text")--调用静态方法
local textComponent= text_ui:GetComponent("Text")--调用成员方法
textComponent.text="123"--设置变量等价于textComponent["text"] = "123"
Slua的分析告一段落,是不是觉得用Lua控制组件比较麻烦,不能像C#脚本一样直接可以把想要控制的组件挂到对应的脚本下,可以思考一下怎么实现。参考链接