JoshWindsor 发表于 2022-2-15 06:30

xLua学习笔记-官网案例教程篇


[*]01_Helloworld: 快速入门的例子。
using UnityEngine;
using XLua;

namespace XLuaTest
{
    public class Helloworld : MonoBehaviour
    {
      // Use this for initialization
      void Start()
      {
            LuaEnv luaenv = new LuaEnv();
            luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");
            luaenv.Dispose();
      }

      // Update is called once per frame
      void Update()
      {

      }
    }
}
xLua的第一个教学案例,最重要的就是start这个函数,告诉我们在C#里要写Lua的代码,首先得先有一个Lua环境,也就是LuaEnv(LuaEnvironment)。上述代码做了三件事,创建环境,执行一串Lua代码,然后释放这个环境。
2. 02_U3DScripting: 展示怎么用lua来写MonoBehaviour。
xLua的第二个教学案例,在接触案例之前先讲一下什么是MonoBehaviour,这是所有脚本的基类,脚本属于组件的一种,挂载在游戏对象上,具体可以看一下Unity的类关系图。
这个链接是初步介绍,感觉讲的还行。
然后正式进入xLua第二个教学案例,这里最重要的就是给出了创建Lua环境的C#代码,后面的几个案例都用的这个代码,只是Lua脚本不同罢了,建议认真理解一下,可以结合我写的注释看(如有错漏,欢迎大佬指正,本人大三菜鸡,随缘分享)。
这段代码起到了什么作用呢?首先是创建总体Lua环境(static,所有脚本通用的),接着给每个挂载在游戏对象上的脚本组件(也就是类的实例)都创建了环境防止冲突,然后给出injections让你可以在Unity的Inspector面板中将你想传的Unity对象传到Lua侧,设置Lua self指针为C#脚本的this指针(脚本的this指针可以获取游戏对象的很多东西)让你在Lua侧可以获取本游戏对象的组件(其他对象通过injections传),然后把Lua的几个函数映射到C#来,你就可以在Lua写你想要的代码啦,最后释放脚本环境。
/*
* Tencent is pleased to support the open source community by making xLua available.
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using XLua;
using System;

namespace XLuaTest
{
    //这是一个序列化标签,说明这个类被序列化,之后可被Unity存储并重构,会出现在Unity的Inspector面板上
   
    public class Injection
    {
      public string name;
      public GameObject value;
    }

    //这个标签说明在引用的Lua代码里会调用C#的函数
   
    //这是一个继承自Unity脚本基类的类,使其可作为Unity脚本组件挂靠在游戏对象上
    public class LuaBehaviour : MonoBehaviour
    {
      //因为存储Lua代码的文件为txt格式,所以这里用文本资源引入
      public TextAsset luaScript;
      //可以通过外部设置将Unity组件引入到Lua中,只需先修改size,然后修改name和value即可
      public Injection[] injections;

      //总体的一个Lua环境,各个脚本环境的基础
      internal static LuaEnv luaEnv = new LuaEnv(); //all lua behaviour shared one luaenv only!
      internal static float lastGCTime = 0;
      //一个时间变量,用来GC的
      internal const float GCInterval = 1;//1 second

      //三个Action(无返回值和参数的delegate),正好用来存对应名字的Lua函数
      private Action luaStart;
      private Action luaUpdate;
      private Action luaOnDestroy;

      //脚本环境,也就是每一个挂靠在游戏对象上的脚本组件,即脚本对象,都有一个独立的脚本环境,但总体都是由类的一个luaEnv new出来的
      private LuaTable scriptEnv;

      void Awake()
      {
            scriptEnv = luaEnv.NewTable();

            //为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突
            //脚本环境的元表的__index方法为总体环境luaEnv.Global,也就是Lua的_G,保存了Lua的全局变量及全局函数
            LuaTable meta = luaEnv.NewTable();
            meta.Set("__index", luaEnv.Global);
            scriptEnv.SetMetaTable(meta);
            meta.Dispose();

            //将lua脚本的self设为当前脚本指针,就可以通过该指针获取游戏对象的component
            scriptEnv.Set("self", this);
            //将外部设置的所有Unity组件传入到Lua对应名字的对象中
            foreach (var injection in injections)
            {
                scriptEnv.Set(injection.name, injection.value);
            }
            //执行将TextAsset对象luaScript的Lua代码,代码块名为LuaTestScript,在scriptEnv中执行
            luaEnv.DoString(luaScript.text, "LuaTestScript", scriptEnv);

            //Lua里有awake就执行一下
            Action luaAwake = scriptEnv.Get<Action>("awake");
            //获取Lua里的仨函数并传给本地Action
            scriptEnv.Get("start", out luaStart);
            scriptEnv.Get("update", out luaUpdate);
            scriptEnv.Get("ondestroy", out luaOnDestroy);

            if (luaAwake != null)
            {
                luaAwake();
            }
      }

      // Use this for initialization
      void Start()
      {
            if (luaStart != null)
            {
                luaStart();
            }
      }

      // Update is called once per frame
      void Update()
      {
            if (luaUpdate != null)
            {
                luaUpdate();
            }
            //定时调用,用来清除Lua的未手动释放的LuaBase对象(比如:LuaTable, LuaFunction),以及其它一些事情
            if (Time.time - LuaBehaviour.lastGCTime > GCInterval)
            {
                luaEnv.Tick();
                LuaBehaviour.lastGCTime = Time.time;
            }
      }

      void OnDestroy()
      {
            if (luaOnDestroy != null)
            {
                luaOnDestroy();
            }
            //释放脚本环境
            luaOnDestroy = null;
            luaUpdate = null;
            luaStart = null;
            scriptEnv.Dispose();
            injections = null;
      }
    }
}
接下来看Lua侧的代码,这部分代码比较简单,start里获取组件,update旋转并换颜色。
local speed = 10
local lightCpnt = nil

function start()
        print("lua start...")
        print("injected object", lightObject)
      //返回该游戏对象的Light组件,typeof返回的是一个字符串,作为GetComponent的参数
        lightCpnt= lightObject:GetComponent(typeof(CS.UnityEngine.Light))
end

function update()
        local r = CS.UnityEngine.Vector3.up * CS.UnityEngine.Time.deltaTime * speed
      //传一个三元数给当前游戏对象旋转
        self.transform:Rotate(r)
        lightCpnt.color = CS.UnityEngine.Color(CS.UnityEngine.Mathf.Sin(CS.UnityEngine.Time.time) / 2 + 0.5, 0, 0, 1)
end

function ondestroy()
    print("lua destroy")
end3.03_UIEvent: 展示怎么用lua来写UI逻辑。
这一部分比较简单,Unity侧就是给Button上脚本之后,将InputField对象通过injections传给代码里的input变量,然后Lua侧就是获取Button组件后,给其添加点击事件输出input对象的inputfield组件的text,就是这么简单,看源码不到十行就懂了。
function start()
        print("lua start...")

        self:GetComponent("Button").onClick:AddListener(function()
                print("clicked, you input is '" ..input:GetComponent("InputField").text .."'")
        end)
end 4.04_LuaObjectOrented: 展示lua面向对象和C#的配合。
在看之前最好先了解一下C#的事件,下面这个链接讲的真的很好,强烈推荐。总的来说,就是发布者和订阅者,也就是设计模式里的观察者模式,发布者负责定义委托(告诉订阅者订阅我的事件应该用什么样的,一般都是无返回值,两个参数,一个发布者对象,一个事件细节数据,以EventHandler结尾)、定义事件(基于委托定义的,event后写委托名,一般命名为委托去掉EventHandler)、定义函数说明什么时候触发事件,到时候就执行这函数啪得执行事件把所有订阅者的方法一通调用就行了;订阅者就负责写自己收到事件后要调用的函数就行,发布者和订阅者写完之后,在主函数里先实例化发布者,再实例化订阅者,接着就给发布者的事件注册方法,告诉发布者,我对你这个事件很感兴趣,发生事件就告诉我,我早做准备,最后发布者开始执行会触发事件的方法就行。
接下来进入C#侧代码部分,Lua代码没有单独文件,作为string写在C#里了,看注释。这部分还是比较复杂的,是前四个案例里最难的,不管是C#还是Lua代码都有很好的知识点。
/*
* Tencent is pleased to support the open source community by making xLua available.
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/

using System;
using UnityEngine;
using XLua;

namespace XLuaTest
{
    //EventArgs是包含事件数据的类的基类,用于传递事件的细节
    public class PropertyChangedEventArgs : EventArgs
    {
      public string name;
      public object value;
    }

    public class InvokeLua : MonoBehaviour
    {
      //将lua的table映射到C# interface中,是官方推荐的用法,需要生成代码,要加标签
      
      public interface ICalc
      {
            //此处定义的事件的委托为EventHandler,然后委托的事件细节为PropertyChangedEventArgs,等价代码为
            //等价于public delegate void EventHandler(object sender, PropertyChangedEventArgs e);
            //public event EventHandler PropertyChanged;
            event EventHandler<PropertyChangedEventArgs> PropertyChanged;

            //调用lua table的Add方法
            int Add(int a, int b);
            //get和set都会去table里找数据,然后触发table元表_index方法的get_Item、set_Item
            int Mult { get; set; }

            //定义的索引器,使类可以像数组一样访问类成员
            object this { get; set; }
      }

      
      public delegate ICalc CalcNew(int mult, params string[] args);

      private string script = @"
                local calc_mt = {
                  __index = {
                        Add = function(self, a, b)
                            return (a + b) * self.Mult
                        end,
                        
                        get_Item = function(self, index)
                            return self.list
                        end,

                        set_Item = function(self, index, value)
                            self.list = value
                            self:notify({name = index, value = value})
                        end,
                        
                        add_PropertyChanged = function(self, delegate)
                                if self.notifylist == nil then
                                        self.notifylist = {}
                                end
                                table.insert(self.notifylist, delegate)
                            print('add',delegate)
                        end,
                                                
                        remove_PropertyChanged = function(self, delegate)
                            for i=1, #self.notifylist do
                                        if CS.System.Object.Equals(self.notifylist, delegate) then
                                                table.remove(self.notifylist, i)
                                                break
                                        end
                                end
                            print('remove', delegate)
                        end,

                        notify = function(self, evt)
                                if self.notifylist ~= nil then
                                        for i=1, #self.notifylist do
                                                self.notifylist(self, evt)
                                        end
                                end       
                        end,
                  }
                }

                Calc = {
                      New = function (mult, ...)
                        print(...)
                        return setmetatable({Mult = mult, list = {'aaaa','bbbb','cccc'}}, calc_mt)
                  end
                }
                ";
      // Use this for initialization
      void Start()
      {
            LuaEnv luaenv = new LuaEnv();
            Test(luaenv);//调用了带可变参数的delegate,函数结束都不会释放delegate,即使置空并调用GC
            luaenv.Dispose();
      }

      void Test(LuaEnv luaenv)
      {
            luaenv.DoString(script);
            //返回脚本里的Calc.New,可看lua代码描述
            CalcNew calc_new = luaenv.Global.GetInPath<CalcNew>("Calc.New");
            //下面将Lua的表转成了C#的接口
            ICalc calc = calc_new(10, "hi", "john"); //constructor
            Debug.Log("sum(*10) =" + calc.Add(1, 2));
            calc.Mult = 100;
            Debug.Log("sum(*100)=" + calc.Add(1, 2));

            //实质上就是lua里的table,会返回table.list
            Debug.Log("list=" + calc);
            Debug.Log("list=" + calc);

            //给属性改变事件注册方法
            calc.PropertyChanged += Notify;
            calc = "dddd";
            Debug.Log("list=" + calc);
            //将注册的方法注销
            calc.PropertyChanged -= Notify;

            calc = "eeee";
            Debug.Log("list=" + calc);
      }

      //订阅器,输出发布器的名字和事件细节的名字及值
      void Notify(object sender, PropertyChangedEventArgs e)
      {
            Debug.Log(string.Format("{0} has property changed {1}={2}", sender, e.name, e.value));
      }

      // Update is called once per frame
      void Update()
      {

      }
    }
}
5.05_NoGc: 展示怎么去避免值类型的GC。
页: [1]
查看完整版本: xLua学习笔记-官网案例教程篇