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

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

简单使用

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

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<Text>();
        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<string> uselist;
            List<string> noUseList;
            
            CustomExport.OnGetNoUseList(out noUseList);
            CustomExport.OnGetUseList(out uselist);

            // Get use and nouse list from custom export.
            object[] aCustomExport = new object[1];
            InvokeEditorMethod<ICustomExportPost>("OnGetUseList", ref aCustomExport);
            if (null != aCustomExport[0])
            {
                if (null != uselist)
                {
                    uselist.AddRange((List<string>)aCustomExport[0]);
                }
                else
                {
                    uselist = (List<string>)aCustomExport[0];
                }
            }

            aCustomExport[0] = null;
            InvokeEditorMethod<ICustomExportPost>("OnGetNoUseList", ref aCustomExport);
            if (null != aCustomExport[0])
            {
                if ((null != noUseList))
                {
                    noUseList.AddRange((List<string>)aCustomExport[0]);
                }
                else
                {
                    noUseList = (List<string>)aCustomExport[0];
                }
            }

            List<Type> exports = new List<Type>();
            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<Type> 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<int> outindex = new List<int>();
            List<int> refindex = new List<int>();
            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<Type> 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<Type> list, string name, int order)
        {
            HashSet<Type> exported = new HashSet<Type>();
            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<IntPtr>[] GetBindList() {");
            Write(file, "Action<IntPtr>[] 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<IntPtr>[] GetBindList() {
            Action<IntPtr>[] 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<Action<IntPtr>> list = new List<Action<IntPtr>>();
            //省略
            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#脚本一样直接可以把想要控制的组件挂到对应的脚本下,可以思考一下怎么实现。参考链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容