|
8. Lua管理器
:::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文件的内容。- publicclassManager:MonoBehaviour{privatestaticLuaManager _lua;publicstaticLuaManager Lua
- {get{return _lua;}}publicvoidAwake(){
- _resource =this.gameObject.AddComponent<ResourceManager>();
- _lua =this.gameObject.AddComponent<LuaManager>();}}
复制代码 lua管理:
异步加载(bundle是异步加载,如果上一步是require加载lua,下一步就调用怎么办?)同步使用预加载(想要同步使用,就要使用预加载方法)
所有lua(bundle)先加载出来,文件内容全部缓存在内存中,使用时直接调用。- publicvoidParseVersionFile(){//拿到版本文件路径string url = Path.Combine(PathUtil.BundleResourcePath, AppConst.FileListName);//对文件进行读取string[] data = File.ReadAllLines(url);//解析文件信息for(int i =0; i < data.Length; i++){BundleInfo bundleInfo =newBundleInfo();string[] info = data[i].Split('|');
- bundleInfo.AssetName = info[0];
- bundleInfo.BundleName = info[1];//list特性:本质是数组,可动态扩容
- bundleInfo.Dependeces =newList<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]);}}}
复制代码- publicstaticreadonlystring LuaPath ="Assets/BuildResources/LuaScripts/";
复制代码- //为什么lua不要pathUtil传回一个相对路径?因为lua调用脚本就是用的相对路径调用,传进来的路径就是相对路径publicvoidLoadLua(string assetName,Action<UnityEngine.Object> action =null){LoadAsset(assetName, action);}
复制代码 关闭物体挂载hotUpdate脚本,修改为EditorMode
调用Lua的函数,要么是Lua文件中直接function();要么通过C#调用lua的函数访问LuaEnv.Global就可以了,例如:luaenv.Global.Get(“a”)
XLua.LuaFunction func = Manager.Lua.LuaEnv.Global.Get<XLua.LuaFunction>(“main”);
func.Call();
//不能这么用,因为全局查找效率比较低,后续脚本中有更好的用法
- usingSystem;usingSystem.Collections;usingSystem.IO;usingSystem.Collections.Generic;usingUnityEngine;usingXLua;/// <summary>/// 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>("xxx");func.Call();/// </summary>publicclassLuaManager:MonoBehaviour{//所有的lua文件名,获取所有lua,然后进行预加载,ResourceManager查找lua文件放进来publicList<string> LuaNames =newList<string>();//缓存lua脚本内容privateDictionary<string,byte[]> m_LuaScripts;//定义一个lua虚拟机,消耗比较大,,全局只需要一个,,需要using XLua;publicLuaEnv LuaEnv;Action InitOK;//如果是Editor模式下,直接从luapath就把所有lua读取到字典中了,然后在调用,属于同步加载后同步使用的情况//但是在其他模式下,需要从bundle异步加载lua需要等待,如果等待时start就调用了,属于异步加载同步使用的情况,需要预加载//需要创建一个回调通知publicvoidInit(Action init){
- InitOK +=init;//初始化虚拟机
- LuaEnv =newLuaEnv();//外部调用require时,会自动调用loader来获取文件
- LuaEnv.AddLoader(Loader);
- m_LuaScripts =newDictionary<string,byte[]>();#if UNITY_EDITORif(AppConst.GameMode == GameMode.EditorMode)EditorLoadLuaScript();else#endifLoadLuaScript();}/// <summary>/// 启动对应脚本的lua文件,实际上是吧启动文件的string传给Loader去加载,然后Loader通过GetLuaScript加载出来lua/// </summary>/// <param name="name"></param>publicvoidStartLua(string name){
- LuaEnv.DoString(string.Format("require '{0}'",name));}/// <summary>/// lua里面调用require后面的参数会传到name/// </summary>/// <param name="name"></param>/// <returns></returns>byte[]Loader(refstring name){returnGetLuaScript(name);}/// <summary>/// 和Loader配合使用找到要调用的指定目录lua文件/// </summary>/// <param name="name"></param>/// <returns></returns>publicbyte[]GetLuaScript(string name){//为什么替换掉.换成/,,因为一般使用require ui.login.register
- name = name.Replace(".","/");//自定义的lua后缀名是.bytesstring 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;}/// <summary>/// 非编辑器模式下,从路径中获取ab包,从ab包拿到文件/// </summary>voidLoadLuaScript(){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 asTextAsset).bytes);//在ResourceManager中解析版本文件时加载所有lua文件到LuaNames//如果LuaNames全部都加载到m_LuaScripts集合中,就清空LuaNames,退出循环if(m_LuaScripts.Count >= LuaNames.Count){//所有lua文件加载完成了。就可以执行使用lua函数的方法了
- InitOK?.Invoke();
- LuaNames.Clear();
- LuaNames =null;}});}}/// <summary>/// 把LuaNames里的名字对应lua文件本身里面的内容,放到集合内/// </summary>/// <param name="assetsName"></param>/// <param name="luaScript"></param>publicvoidAddLuaScript(string assetsName,byte[] luaScript){//为了放置重复添加,用这种方式可以直接覆盖
- m_LuaScripts[assetsName]= luaScript;}#if UNITY_EDITOR//编辑器模式下直接加载lua文件,并把lua名字和内容放到集合内voidEditorLoadLuaScript(){//搜索所有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();}#endifprivatevoidUpdate(){//释放lua内存if(LuaEnv !=null){
- LuaEnv.Tick();}}privatevoidOnDestroy(){//虚拟机需要销毁掉if(LuaEnv !=null){
- LuaEnv.Dispose();
- LuaEnv =null;}}}
复制代码- publicclassGameStart:MonoBehaviour{publicGameMode GameMode;// Start is called before the first frame updatevoidStart(){
- 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();});}}
复制代码 9. LuaBehaviour
9-1. LuaBehaviour
逻辑分离
绑定:写C#函数的习惯分离到Lua中,把Lua的函数写成和C#一样的,当执行C#时就知道执行哪个Lua脚本离的哪个逻辑
xLua官方demo- usingUnityEngine;usingSystem.Collections;usingSystem.Collections.Generic;usingXLua;usingSystem;namespaceXLuaTest{//拖拽进来的灯[System.Serializable]publicclassInjection{publicstring name;publicGameObjectvalue;}[LuaCallCSharp]publicclassLuaBehaviour:MonoBehaviour{//读取的lua脚本的内容publicTextAsset luaScript;publicInjection[] injections;internalstaticLuaEnv luaEnv =newLuaEnv();//all lua behaviour shared one luaenv only!internalstaticfloat lastGCTime =0;//GC的计时internalconstfloat GCInterval =1;//1 second GC的间隔//action引用lua的委托privateAction luaStart;privateAction luaUpdate;privateAction luaOnDestroy;//脚本的运行环境privateLuaTable scriptEnv;voidAwake(){
- 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的awakeAction 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 initializationvoidStart(){if(luaStart !=null){luaStart();}}// Update is called once per framevoidUpdate(){if(luaUpdate !=null){luaUpdate();}if(Time.time - LuaBehaviour.lastGCTime > GCInterval){
- luaEnv.Tick();
- LuaBehaviour.lastGCTime = Time.time;}}voidOnDestroy(){if(luaOnDestroy !=null){luaOnDestroy();}
- luaOnDestroy =null;
- luaUpdate =null;
- luaStart =null;
- scriptEnv.Dispose();
- injections =null;}}}
复制代码 这个脚本内的Awake调用LuaAwake,Start调用LuaStart,Update调用LuaUpdate,Destroy调用LuaDestroy,,,前面通过Env.Get拿到函数- local speed =10local lightCpnt =nilfunctionstart()print("lua start...")print("injected object", lightObject)--C#里的Injection变量定义了lightObject,Lua没有定义,但可以直接用--见上面代码块蓝色部分
- lightCpnt= lightObject:GetComponent(typeof(CS.UnityEngine.Light))endfunctionupdate()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)endfunctionondestroy()print("lua destroy")end
复制代码 创建我们自己的lua运行脚本
LuaBehaviour是个父类,将来会有别的脚本继承他,例如UI,3DObject,他们的逻辑不同,父类只提供Unity的生命周期,特定的方法在子类中实现。- usingSystem;usingUnityEngine;usingXLua;publicclassLuaBehaviour:MonoBehaviour{//全局只能有一个LuaEnvprivateLuaEnv m_LuaEnv = Manager.Lua.LuaEnv;protectedLuaTable m_ScriptEnv;privateAction m_LuaAwake;privateAction m_LuaStart;privateAction m_LuaUpdate;privateAction m_LuaOnDestroy;voidAwake(){
- 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 updatevoidStart(){
- m_LuaStart?.Invoke();}// Update is called once per framevoidUpdate(){
- m_LuaUpdate?.Invoke();}//父类的需要是保护级,因为子类更特殊有其他需要进行的操作protectedvirtualvoidClear(){
- m_LuaOnDestroy =null;
- m_LuaAwake =null;
- m_LuaStart =null;//运行环境释放掉
- m_ScriptEnv?.Dispose();
- m_ScriptEnv =null;}//两个不一定同时触发,退出的时候不会调用OnDestroy,所以都要写ClearprivatevoidOnDestroy(){
- m_LuaOnDestroy?.Invoke();Clear();}privatevoidOnApplicationQuit(){Clear();}}
复制代码 9-2. UILogic
如何能在Awake之前把变量传进去?prefab添加脚本手动赋值可以,,但是框架不能这样做,不能全都手动赋值,,,只有这一种办法,,只能舍弃unity中使用awake,start,需要在Unity中模拟出awake和start的特性并提供给Lua使用这种特性(awake实例化之后最先调用,实例化到销毁周期内只触发一次,隐藏和激活不会触发awake;;;;start加载和激活都会从触发),start刷新UI,awake加载一些配置和数据
开发者:调用API,传入一个UI预设的名字和lua脚本的名字,自动绑定C#脚本在UI预设上,并且执行Lua脚本- publicclassLuaBehaviour:MonoBehaviour{//全局只能有一个LuaEnvprivateLuaEnv m_LuaEnv = Manager.Lua.LuaEnv;protectedLuaTable m_ScriptEnv;privateAction m_LuaInit;privateAction m_LuaUpdate;privateAction m_LuaOnDestroy;publicstring luaName;voidAwake(){
- 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的awakepublicvirtualvoidInit(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 framevoidUpdate(){
- m_LuaUpdate?.Invoke();}//父类的需要是保护级,因为子类更特殊有其他需要进行的操作protectedvirtualvoidClear(){
- m_LuaOnDestroy =null;//运行环境释放掉
- m_ScriptEnv?.Dispose();
- m_ScriptEnv =null;
- m_LuaInit =null;
- m_LuaUpdate =null;}//两个不一定同时触发,退出的时候不会调用OnDestroy,所以都要写ClearprivatevoidOnDestroy(){
- m_LuaOnDestroy?.Invoke();Clear();}privatevoidOnApplicationQuit(){Clear();}}
复制代码 创建两个脚本Scripts/Framework/Behaviour/UILogic.cs和Scripts/Framework/Manager/UIManager.cs(对外提供OpenUI的接口)
UILogic继承LuaBehaviour,实现UI自己的Open和Close方法- usingSystem;//继承LuaBehaviourpublicclassUILogic:LuaBehaviour{Action m_LuaOnOpen;Action m_LuaOnClose;publicoverridevoidInit(string luaName){base.Init(luaName);
- m_ScriptEnv.Get("OnOpen",out m_LuaOnOpen);
- m_ScriptEnv.Get("OnClose",out m_LuaOnClose);}publicvoidOnOpen(){
- m_LuaOnOpen?.Invoke();}publicvoidOnClose(){
- m_LuaOnClose?.Invoke();}protectedoverridevoidClear(){base.Clear();
- m_LuaOnOpen =null;
- m_LuaOnClose =null;}}
复制代码- usingSystem.Collections.Generic;usingUnityEngine;publicclassUIManager:MonoBehaviour{//用名字作为key,对象作为value,,,缓存UI,,后期会用对象池管理Dictionary<string, GameObject> m_UI =newDictionary<string, GameObject>();/// <summary>/// 传入一个ui名字和lua名字,自动给ui预制体绑定C#脚本,自动执行lua脚本/// </summary>/// <param name="uiName">ui名字</param>/// <param name="luaName">lua名字</param>publicvoidOpenUI(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)asGameObject;
- m_UI.Add(uiName, ui);//给UI预制体绑定UILogic的C#脚本UILogic uiLogic = ui.AddComponent<UILogic>();//初始化这个lua脚本(Awake)
- uiLogic.Init(luaName);//UI的Start
- uiLogic.OnOpen();});}}
复制代码- privatestaticUIManager _ui;publicstaticUIManager UI
- {get{return _ui;}}publicvoidAwake(){
- _ui =this.gameObject.AddComponent<UIManager>();}
复制代码 创建BuildResources/LuaScripts/ui/testUI.bytes- functionOnInit()print("lua OnInit")
- end
- functionOnOpen()print("lua OnOpen")
- end
- functionUpdate()print("lua Update")
- end
- functionOnClose()print("lua OnClose")
- end
复制代码- Manager = CS.Manager --引用C#里面定义的类实例
- functionMain()print("hello main")--"TestUI"传的是BuildResources/UI/Prefabs/TestUI.prefab
- --"ui.TestUI"是lua脚本的名字
- Manager.UI:OpenUI("TestUI","ui.TestUI")
- end
复制代码
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层级特点
- publicclassUIManager:MonoBehaviour{//用名字作为key,对象作为value,,,缓存UI,,后期会用对象池管理Dictionary<string, GameObject> m_UI =newDictionary<string, GameObject>();//UI分组Dictionary<string, Transform> m_UIGroups =newDictionary<string, Transform>();privateTransform m_UIParent;privatevoidAwake(){
- m_UIParent =this.transform.parent.Find("UI");}/// <summary>/// 给Lua提供接口,方便添加分组("第一界面"" 二级弹窗"xxxxx),用于热更给Canvas UI节点添加包含UI层级组/// </summary>/// <param name="group">要添加的UI层级名称的list</param>publicvoidSetUIGroup(List<string>group){for(int i =0; i <group.Count; i++){GameObject go =newGameObject("Group-"+group[i]);
- go.transform.SetParent(m_UIParent,false);
- m_UIGroups.Add(group[i], go.transform);}}/// <summary>/// 返回指定层级的transform/// </summary>/// <param name="group">ui层级名称</param>/// <returns>返回字典中对应层级名称的transform</returns>TransformGetUIGroup(stringgroup){if(!m_UIGroups.ContainsKey(group)){
- Debug.LogError("group is not exist");}return m_UIGroups[group];}/// <summary>/// 传入一个ui名字和lua名字,以及ui要放到的组中,自动给ui预制体绑定C#脚本,自动执行lua脚本/// </summary>/// <param name="uiName">ui名字</param>/// <param name="luaName">lua名字</param>publicvoidOpenUI(string uiName,stringgroup,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)asGameObject;
- 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();});}}
复制代码- Manager = CS.Manager --引用C#里面定义的类--定义UI层级local group ={"Main","UI","Box",}
- Manager.UI:SetUIGroup(group)functionMain()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
复制代码 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|