我会先把源码直接给出来。博主本身也不是什么大牛,所以写的也不是什么很厉害的框架,只是一个很小很小的UI框架,给初学者一点入门的思路。 好,如果你做好了思想准备,那么就先下载源码,读起来吧!这真的是一个很简单的UI框架,内容不多。读起来应该很容易。 那接下来我们就开始吧! 初次接触框架的读者可能会完全不知道从哪里下手阅读,那么博主就带着大家一起看看,这个框架主要是用来解决什么问题的。 打开这个文件夹,有如下脚本: 很简单,只有20行代码。这个脚本只做了一件事,就是实现一个单例。特殊之处在于,这是一个单例的带有泛型的基类,使用反射的方式搞定的单例,那么他的作用呢?也很简单,只要是你想要把一个脚本做成单例模式,那么只需要继承这个脚本,再私有化无参构造即可。文字可能解释的不是很清楚,我们结合代码来看,接下来我们来看看AssetsManager: AssetsManager脚本是我用来做资源管理的脚本,那么可能会在很多其他地方要用到,那每次要用到我都去实例化就太麻烦了,而且也不利于资源管理,所以我把他做成单例。怎么做?就利用上面的Singleton脚本来实现,我们让AssetsManager继承Singleton,再私有化构造函数,就自然而然的完成了我们的需求,以后再有其它脚本要实现单例,也这样如法炮制即可。这里就已经体现了一些些框架的威力。也可以说是设计原则的好处。不展开了,回到正题。那么这个脚本管理的是什么资源呢?其实,什么资源都可以,但我们主要是针对UI资源进行管理,首先我们写了一个缓存池,用来缓存已经取过的资源用以二次利用。然后就是一个获取资源的方法了,很简单,就是传过来一个资源的所在路径,通过路径将资源拿出来放进缓存后返回该资源,自己看看应该就能看懂。 同样的,JsonPanelManager 这个脚本我们也继承了单例,那么这个脚本主要是用来做什么的呢?很简单,就是解析json文件,将json文件中存储的资源路径都给拿出来,然后再利用上一个脚本将资源拿出来放进缓存(也就是我们自己定义的字典里面,字典有多好用就不用我多说了吧)。至于json文件的解析,本篇博客也不赘述,都来看框架了,json解析这种东西应该没问题吧?那这个脚本中除了获取UI模块资源,还写了一个获取UI动态元件和本地化的json解析,暂时用不到,先不说。 SystemDefaine这个脚本,单纯是用来保存常数的,注意,这是一个静态类,暂时我们只用到了最上面的json文件路径的字符串常数。也没什么特殊的。其他的用到再说。 UIBases文件夹下有如下脚本: UIWidgetBase 是一个通过代码添加到所有以“_F”结尾的UI元件身上的脚本组件。他继承了UIMono,UIMono则实现了很多关于UI组件操作的接口,详情自己去看看UIInterface文件夹,因为过于简单,所以博主不做讲解。 我们再来看UIModuleBase: UIModuleBase这个脚本是挂载在所有UI模块上的。但是,不是直接挂载,而是每个模块自己写一个脚本继承这个脚本之后挂载。并且需要重写这个脚本的awake方法。具体的解释可以仔细看看代码以及注释,不难理解。值得一提的是,一定要记得绑定相对应的Controller,不然无法实现相关功能。 那么最后我们来说说UIControllerBase: UIControllerBase跟UIModuleBase 一样,每个UI模块都需要写一个自身的控制器继承这个UIControllerBase,然后不需要挂载,因为他跟UIModuleBase 绑定在一起,可以直接调用。具体用法我们后面说。我们先接着看框架。 Managers文件夹下有如下脚本: 可以这么说,你看懂了这个脚本,那么基本上这个框架你就看懂了六成以上。首先,这个脚本也是实现的单例。然后你会发现,我们定义了很多容器,包括栈,集合,字典等等,我的注释也写得很清楚他们各自负责的是什么。读懂其实是不难的,也没有什么复杂的语法,主要就是各个脚本之间相互跳转可能会有一点绕。但这些看起来麻烦的操作都是为了一件事:解耦合。不用我多说,大家也都知道解耦合对一个程序来说有多重要。这个脚本我能说的不多,因为各个模块的跳转需要你自己在编译器上读代码的时候自己跳转看起来更方便。 新建两个文件夹: MainModule继承了UIModuleBase ,并挂载在UI模块mainPanel上面。 MainController 继承了UIControllerBase ,并与MainModule 绑定在一起。通过uiModuleBase的GetWidget(string widgetName)方法可以拿到相应的UI元件,并通过UIMono中实现的接口方法完成所要实现的需求。这里做的是一个网络对战游戏的主界面,所以实现的都是网络相关功能。也只是一些简单的添加点击实现,获取UI输入框中的内容,更改UI输入框中的内容,都是可以一句话就解决,非常方便,也非常酷。 这个脚本很简单,只是将主模块在游戏开始运行时push出来。你也可以有自己的想法。 如果你看到了这里,那么恭喜你,你已经对UI框架有了一个最最最基础的认知。这是一个很简单的UI框架,代码量总共也没多少,真正的框架可比这个复杂的多,也不是一篇博客就能说得清的,如果想深入学习,可以去找找大牛写的框架,本博客只做入门用,甚至都算不上。希望能对小白有些帮助。一起加油吧!
写在前面的话
很多人在刚开始接触框架的时候都会有一种感觉:这NM什么DDX啊?!完全懵逼了有没有?!我还没开始写代码呢!怎么就已经有这么多代码要看啊?!还有大部分新人都会吐槽的事情:为什么要这么写啊?这玩意随便写个脚本挂上去不是分分钟实现功能吗?你这绕来绕去搞毛?这个主程是不是NT?!emmmmmmmmmmm…如果你知道主程怎么想的,你不就是主程了?
所以,初次接触框架的话,可以抱怨,但是请尽快冷静下来,好好看代码。起码要以最快的速度会用这个框架,在慢慢去分析为什么框架要这么写。其实看完后你就会发现,主程写的框架实现一个功能确实很麻烦,但是你用起来的时候却非常方便,因为很多东西主程早早的就给你封装在框架里了,你只需要干一些不动脑子的体力活就实现了需求,爽就一个字!这就是为什么要使用框架,为什么主程要弯弯绕绕的实现功能。
UIFrame
提取码:gt4a脚本文件夹结构
这个框架有五个基本文件夹,分别是:Managers,Models,UIBases,UIInterface,Utility。
怎么样?都是大家耳熟能详的名字吧?那么我们应该先从哪里开始阅读呢?自然就是Utility文件夹了。Utility文件夹下的脚本
下面我将依次讲解这几个脚本各自负责做些什么。
首先,我们先看Singlton:using System; namespace UIFrame { public class Singleton<T> where T : class { private static T instance; public static T Instance { get { if (instance == null) { instance = (T)Activator.CreateInstance(typeof(T), true); } return instance; } } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace UIFrame { public class AssetsManager : Singleton<AssetsManager> { private AssetsManager() { assetsChace = new Dictionary<string, object>(); } /// <summary> /// 资源缓存池 /// </summary> private Dictionary<string, object> assetsChace; /// <summary> /// 获取资源方法 /// </summary> /// <param name="assetPath">路径</param> /// <returns>获取到的对象</returns> public object GetAsset(string assetPath) { object asset = null; if (!assetsChace.TryGetValue(assetPath,out asset)) { asset = Resources.Load(assetPath); assetsChace.Add(assetPath, asset); } return asset; } } }
好,那这个脚本就不多说了,我们看下一个,JsonPanelManager:using System.Collections; using System.Collections.Generic; using UnityEngine; namespace UIFrame { public class JsonPanelManager : Singleton<JsonPanelManager> { private JsonPanelManager() { jsonPanelDataDic = new Dictionary<int, Dictionary<string, string>>(); jsonLocalizationDic = new Dictionary<int, Dictionary<string, string[]>>(); widgetDataDic = new Dictionary<int, Dictionary<string, string>>(); ParseJsonPanel(); //ParseJsonLocalization(); ParseJsonWidget(); } /// <summary> /// json转换的对象 /// </summary> private JsonPanelModel jsonPanelData; /// <summary> /// json转换后的字典存储 /// </summary> private Dictionary<int, Dictionary<string,string>> jsonPanelDataDic; /// <summary> /// json转化后的本地化文件对象 /// </summary> private JsonLocalizationModel jsonLocalizationData; /// <summary> /// json转换后的本地化文件字典 /// </summary> private Dictionary<int, Dictionary<string, string[]>> jsonLocalizationDic; //Widget解析后的数据 private JsonWidgetModel widgetData; //Widget解析后的数据【字典版】 private Dictionary<int, Dictionary<string, string>> widgetDataDic; /// <summary> /// json解析 /// </summary> private void ParseJsonPanel() { //获取json文本对象 TextAsset assetText = AssetsManager.Instance.GetAsset(SystemDefaine.JsonPanelsPath) as TextAsset; //解析json jsonPanelData = JsonUtility.FromJson<JsonPanelModel>(assetText.text); for (int i = 0; i < jsonPanelData.AllData.Length; i++) { //新定义一个字典 Dictionary<string, string> crtPanelData = new Dictionary<string, string>(); //将场景ID和字典存入 jsonPanelDataDic.Add(i, crtPanelData); //将场景的所有资源路径保存 for (int j = 0; j < jsonPanelData.AllData[i].Data.Length; j++) { crtPanelData.Add(jsonPanelData.AllData[i].Data[j].PanelName, jsonPanelData.AllData[i].Data[j].PanelPath); } } } /// <summary> /// json解析 /// </summary> private void ParseJsonLocalization() { //获取json文本对象 TextAsset assetText = AssetsManager.Instance.GetAsset(SystemDefaine.JsonLanguages) as TextAsset; //解析json jsonLocalizationData = JsonUtility.FromJson<JsonLocalizationModel>(assetText.text); for (int i = 0; i < jsonLocalizationData.AllData.Length; i++) { //新定义一个字典 Dictionary<string, string[]> crtPanelData = new Dictionary<string, string[]>(); //将场景ID和字典存入 jsonLocalizationDic.Add(i, crtPanelData); //将场景的所有资源路径保存 for (int j = 0; j < jsonLocalizationData.AllData[i].Data.Length; j++) { crtPanelData.Add(jsonLocalizationData.AllData[i].Data[j].ObjName, jsonLocalizationData.AllData[i].Data[j].TextLanguageText); } } } private void ParseJsonWidget() { TextAsset assetText = AssetsManager.Instance.GetAsset(SystemDefaine.JsonWidgetsPath) as TextAsset; widgetData = JsonUtility.FromJson<JsonWidgetModel>(assetText.text); for (int i = 0; i < widgetData.AllData.Length; i++) { //创建一个字典 Dictionary<string, string> crtDic = new Dictionary<string, string>(); //添加一个场景ID和一个字典 widgetDataDic.Add(i, crtDic); //遍历当前场景内的所有Panel路径数据 for (int j = 0; j < widgetData.AllData[i].Data.Length; j++) { //以PanelName为Key,以PanelPath为value进行存储 crtDic.Add(widgetData.AllData[i].Data[j].WidgetName, widgetData.AllData[i].Data[j].WidgetPath); } } } /// <summary> /// 获取资源路径 /// </summary> /// <param name="panelName"></param> /// <returns></returns> public string GetAssetPath(string panelName,int sceneID) { if (!jsonPanelDataDic.ContainsKey(sceneID)) { return null; } if (!jsonPanelDataDic[sceneID].ContainsKey(panelName)) { return null; } return jsonPanelDataDic[sceneID][panelName]; } /// <summary> /// 获取本地化语言数组 /// </summary> /// <param name="objName"></param> /// <param name="sceneID"></param> /// <returns></returns> public string[] GetLocalizationTextArray(string objName,int sceneID) { if (!jsonLocalizationDic.ContainsKey(sceneID)) { return null; } if (!jsonLocalizationDic[sceneID].ContainsKey(objName)) { return null; } return jsonLocalizationDic[sceneID][objName]; } public string GetWidgetPath(string widgetName, int sceneID) { if (!widgetDataDic.ContainsKey(sceneID)) return null; if (!widgetDataDic[sceneID].ContainsKey(widgetName)) return null; //如果都ID和Widget在字典中都存在,则直接返回 return widgetDataDic[sceneID][widgetName]; } } }
好,那这个文件夹就只剩最后一个脚本了,SystemDefaine:using UnityEngine; public static class SystemDefaine { public const string JsonPanelsPath = "Configuration/UIPanelConfig"; public const string JsonWidgetsPath = "Configuration/UIWidgetConfig"; public const string JsonLanguages = "Configuration/LocalizationConfig"; public enum SceneID { Login = 0, FightScene = 1 } public static string[] UIWidgetsMark = new string[] { "_F" }; }
UIBases文件夹
我们先来看UIWidgetBase:using System.Collections; using System.Collections.Generic; using UnityEngine; using UIInterface; namespace UIFrame { public class UIWidgetBase : UIMono { //绑定所属的UI模块 private UIModuleBase crtModule; //初始化UI元件 public void UIWidgetInit(UIModuleBase uIModuleBase) { crtModule = uIModuleBase; UIManager.Instance.AddWidget(crtModule.name, name, this); } private void OnDisable() { UIManager.Instance.RemoveWidget(crtModule.name, name); } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace UIFrame { //该脚本挂载在每个UI模块上,并确保拥有CanvasGroup组件 [RequireComponent(typeof(CanvasGroup))] public class UIModuleBase : MonoBehaviour { protected CanvasGroup canvasGroup; //获取所有的子对象 private Transform[] allChild; protected virtual void Awake() { canvasGroup = GetComponent<CanvasGroup>(); //修改对象名称(将生成的对象名称后面的(clone)去掉) gameObject.name = gameObject.name.Remove(gameObject.name.Length - "(clone)".Length); allChild = transform.GetComponentsInChildren<Transform>(); AddUIWidgetBehaviour(); } //绑定该模块对应的Controller protected void BindController(UIControllerBase controller) { controller.ControllerInit(this); } /// <summary> /// 找出需要添加widgetbase的脚本 /// </summary> private void AddUIWidgetBehaviour() { for (int i = 0; i < allChild.Length; i++) { for (int j = 0; j < SystemDefaine.UIWidgetsMark.Length; j++) { //所有子对象中名称以规定字符串结尾的都添加上一个UIWidgetBase脚本。 if (allChild[i].name.EndsWith(SystemDefaine.UIWidgetsMark[j])) { AddComponetForWidget(i); } } } } /// <summary> /// 给对象添加widgetbase组件 /// </summary> /// <param name="index"></param> protected virtual void AddComponetForWidget(int index) { UIWidgetBase crtbase = allChild[index].gameObject.AddComponent<UIWidgetBase>(); crtbase.UIWidgetInit(this); } //通过UI元件名称获取该模块中绑定的UI元件 public UIWidgetBase GetWidget(string widgetName) { return UIManager.Instance.GetWidget(name, widgetName); } //以下是用以模态处理的四个操作,你可以在重写时加入想要的模态处理效果,或者添加一些动画之类的效果,简单易懂,不做赘述 public virtual void OnEnter() { canvasGroup.blocksRaycasts = true; //LocalizationManager.Instance.LocalizationInit(); } public virtual void OnPurse() { canvasGroup.blocksRaycasts = false; } public virtual void OnResume() { canvasGroup.blocksRaycasts = true; } public virtual void OnExit() { canvasGroup.blocksRaycasts = false; } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; namespace UIFrame { public class UIControllerBase { //绑定相应的UIModuleBase protected UIModuleBase uiModuleBase; //初始化控制器 public void ControllerInit(UIModuleBase uiModuleBase) { this.uiModuleBase = uiModuleBase; ControllerStart(); } protected virtual void ControllerStart() { } } }
Managers文件夹
这两个脚本才是我们框架的重中之重,或者说UIManager这个脚本才是核心。
话不多说,我们直接看这个脚本:using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace UIFrame { public class UIManager : Singleton<UIManager> { private UIManager() { canvas = GameObject.Find("Canvas").transform; uiModuleBases = new Dictionary<UIType, UIModuleBase>(); uiModuleStack = new Stack<UIModuleBase>(); uiWidgetBases = new Dictionary<string, Dictionary<string, UIWidgetBase>>(); uiModuleList = new List<UIModuleBase>(); } /// <summary> /// ui模块的栈 /// </summary> Stack<UIModuleBase> uiModuleStack; /// <summary> /// ui模块的列表存储 /// </summary> List<UIModuleBase> uiModuleList; /// <summary> /// 管理所有的UI模块 /// </summary> Dictionary<UIType, UIModuleBase> uiModuleBases; /// <summary> /// 管理所有的元件 /// </summary> Dictionary<string, Dictionary<string, UIWidgetBase>> uiWidgetBases; /// <summary> /// 画布 /// </summary> private Transform canvas; public UIModuleBase GetUIModuleByName(string panelName) { UIType type = UITypeManager.Instance.GetUiType(panelName, 0); return GetUIModule(type); } /// <summary> /// 获取ui模块 /// </summary> /// <param name="type"></param> /// <returns></returns> public UIModuleBase GetUIModule(UIType type) { UIModuleBase crtUIMdule = null; if (!uiModuleBases.TryGetValue(type,out crtUIMdule)) { crtUIMdule = InstantiateUIModule(AssetsManager.Instance.GetAsset(type.panelPath) as GameObject); uiModuleBases.Add(type, crtUIMdule); } else if(crtUIMdule == null) { crtUIMdule = InstantiateUIModule(AssetsManager.Instance.GetAsset(type.panelPath) as GameObject); uiModuleBases[type] = crtUIMdule; } return crtUIMdule; } /// <summary> /// 生成ui模块对象 /// </summary> /// <param name="prefab"></param> /// <returns></returns> private UIModuleBase InstantiateUIModule(GameObject prefab) { GameObject obj = GameObject.Instantiate(prefab); obj.transform.SetParent(canvas, false); return obj.GetComponent<UIModuleBase>(); } /// <summary> /// 压栈 /// </summary> /// <param name="panelName">模块名称</param> /// <param name="sceneID">场景ID</param> public void PushUI(string panelName,int sceneID) { //定义一个uitype UIType type = UITypeManager.Instance.GetUiType(panelName,sceneID); //获取模块 UIModuleBase crtbase = GetUIModule(type); //如果栈中已有元素 if (uiModuleStack.Count > 0) { //当前模块暂停使用 uiModuleStack.Peek().OnPurse(); } //新模块压栈 uiModuleStack.Push(crtbase); //新模块开启 crtbase.OnEnter(); } /// <summary> /// 展示UI /// </summary> /// <param name="panelName"></param> /// <param name="sceneID"></param> public void ShowUI(string panelName,int sceneID) { //定义一个uitype UIType type = UITypeManager.Instance.GetUiType(panelName, sceneID); //获取模块 UIModuleBase crtbase = GetUIModule(type); //如果栈中已有元素 if (!uiModuleList.Contains(crtbase)) { //当前模块暂停使用 uiModuleList.Add(crtbase); } //新模块开启 crtbase.OnEnter(); } /// <summary> /// 出栈 /// </summary> public void PopUI() { if (uiModuleStack.Count > 0) { uiModuleStack.Pop().OnExit(); } if (uiModuleStack.Count > 0) { uiModuleStack.Peek().OnResume(); } } /// <summary> /// 注册UI模块元件 /// </summary> /// <param name="moduleName"></param> private void RegisterUIModuleWidgets(string moduleName) { if (!uiWidgetBases.ContainsKey(moduleName)) { uiWidgetBases.Add(moduleName, new Dictionary<string, UIWidgetBase>()); } } /// <summary> /// 注销UI模块元件 /// </summary> /// <param name="moduleName"></param> public void UnRegisterUIModuleWidgets(string moduleName) { if (uiWidgetBases.ContainsKey(moduleName)) { uiWidgetBases.Remove(moduleName); } } /// <summary> /// 添加元件 /// </summary> /// <param name="moduleName">模块名</param> /// <param name="widgetName">元件名</param> /// <param name="widget">元件</param> public void AddWidget(string moduleName, string widgetName, UIWidgetBase widget) { RegisterUIModuleWidgets(moduleName); if (!uiWidgetBases[moduleName].ContainsKey(widgetName)) { uiWidgetBases[moduleName].Add(widgetName, widget); } } /// <summary> /// 移除元件 /// </summary> /// <param name="moduleName"></param> /// <param name="widgetName"></param> public void RemoveWidget(string moduleName, string widgetName) { if (!uiWidgetBases.ContainsKey(moduleName)) { return; } if (!uiWidgetBases[moduleName].ContainsKey(widgetName)) { return; } uiWidgetBases[moduleName].Remove(widgetName); } /// <summary> /// 获取元件 /// </summary> /// <param name="moduleName"></param> /// <param name="widgetName"></param> /// <returns></returns> public UIWidgetBase GetWidget(string moduleName, string widgetName) { //如果该模块未注册,注册 if (!uiWidgetBases.ContainsKey(moduleName)) { RegisterUIModuleWidgets(moduleName); } UIWidgetBase widget = null; //尝试获取该模块此元件并返回,没有返回null uiWidgetBases[moduleName].TryGetValue(widgetName, out widget); return widget; } public GameObject CreateDynamicWidget(string widgetName) { string widgetPath = JsonPanelManager.Instance.GetWidgetPath(widgetName, 0); GameObject prefab = AssetsManager.Instance.GetAsset(widgetPath) as GameObject; return GameObject.Instantiate(prefab); } public GameObject CreateDynamicWidget(string widgetName, Transform parent, bool worldPosStays) { GameObject obj = CreateDynamicWidget(widgetName); obj.transform.SetParent(parent, worldPosStays); return obj; } } }
下面我写一个简单的框架使用过程,希望能帮你更好的理解这个框架。使用该框架
方便我们管理我的脚本,这两个文件夹名称也很能说明问题,自然一个是放模块脚本,一个是放控制器脚本的。
我写了一个MainModule和一个MainController :using System.Collections; using System.Collections.Generic; using UnityEngine; using UIFrame; public class MainModule : UIModuleBase { private MainController controller; protected override void Awake() { base.Awake(); controller = new MainController(); BindController(controller); } }
using System.Collections; using System.Collections.Generic; using UnityEngine; using UIFrame; using Photon.Pun; using Hashtable = ExitGames.Client.Photon.Hashtable; public class MainController : UIControllerBase { protected override void ControllerStart() { base.ControllerStart(); MainModuleInit(); BindEvents(); } void MainModuleInit() { uiModuleBase.GetWidget("PlayerNameInputField_F").SetInputFieldText("Player" + Random.Range(101, 999)); MonoHelper.Instance.InvokeRepeat(() => { uiModuleBase.GetWidget("NetworkingState_F").SetTextText(PhotonNetwork.NetworkClientState.ToString()); }, 0, () => { return false; }); } void SetPlayerInfo() { PhotonNetwork.LocalPlayer.NickName = uiModuleBase.GetWidget("PlayerNameInputField_F").GetInputFieldText(); int teamIndex = uiModuleBase.GetWidget("BattleArrayDropdown_F").GetDropDownValue(); Hashtable table = new Hashtable(); table.Add(GameConst.TEAMINDEX, teamIndex); PhotonNetwork.LocalPlayer.SetCustomProperties(table); } void BindEvents() { uiModuleBase.GetWidget("BattleArrayDropdown_F").OnDropDownValueChange(OnDropDownValueChange); uiModuleBase.GetWidget("CreateRoomButton_F").AddOnClickListener(() => { SetPlayerInfo(); UIManager.Instance.PushUI("CreateRoomModule",0); }); uiModuleBase.GetWidget("RandomJoinRoomButton_F").AddOnClickListener(() => { SetPlayerInfo(); Hashtable hashtable = new Hashtable(); hashtable.Add("Password", "NULL"); PhotonNetwork.JoinRandomRoom(hashtable,2); //UIManager.Instance.PushUI("RoomModule", 0); }); uiModuleBase.GetWidget("RandomLobbyButton_F").AddOnClickListener(() => { SetPlayerInfo(); PhotonNetwork.JoinLobby(); //UIManager.Instance.PushUI("LobbyModule", 0); }); } void OnDropDownValueChange(int value) { if (value == 0) { uiModuleBase.GetWidget("BattleArrayDropdown_F").SetImageColor(Color.red); } else { uiModuleBase.GetWidget("BattleArrayDropdown_F").SetImageColor(Color.blue); } } }
当然,我们还需要在游戏一开始运行的时候,先自动push出我们的主页面,所以还需要一个类来启动框架。
我写了一个Facade:using System.Collections; using System.Collections.Generic; using UnityEngine; using UIFrame; public class Facade : MonoBehaviour { private void Start() { UIManager.Instance.PushUI("MainModule",0); } }
总结
纯手打,码字不易,如果对你有一丝丝帮助,请点个赞,收个藏,能给个关注就更好了!有不懂得可以私我或者下方评论交流。
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算