找回密码
 立即注册
查看: 366|回复: 0

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

[复制链接]
发表于 2023-4-9 09:28 | 显示全部楼层 |阅读模式
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文件的内容。
  1. publicclassManager:MonoBehaviour{privatestaticLuaManager _lua;publicstaticLuaManager Lua
  2.     {get{return _lua;}}publicvoidAwake(){
  3.         _resource =this.gameObject.AddComponent<ResourceManager>();
  4.         _lua =this.gameObject.AddComponent<LuaManager>();}}
复制代码
lua管理:
    异步加载(bundle是异步加载,如果上一步是require加载lua,下一步就调用怎么办?)同步使用预加载(想要同步使用,就要使用预加载方法)
所有lua(bundle)先加载出来,文件内容全部缓存在内存中,使用时直接调用。
  1. 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('|');
  2.         bundleInfo.AssetName = info[0];
  3.         bundleInfo.BundleName = info[1];//list特性:本质是数组,可动态扩容
  4.         bundleInfo.Dependeces =newList<string>(info.Length -2);for(int j =2; j < info.Length; j++){
  5.             bundleInfo.Dependeces.Add(info[j]);}
  6.         m_BundleInfos.Add(bundleInfo.AssetName, bundleInfo);//查找luaScripts下的lua文件,添加到luamanager中if(info[0].IndexOf("LuaScripts")>0){
  7.             Manager.Lua.LuaNames.Add(info[0]);}}}
复制代码
  1. publicstaticreadonlystring LuaPath ="Assets/BuildResources/LuaScripts/";
复制代码
  1. //为什么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();
//不能这么用,因为全局查找效率比较低,后续脚本中有更好的用法
  1. 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){
  2.         InitOK +=init;//初始化虚拟机
  3.         LuaEnv =newLuaEnv();//外部调用require时,会自动调用loader来获取文件
  4.         LuaEnv.AddLoader(Loader);
  5.         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){
  6.         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
  7.         name = name.Replace(".","/");//自定义的lua后缀名是.bytesstring fileName = PathUtil.GetLuaPath(name);byte[] luaScript =null;//从集合中拿到缓存的lua内容if(!m_LuaScripts.TryGetValue(fileName,out luaScript)){
  8.             Debug.LogError("lua script is not exist:"+ fileName);}return luaScript;}/// <summary>/// 非编辑器模式下,从路径中获取ab包,从ab包拿到文件/// </summary>voidLoadLuaScript(){foreach(var name in LuaNames){//异步的需要一个回调(=>后面那一坨,当LoadLua执行时完,执行回调并invoke把结果返回),obj就是返回的lua的对象
  9.             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函数的方法了
  10.                     InitOK?.Invoke();
  11.                     LuaNames.Clear();
  12.                     LuaNames =null;}});}}/// <summary>/// 把LuaNames里的名字对应lua文件本身里面的内容,放到集合内/// </summary>/// <param name="assetsName"></param>/// <param name="luaScript"></param>publicvoidAddLuaScript(string assetsName,byte[] luaScript){//为了放置重复添加,用这种方式可以直接覆盖
  13.         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);}
  14.                 InitOK?.Invoke();}#endifprivatevoidUpdate(){//释放lua内存if(LuaEnv !=null){
  15.             LuaEnv.Tick();}}privatevoidOnDestroy(){//虚拟机需要销毁掉if(LuaEnv !=null){
  16.             LuaEnv.Dispose();
  17.             LuaEnv =null;}}}
复制代码
  1. publicclassGameStart:MonoBehaviour{publicGameMode GameMode;// Start is called before the first frame updatevoidStart(){
  2.         AppConst.GameMode =this.GameMode;DontDestroyOnLoad(this);
  3.         Manager.Resource.ParseVersionFile();
  4.         Manager.Lua.Init(()=>{//初始化完成之后(lua都加载完),在执行回调
  5.                 Manager.Lua.StartLua("main");//输入的文件名//输入的是函数名XLua.LuaFunction func = Manager.Lua.LuaEnv.Global.Get<XLua.LuaFunction>("Main");
  6.                 func.Call();});}}
复制代码
9. LuaBehaviour

9-1. LuaBehaviour

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





xLua官方demo
  1. 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(){
  2.             scriptEnv = luaEnv.NewTable();// 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突LuaTable meta = luaEnv.NewTable();
  3.             meta.Set("__index", luaEnv.Global);
  4.             scriptEnv.SetMetaTable(meta);
  5.             meta.Dispose();//把这个脚本的实例注入到lua的self,让self = this
  6.             scriptEnv.Set("self",this);foreach(var injection in injections){//令injection.name = injection.value//即lightObject = light对象,因此lua可以直接用
  7.                 scriptEnv.Set(injection.name, injection.value);}//把LuaTestScript脚本绑定到scriptEnv的运行环境
  8.             luaEnv.DoString(luaScript.text,"LuaTestScript", scriptEnv);//如果lua的awake不为空,C#的awake执行时调用lua的awakeAction luaAwake = scriptEnv.Get<Action>("awake");//上下两种定义一样,重载类型不同
  9.             scriptEnv.Get("start",out luaStart);
  10.             scriptEnv.Get("update",out luaUpdate);
  11.             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){
  12.                 luaEnv.Tick();
  13.                 LuaBehaviour.lastGCTime = Time.time;}}voidOnDestroy(){if(luaOnDestroy !=null){luaOnDestroy();}
  14.             luaOnDestroy =null;
  15.             luaUpdate =null;
  16.             luaStart =null;
  17.             scriptEnv.Dispose();
  18.             injections =null;}}}
复制代码
这个脚本内的Awake调用LuaAwake,Start调用LuaStart,Update调用LuaUpdate,Destroy调用LuaDestroy,,,前面通过Env.Get拿到函数
  1. local speed =10local lightCpnt =nilfunctionstart()print("lua start...")print("injected object", lightObject)--C#里的Injection变量定义了lightObject,Lua没有定义,但可以直接用--见上面代码块蓝色部分
  2.   lightCpnt= lightObject:GetComponent(typeof(CS.UnityEngine.Light))endfunctionupdate()local r = CS.UnityEngine.Vector3.up * CS.UnityEngine.Time.deltaTime * speed
  3.   self.transform:Rotate(r)
  4.   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的生命周期,特定的方法在子类中实现。
  1. 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(){
  2.         m_ScriptEnv = m_LuaEnv.NewTable();// 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突LuaTable meta = m_LuaEnv.NewTable();
  3.         meta.Set("__index", m_LuaEnv.Global);
  4.         m_ScriptEnv.SetMetaTable(meta);
  5.         meta.Dispose();
  6.         m_ScriptEnv.Set("self",this);
  7.         m_ScriptEnv.Get("Awake",out m_LuaAwake);
  8.         m_ScriptEnv.Get("Start",out m_LuaStart);
  9.         m_ScriptEnv.Get("Update",out m_LuaUpdate);
  10.         m_LuaAwake?.Invoke();}// Start is called before the first frame updatevoidStart(){
  11.         m_LuaStart?.Invoke();}// Update is called once per framevoidUpdate(){
  12.         m_LuaUpdate?.Invoke();}//父类的需要是保护级,因为子类更特殊有其他需要进行的操作protectedvirtualvoidClear(){
  13.         m_LuaOnDestroy =null;
  14.         m_LuaAwake =null;
  15.         m_LuaStart =null;//运行环境释放掉
  16.         m_ScriptEnv?.Dispose();
  17.         m_ScriptEnv =null;}//两个不一定同时触发,退出的时候不会调用OnDestroy,所以都要写ClearprivatevoidOnDestroy(){
  18.         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脚本
  1. publicclassLuaBehaviour:MonoBehaviour{//全局只能有一个LuaEnvprivateLuaEnv m_LuaEnv = Manager.Lua.LuaEnv;protectedLuaTable m_ScriptEnv;privateAction m_LuaInit;privateAction m_LuaUpdate;privateAction m_LuaOnDestroy;publicstring luaName;voidAwake(){
  2.         m_ScriptEnv = m_LuaEnv.NewTable();// 为每个脚本设置一个独立的环境,可一定程度上防止脚本间全局变量、函数冲突LuaTable meta = m_LuaEnv.NewTable();
  3.         meta.Set("__index", m_LuaEnv.Global);
  4.         m_ScriptEnv.SetMetaTable(meta);
  5.         meta.Dispose();
  6.         m_ScriptEnv.Set("self",this);}//用Init来代替unity的awakepublicvirtualvoidInit(string luaName){
  7.         m_LuaEnv.DoString(Manager.Lua.GetLuaScript(luaName), luaName, m_ScriptEnv);
  8.         m_ScriptEnv.Get("Update",out m_LuaUpdate);
  9.         m_ScriptEnv.Get("OnInit",out m_LuaInit);
  10.         m_LuaInit?.Invoke();}// Start直接删掉// Update is called once per framevoidUpdate(){
  11.         m_LuaUpdate?.Invoke();}//父类的需要是保护级,因为子类更特殊有其他需要进行的操作protectedvirtualvoidClear(){
  12.         m_LuaOnDestroy =null;//运行环境释放掉
  13.         m_ScriptEnv?.Dispose();
  14.         m_ScriptEnv =null;
  15.                 m_LuaInit =null;
  16.                 m_LuaUpdate =null;}//两个不一定同时触发,退出的时候不会调用OnDestroy,所以都要写ClearprivatevoidOnDestroy(){
  17.         m_LuaOnDestroy?.Invoke();Clear();}privatevoidOnApplicationQuit(){Clear();}}
复制代码
创建两个脚本Scripts/Framework/Behaviour/UILogic.cs和Scripts/Framework/Manager/UIManager.cs(对外提供OpenUI的接口)
UILogic继承LuaBehaviour,实现UI自己的Open和Close方法
  1. usingSystem;//继承LuaBehaviourpublicclassUILogic:LuaBehaviour{Action m_LuaOnOpen;Action m_LuaOnClose;publicoverridevoidInit(string luaName){base.Init(luaName);
  2.         m_ScriptEnv.Get("OnOpen",out m_LuaOnOpen);
  3.         m_ScriptEnv.Get("OnClose",out m_LuaOnClose);}publicvoidOnOpen(){
  4.         m_LuaOnOpen?.Invoke();}publicvoidOnClose(){
  5.         m_LuaOnClose?.Invoke();}protectedoverridevoidClear(){base.Clear();
  6.         m_LuaOnOpen =null;
  7.         m_LuaOnClose =null;}}
复制代码
  1. 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>();
  2.             uiLogic.OnOpen();return;}
  3.         Manager.Resource.LoadUI(uiName,(UnityEngine.Object obj)=>{
  4.             ui =Instantiate(obj)asGameObject;
  5.             m_UI.Add(uiName, ui);//给UI预制体绑定UILogic的C#脚本UILogic uiLogic = ui.AddComponent<UILogic>();//初始化这个lua脚本(Awake)
  6.             uiLogic.Init(luaName);//UI的Start
  7.             uiLogic.OnOpen();});}}
复制代码
  1. privatestaticUIManager _ui;publicstaticUIManager UI
  2. {get{return _ui;}}publicvoidAwake(){
  3.     _ui =this.gameObject.AddComponent<UIManager>();}
复制代码
创建BuildResources/LuaScripts/ui/testUI.bytes
  1. functionOnInit()print("lua OnInit")
  2. end
  3. functionOnOpen()print("lua OnOpen")
  4. end
  5. functionUpdate()print("lua Update")
  6. end
  7. functionOnClose()print("lua OnClose")
  8. end
复制代码
  1. Manager = CS.Manager --引用C#里面定义的类实例
  2. functionMain()print("hello main")--"TestUI"传的是BuildResources/UI/Prefabs/TestUI.prefab
  3.     --"ui.TestUI"是lua脚本的名字
  4.     Manager.UI:OpenUI("TestUI","ui.TestUI")
  5. 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层级特点
      根据节点顺序渲染(Canvas谁越靠下越先渲染)

  1. 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(){
  2.         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]);
  3.             go.transform.SetParent(m_UIParent,false);
  4.             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)){
  5.             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>();
  6.             uiLogic.OnOpen();return;}
  7.         Manager.Resource.LoadUI(uiName,(UnityEngine.Object obj)=>{
  8.             ui =Instantiate(obj)asGameObject;
  9.             m_UI.Add(uiName, ui);//加载ui成功后,设置父节点Transform parent =GetUIGroup(group);
  10.             ui.transform.SetParent(parent,false);//给UI预制体绑定UILogic的C#脚本UILogic uiLogic = ui.AddComponent<UILogic>();//初始化这个lua脚本(Awake)
  11.             uiLogic.Init(luaName);//UI的Start
  12.             uiLogic.OnOpen();});}}
复制代码
  1. Manager = CS.Manager --引用C#里面定义的类--定义UI层级local group ={"Main","UI","Box",}
  2. Manager.UI:SetUIGroup(group)functionMain()print("hello main")--"TestUI"传的是BuildResources/UI/Prefabs/TestUI.prefab--"UI"是要把这个TestUI.prefab放到的层级父对象下面--"ui.TestUI"是lua脚本的名字
  3.   Manager.UI:OpenUI("TestUI","UI","ui.TestUI")
  4.   Manager.UI:OpenUI("Login/LoginUI","Main","ui.TestUI")end
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-23 12:23 , Processed in 0.091428 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表