• Unity Xlua热更新框架(五):Lua和UI管理


    8. Lua管理器

    image.png
    :::info
    Lua存在两种加载器,一种默认加载器(env.DoString("require(‘test’)"直接用了默认加载其),直接调用StreamingAssets中的脚本);一种是自定义加载器(env.AddLoader(Envpath)),优先于默认加载器(下文DoString就是从自定义加载器的路径读取的),并且当Lua代码执行require函数时,自定义加载器会尝试获得文件的内容,并通过虚拟机解析执行。
    :::
    注意:BuildPipeline.BuildAssetBundles没法build构建.lua文件,只能构建.bytes
    xLua加自定义loader是很简单的,只涉及到一个接口:
    :::info
    public delegate byte[] CustomLoader(ref string filepath);
    public void LuaEnv.AddLoader(CustomLoader loader)
    :::
    通过AddLoader可以注册个回调,该回调参数是字符串,lua代码里头调用require时,参数将会透传给回调,回调中就可以根据这个参数去加载指定文件,如果需要支持调试,需要把filepath修改为真实路径传出。该回调返回值是一个byte数组,如果为空表示该loader找不到,否则则为lua文件的内容。

    public class Manager : MonoBehaviour
    {
        private static LuaManager _lua;
        public static LuaManager Lua
        {
            get { return _lua; }
        }
    
        public void Awake()
        {
            _resource = this.gameObject.AddComponent<ResourceManager>();
            _lua = this.gameObject.AddComponent<LuaManager>();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    lua管理:

    • 异步加载(bundle是异步加载,如果上一步是require加载lua,下一步就调用怎么办?)
    • 同步使用
    • 预加载(想要同步使用,就要使用预加载方法)

    所有lua(bundle)先加载出来,文件内容全部缓存在内存中,使用时直接调用。

    public void ParseVersionFile()
    {
        //拿到版本文件路径
        string url = Path.Combine(PathUtil.BundleResourcePath, AppConst.FileListName);
        //对文件进行读取
        string[] data = File.ReadAllLines(url);
    
        //解析文件信息
        for (int i = 0; i < data.Length; i++)
        {
            BundleInfo bundleInfo = new BundleInfo();
            string[] info = data[i].Split('|');
            bundleInfo.AssetName = info[0];
            bundleInfo.BundleName = info[1];
            //list特性:本质是数组,可动态扩容
            bundleInfo.Dependeces = new List<string>(info.Length - 2);
            for (int j = 2; j < info.Length; j++)
            {
                bundleInfo.Dependeces.Add(info[j]);
            }
            m_BundleInfos.Add(bundleInfo.AssetName, bundleInfo);
    
            //查找luaScripts下的lua文件,添加到luamanager中
            if(info[0].IndexOf("LuaScripts") > 0)
            {
                Manager.Lua.LuaNames.Add(info[0]);
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    public static readonly string LuaPath = "Assets/BuildResources/LuaScripts/";
    
    • 1
    //为什么lua不要pathUtil传回一个相对路径?因为lua调用脚本就是用的相对路径调用,传进来的路径就是相对路径
    public void LoadLua(string assetName, Action<UnityEngine.Object> action = null)
    {
        LoadAsset(assetName, action);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    关闭物体挂载hotUpdate脚本,修改为EditorMode
    调用Lua的函数,要么是Lua文件中直接function();要么通过C#调用lua的函数访问LuaEnv.Global就可以了,例如:luaenv.Global.Get(“a”)

    XLua.LuaFunction func = Manager.Lua.LuaEnv.Global.Get(“main”);
    func.Call();
    //不能这么用,因为全局查找效率比较低,后续脚本中有更好的用法

    using System;
    using System.Collections;
    using System.IO;
    using System.Collections.Generic;
    using UnityEngine;
    using XLua;
    
    /// 
    /// LuaNames是ResourceManager中解析版本文件ParseVersionFile时赋值的,根据LuaNames,从Bundle(或Editor本地)加载LoadLua,加载好就放入m_LuaScripts,全部加载完清空LuaNames
    /// 初始化Init:添加一个回调,创建new LuaEnv()虚拟机,虚拟机LuaEnv.AddLoader(loader)(外部StartLua调用DoString找lua,loader调用GetLuaScript找到lua脚本),按模式加载lua(bundle或者editor)
    /// 调用的前提是全部加载完了,执行了回调通知加载完毕,才可以调用。即InitOk
    /// 调用:1.Init;2.StartLua(内部是LuaEnv.DoStringloader调用GetLuaScript找到lua脚本);3.调用函数XLua.LuaFunction func = Global.Get("xxx");func.Call();
    /// 
    public class LuaManager : MonoBehaviour
    {
        //所有的lua文件名,获取所有lua,然后进行预加载,ResourceManager查找lua文件放进来
        public List<string> LuaNames = new List<string>();
    
        //缓存lua脚本内容
        private Dictionary<string, byte[]> m_LuaScripts;
    
        //定义一个lua虚拟机,消耗比较大,,全局只需要一个,,需要using XLua;
        public LuaEnv LuaEnv;
    
        Action InitOK;
        //如果是Editor模式下,直接从luapath就把所有lua读取到字典中了,然后在调用,属于同步加载后同步使用的情况
        //但是在其他模式下,需要从bundle异步加载lua需要等待,如果等待时start就调用了,属于异步加载同步使用的情况,需要预加载
        //需要创建一个回调通知
        public void Init(Action init)
        {
            InitOK += init;
            //初始化虚拟机
            LuaEnv = new LuaEnv();
            //外部调用require时,会自动调用loader来获取文件
            LuaEnv.AddLoader(Loader);
    
            m_LuaScripts = new Dictionary<string, byte[]>();
    
    #if UNITY_EDITOR
            if (AppConst.GameMode == GameMode.EditorMode)
                EditorLoadLuaScript();
            else
    #endif
                LoadLuaScript();
        }
    
        /// 
        /// 启动对应脚本的lua文件,实际上是吧启动文件的string传给Loader去加载,然后Loader通过GetLuaScript加载出来lua
        /// 
        /// 
        public void StartLua(string name)
        {
            LuaEnv.DoString(string.Format("require '{0}'",name));
        }
    
        /// 
        /// lua里面调用require后面的参数会传到name
        /// 
        /// 
        /// 
        byte[] Loader(ref string name)
        {
            return GetLuaScript(name);
        }
        /// 
        /// 和Loader配合使用找到要调用的指定目录lua文件
        /// 
        /// 
        /// 
        public byte[] GetLuaScript(string name)
        {
            //为什么替换掉.换成/,,因为一般使用require ui.login.register
            name = name.Replace(".", "/");
            //自定义的lua后缀名是.bytes
            string fileName = PathUtil.GetLuaPath(name);
    
            byte[] luaScript = null;
            //从集合中拿到缓存的lua内容
            if(!m_LuaScripts.TryGetValue(fileName, out luaScript))
            {
                Debug.LogError("lua script is not exist:" + fileName);
            }
            return luaScript;
        }
    
        /// 
        /// 非编辑器模式下,从路径中获取ab包,从ab包拿到文件
        /// 
        void LoadLuaScript()
        {
            foreach (var name in LuaNames)
            {
                //异步的需要一个回调(=>后面那一坨,当LoadLua执行时完,执行回调并invoke把结果返回),obj就是返回的lua的对象
                Manager.Resource.LoadLua(name, (UnityEngine.Object obj) =>
                {
                    //LoadLua调用完会把bundle加载好的bundleRequest.asset传进来用obj接受
                    //把这个lua根据名称添加到m_LuaScripts中
                    AddLuaScript(name, (obj as TextAsset).bytes);
                    //在ResourceManager中解析版本文件时加载所有lua文件到LuaNames
                    //如果LuaNames全部都加载到m_LuaScripts集合中,就清空LuaNames,退出循环
                    if (m_LuaScripts.Count >= LuaNames.Count)
                    {
                        //所有lua文件加载完成了。就可以执行使用lua函数的方法了
                        InitOK?.Invoke();
                        LuaNames.Clear();
                        LuaNames = null;
                    }
                });
            }
        }
    
        /// 
        /// 把LuaNames里的名字对应lua文件本身里面的内容,放到集合内
        /// 
        /// 
        /// 
        public void AddLuaScript(string assetsName, byte[] luaScript)
        {
            //为了放置重复添加,用这种方式可以直接覆盖
            m_LuaScripts[assetsName] = luaScript;
        }
    
    #if UNITY_EDITOR
        //编辑器模式下直接加载lua文件,并把lua名字和内容放到集合内
        void EditorLoadLuaScript()
        {
            //搜索所有lua文件
            string[] luaFiles = Directory.GetFiles(PathUtil.LuaPath, "*.bytes", SearchOption.AllDirectories);
            for (int i = 0; i < luaFiles.Length; i++)
            {
                string fileName = PathUtil.GetStandardPath(luaFiles[i]);
                //读取lua文件
                byte[] file = File.ReadAllBytes(fileName);
                //把读取的lua文件添加进去
                AddLuaScript(PathUtil.GetUnityPath(fileName), file);
            }
    		InitOK?.Invoke();
        }
    #endif
    
        private void Update()
        {
            //释放lua内存
            if (LuaEnv != null)
            {
                LuaEnv.Tick();
            }
        }
    
        private void OnDestroy()
        {
            //虚拟机需要销毁掉
            if (LuaEnv != null)
            {
                LuaEnv.Dispose();
                LuaEnv = null;
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    public class GameStart : MonoBehaviour
    {
        public GameMode GameMode;
        // Start is called before the first frame update
        void Start()
        {
            AppConst.GameMode = this.GameMode;
            DontDestroyOnLoad(this);
    
            Manager.Resource.ParseVersionFile();
            Manager.Lua.Init(
                () =>
                {
                    //初始化完成之后(lua都加载完),在执行回调
                    Manager.Lua.StartLua("main"); //输入的文件名
                    //输入的是函数名
                    XLua.LuaFunction func = Manager.Lua.LuaEnv.Global.Get<XLua.LuaFunction>("Main");
                    func.Call();
                }
                );        
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    9. LuaBehaviour

    9-1. LuaBehaviour

    逻辑分离
    绑定:写C#函数的习惯分离到Lua中,把Lua的函数写成和C#一样的,当执行C#时就知道执行哪个Lua脚本离的哪个逻辑

    rec-screen-_1_.gifimage.pngimage.pngxLua官方demo

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using XLua;
    using System;
    
    namespace XLuaTest
    {
        //拖拽进来的灯
        [System.Serializable]
        public class Injection
        {
            public string name;
            public GameObject value;
        }
    
        [LuaCallCSharp]
        public class LuaBehaviour : MonoBehaviour
        {
        	//读取的lua脚本的内容
            public TextAsset luaScript;
            public Injection[] injections;
    
            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 GC的间隔
    
        	//action引用lua的委托
            private Action luaStart;
            private Action luaUpdate;
            private Action luaOnDestroy;
    
        	//脚本的运行环境
            private LuaTable scriptEnv;
    
            void Awake()
            {
                scriptEnv = luaEnv.NewTable();
    
                // 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突
                LuaTable meta = luaEnv.NewTable();
                meta.Set("__index", luaEnv.Global);
                scriptEnv.SetMetaTable(meta);
                meta.Dispose();
    
            	//把这个脚本的实例注入到lua的self,让self = this
                scriptEnv.Set("self", this);
                foreach (var injection in injections)
                {
                	//令injection.name = injection.value
    				//即lightObject = light对象,因此lua可以直接用
                    scriptEnv.Set(injection.name, injection.value);
                }
    
            	//把LuaTestScript脚本绑定到scriptEnv的运行环境
                luaEnv.DoString(luaScript.text, "LuaTestScript", scriptEnv);
    			
    			//如果lua的awake不为空,C#的awake执行时调用lua的awake
                Action luaAwake = scriptEnv.Get<Action>("awake");
            	//上下两种定义一样,重载类型不同
                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();
                }
                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;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    这个脚本内的Awake调用LuaAwake,Start调用LuaStart,Update调用LuaUpdate,Destroy调用LuaDestroy,,,前面通过Env.Get拿到函数

    local speed = 10
    local lightCpnt = nil
    
    function start()
      print("lua start...")
      print("injected object", lightObject)
      --C#里的Injection变量定义了lightObject,Lua没有定义,但可以直接用
      --见上面代码块蓝色部分
      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")
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    创建我们自己的lua运行脚本
    image.pngLuaBehaviour是个父类,将来会有别的脚本继承他,例如UI,3DObject,他们的逻辑不同,父类只提供Unity的生命周期,特定的方法在子类中实现。

    using System;
    using UnityEngine;
    using XLua;
    
    public class LuaBehaviour : MonoBehaviour
    {
        //全局只能有一个LuaEnv
        private LuaEnv m_LuaEnv = Manager.Lua.LuaEnv;
        protected LuaTable m_ScriptEnv;
        private Action m_LuaAwake;
        private Action m_LuaStart;
        private Action m_LuaUpdate;
        private Action m_LuaOnDestroy;
    
        void Awake()
        {
            m_ScriptEnv = m_LuaEnv.NewTable();
    
            // 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突
            LuaTable meta = m_LuaEnv.NewTable();
            meta.Set("__index", m_LuaEnv.Global);
            m_ScriptEnv.SetMetaTable(meta);
            meta.Dispose();
    
            m_ScriptEnv.Set("self", this);
            m_ScriptEnv.Get("Awake", out m_LuaAwake);
            m_ScriptEnv.Get("Start", out m_LuaStart);
            m_ScriptEnv.Get("Update", out m_LuaUpdate);
    
            m_LuaAwake?.Invoke();
        }
    
        // Start is called before the first frame update
        void Start()
        {
            m_LuaStart?.Invoke();
        }
    
        // Update is called once per frame
        void Update()
        {
            m_LuaUpdate?.Invoke();
        }
    
        //父类的需要是保护级,因为子类更特殊有其他需要进行的操作
        protected virtual void Clear()
        {
            m_LuaOnDestroy = null;
            m_LuaAwake = null;
            m_LuaStart = null;
            //运行环境释放掉
            m_ScriptEnv?.Dispose();
            m_ScriptEnv = null;
        }
    
        //两个不一定同时触发,退出的时候不会调用OnDestroy,所以都要写Clear
        private void OnDestroy()
        {
            m_LuaOnDestroy?.Invoke();
            Clear();
        }
        private void OnApplicationQuit()
        {
            Clear();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    9-2. UILogic

    如何能在Awake之前把变量传进去?prefab添加脚本手动赋值可以,,但是框架不能这样做,不能全都手动赋值,,,只有这一种办法,,只能舍弃unity中使用awake,start,需要在Unity中模拟出awake和start的特性并提供给Lua使用这种特性(awake实例化之后最先调用,实例化到销毁周期内只触发一次,隐藏和激活不会触发awake;;;;start加载和激活都会从触发),start刷新UI,awake加载一些配置和数据
    开发者:调用API,传入一个UI预设的名字和lua脚本的名字,自动绑定C#脚本在UI预设上,并且执行Lua脚本

    public class LuaBehaviour : MonoBehaviour
    {
        //全局只能有一个LuaEnv
        private LuaEnv m_LuaEnv = Manager.Lua.LuaEnv;
        protected LuaTable m_ScriptEnv;
        private Action m_LuaInit;
        private Action m_LuaUpdate;
        private Action m_LuaOnDestroy;
    
        public string luaName;
        void Awake()
        {
            m_ScriptEnv = m_LuaEnv.NewTable();
    
            // 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突
            LuaTable meta = m_LuaEnv.NewTable();
            meta.Set("__index", m_LuaEnv.Global);
            m_ScriptEnv.SetMetaTable(meta);
            meta.Dispose();
    
            m_ScriptEnv.Set("self", this);
        }
    
        //用Init来代替unity的awake
        public virtual void Init(string luaName)
        {
            m_LuaEnv.DoString(Manager.Lua.GetLuaScript(luaName), luaName, m_ScriptEnv);
            m_ScriptEnv.Get("Update", out m_LuaUpdate);
            m_ScriptEnv.Get("OnInit", out m_LuaInit);
    
            m_LuaInit?.Invoke();
        }
        // Start直接删掉
        // Update is called once per frame
        void Update()
        {
            m_LuaUpdate?.Invoke();
        }
    
        //父类的需要是保护级,因为子类更特殊有其他需要进行的操作
        protected virtual void Clear()
        {
            m_LuaOnDestroy = null;
            //运行环境释放掉
            m_ScriptEnv?.Dispose();
            m_ScriptEnv = null;
    		m_LuaInit = null;
    		m_LuaUpdate = null;
        }
    
        //两个不一定同时触发,退出的时候不会调用OnDestroy,所以都要写Clear
        private void OnDestroy()
        {
            m_LuaOnDestroy?.Invoke();
            Clear();
        }
        private void OnApplicationQuit()
        {
            Clear();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    创建两个脚本Scripts/Framework/Behaviour/UILogic.cs和Scripts/Framework/Manager/UIManager.cs(对外提供OpenUI的接口)
    UILogic继承LuaBehaviour,实现UI自己的Open和Close方法

    using System;
    
    //继承LuaBehaviour
    public class UILogic : LuaBehaviour
    {
        Action m_LuaOnOpen;
        Action m_LuaOnClose;
        public override void Init(string luaName)
        {
            base.Init(luaName);
            m_ScriptEnv.Get("OnOpen", out m_LuaOnOpen);
            m_ScriptEnv.Get("OnClose", out m_LuaOnClose);
        }
    
        public void OnOpen()
        {
            m_LuaOnOpen?.Invoke();
        }
        public void OnClose()
        {
            m_LuaOnClose?.Invoke();
        }
        protected override void Clear()
        {
            base.Clear();
            m_LuaOnOpen = null;
            m_LuaOnClose = null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    using System.Collections.Generic;
    using UnityEngine;
    
    public class UIManager : MonoBehaviour
    {
        //用名字作为key,对象作为value,,,缓存UI,,后期会用对象池管理
        Dictionary<string, GameObject> m_UI = new Dictionary<string, GameObject>();
    
        /// 
        /// 传入一个ui名字和lua名字,自动给ui预制体绑定C#脚本,自动执行lua脚本
        /// 
        /// ui名字
        /// lua名字
        public void OpenUI(string uiName, string luaName)
        {
            GameObject ui = null;
            //如果ui已经加载过了(从ab包取出放到Dictionary中),就只执行OnOpen(Start),不在执行Init(Awake)
            if(m_UI.TryGetValue(uiName, out ui))
            {
                UILogic uiLogic = ui.GetComponent<UILogic>();
                uiLogic.OnOpen();
                return;
            }
    
            Manager.Resource.LoadUI(uiName, (UnityEngine.Object obj) =>
            {
                ui = Instantiate(obj) as GameObject;
                m_UI.Add(uiName, ui);
                //给UI预制体绑定UILogic的C#脚本
                UILogic uiLogic = ui.AddComponent<UILogic>();
                //初始化这个lua脚本(Awake)
                uiLogic.Init(luaName);
                //UI的Start
                uiLogic.OnOpen();
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    private static UIManager _ui;
    public static UIManager UI
    {
        get { return _ui; }
    }
    public void Awake()
    {
        _ui = this.gameObject.AddComponent<UIManager>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    创建BuildResources/LuaScripts/ui/testUI.bytes

    function OnInit()
        print("lua OnInit")
    end
    
    function OnOpen()
        print("lua OnOpen")
    end
    
    function Update()
        print("lua Update")
    end
    
    function OnClose()
        print("lua OnClose")
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    Manager = CS.Manager --引用C#里面定义的类实例
    function Main()
        print("hello main")
        --"TestUI"传的是BuildResources/UI/Prefabs/TestUI.prefab
        --"ui.TestUI"是lua脚本的名字
        Manager.UI:OpenUI("TestUI","ui.TestUI")
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    image.png
    GameStart.cs主脚本调用main.lua主脚本,main.lua执行Manager.UI.OpenUI调用UIManager.cs的OpenUI函数执行Manager.Resource.LoadUI加载了一个TestUI.prefab,并绑定了一个UILogic脚本,执行Init和OnOpen两个自定义的Awake和Start,UILogic脚本绑定在物体上之后,还会执行OnUpdate\OnDestrou等函数分别取调用Test UI.bytes的lua脚本

    9-3. UI层级

    UIManager

    • 加载UI
    • 绑定和执行Lua脚本
    • UI对象管理(委托给对象池)(该部分暂时不写)
    • 层级管理

    UI界面层级:

    • 界面类型:
      • 一级界面(主界面,渲染层级最低)
      • 二级弹窗
      • 三级弹窗(渲染层级最高)
      • 特殊界面(跑马灯之类的,永远在最上面)
    • UGUI层级特点
      • 根据节点顺序渲染(Canvas谁越靠下越先渲染)
    public class UIManager : MonoBehaviour
    {
        //用名字作为key,对象作为value,,,缓存UI,,后期会用对象池管理
        Dictionary<string, GameObject> m_UI = new Dictionary<string, GameObject>();
    
        //UI分组
        Dictionary<string, Transform> m_UIGroups = new Dictionary<string, Transform>();
    
        private Transform m_UIParent;
    
        private void Awake()
        {
            m_UIParent = this.transform.parent.Find("UI");
        }
    
        /// 
        /// 给Lua提供接口,方便添加分组("第一界面""  二级弹窗"xxxxx),用于热更给Canvas UI节点添加包含UI层级组
        /// 
        /// 要添加的UI层级名称的list
        public void SetUIGroup(List<string> group)
        {
            for (int i = 0; i < group.Count; i++)
            {
                GameObject go = new GameObject("Group-" + group[i]);
                go.transform.SetParent(m_UIParent, false);
                m_UIGroups.Add(group[i], go.transform);
            }
        }
    
        /// 
        /// 返回指定层级的transform
        /// 
        /// ui层级名称
        /// 返回字典中对应层级名称的transform
        Transform GetUIGroup(string group)
        {
            if (!m_UIGroups.ContainsKey(group))
            {
                Debug.LogError("group is not exist");
            }
            return m_UIGroups[group];
        }
    
        /// 
        /// 传入一个ui名字和lua名字,以及ui要放到的组中,自动给ui预制体绑定C#脚本,自动执行lua脚本
        /// 
        /// ui名字
        /// lua名字
        public void OpenUI(string uiName, string group, string luaName)
        {
            GameObject ui = null;
            //如果ui已经加载过了(从ab包取出放到Dictionary中),就只执行OnOpen(Start),不在执行Init(Awake)
            if(m_UI.TryGetValue(uiName, out ui))
            {
                UILogic uiLogic = ui.GetComponent<UILogic>();
                uiLogic.OnOpen();
                return;
            }
    
            Manager.Resource.LoadUI(uiName, (UnityEngine.Object obj) =>
            {
                ui = Instantiate(obj) as GameObject;
                m_UI.Add(uiName, ui);
    
                //加载ui成功后,设置父节点
                Transform parent = GetUIGroup(group);
                ui.transform.SetParent(parent, false);
    
                //给UI预制体绑定UILogic的C#脚本
                UILogic uiLogic = ui.AddComponent<UILogic>();
                //初始化这个lua脚本(Awake)
                uiLogic.Init(luaName);
                //UI的Start
                uiLogic.OnOpen();
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    Manager = CS.Manager --引用C#里面定义的类
    
    --定义UI层级
    local group = 
    {
      "Main",
      "UI",
      "Box",
    }
    
    Manager.UI:SetUIGroup(group)
    function Main()
      print("hello main")
      --"TestUI"传的是BuildResources/UI/Prefabs/TestUI.prefab
      --"UI"是要把这个TestUI.prefab放到的层级父对象下面
      --"ui.TestUI"是lua脚本的名字
      Manager.UI:OpenUI("TestUI", "UI", "ui.TestUI")
      Manager.UI:OpenUI("Login/LoginUI", "Main", "ui.TestUI")
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    【数据库原理及应用】——关系操作、关系完整性、关系代数(万字大总结)
    写一点什么吧
    HTML + JQuery + CSS 实现动态变色雪花特效
    【C++11保姆级教程】列表初始化(Literal types)和委派构造函数(delegating))
    如何使用ArcGIS Pro自动矢量化道路
    https://www.jianshu.com/p/34bf240b85a9
    关于next和nextLine
    vue基础知识(三)
    GPT-4 到底能帮你干点啥?
    北大联合智源提出训练框架LLaMA-Rider
  • 原文地址:https://blog.csdn.net/weixin_42264818/article/details/128211241