[Unity]SLua的使用与源码浅析

SLua学习前提:
1.熟悉Lua基本语法
2.熟悉Lua与C交互(C#交互类似)
3.熟悉Unity基本组件以及在

简单使用

1.Slua下载配置
2.导出Lua接口

[Unity]SLua的使用与源码浅析_第1张图片
SLua菜单

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#脚本一样直接可以把想要控制的组件挂到对应的脚本下,可以思考一下怎么实现。参考链接

你可能感兴趣的:([Unity]SLua的使用与源码浅析)