unity3D之实现一个简单的UI框架

Unity项目UI管理简易框架 一个易于实现且扩展性比较强的,包含了UI的显示隐藏、数据加载、事件监听事件系统等功能UI界面管理的简易框架的实现思路。 阅读详情

写在前面的话

我会先把源码直接给出来。博主本身也不是什么大牛,所以写的也不是什么很厉害的框架,只是一个很小很小的UI框架,给初学者一点入门的思路。
很多人在刚开始接触框架的时候都会有一种感觉:这NM什么DDX啊?!完全懵逼了有没有?!我还没开始写代码呢!怎么就已经有这么多代码要看啊?!还有大部分新人都会吐槽的事情:为什么要这么写啊?这玩意随便写个脚本挂上去不是分分钟实现功能吗?你这绕来绕去搞毛?这个主程是不是NT?!emmmmmmmmmmm…如果你知道主程怎么想的,你不就是主程了?
所以,初次接触框架的话,可以抱怨,但是请尽快冷静下来,好好看代码。起码要以最快的速度会用这个框架,在慢慢去分析为什么框架要这么写。其实看完后你就会发现,主程写的框架实现一个功能确实很麻烦,但是你用起来的时候却非常方便,因为很多东西主程早早的就给你封装在框架里了,你只需要干一些不动脑子的体力活就实现了需求,爽就一个字!这就是为什么要使用框架,为什么主程要弯弯绕绕的实现功能。

好,如果你做好了思想准备,那么就先下载源码,读起来吧!这真的是一个很简单的UI框架,内容不多。读起来应该很容易。
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;
            }
        }
    }
}

很简单,只有20行代码。这个脚本只做了一件事,就是实现一个单例。特殊之处在于,这是一个单例的带有泛型的基类,使用反射的方式搞定的单例,那么他的作用呢?也很简单,只要是你想要把一个脚本做成单例模式,那么只需要继承这个脚本,再私有化无参构造即可。文字可能解释的不是很清楚,我们结合代码来看,接下来我们来看看AssetsManager:

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;
        }
    }
}

AssetsManager脚本是我用来做资源管理的脚本,那么可能会在很多其他地方要用到,那每次要用到我都去实例化就太麻烦了,而且也不利于资源管理,所以我把他做成单例。怎么做?就利用上面的Singleton脚本来实现,我们让AssetsManager继承Singleton,再私有化构造函数,就自然而然的完成了我们的需求,以后再有其它脚本要实现单例,也这样如法炮制即可。这里就已经体现了一些些框架的威力。也可以说是设计原则的好处。不展开了,回到正题。那么这个脚本管理的是什么资源呢?其实,什么资源都可以,但我们主要是针对UI资源进行管理,首先我们写了一个缓存池,用来缓存已经取过的资源用以二次利用。然后就是一个获取资源的方法了,很简单,就是传过来一个资源的所在路径,通过路径将资源拿出来放进缓存后返回该资源,自己看看应该就能看懂。
好,那这个脚本就不多说了,我们看下一个,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];
        }
    }
}

同样的,JsonPanelManager 这个脚本我们也继承了单例,那么这个脚本主要是用来做什么的呢?很简单,就是解析json文件,将json文件中存储的资源路径都给拿出来,然后再利用上一个脚本将资源拿出来放进缓存(也就是我们自己定义的字典里面,字典有多好用就不用我多说了吧)。至于json文件的解析,本篇博客也不赘述,都来看框架了,json解析这种东西应该没问题吧?那这个脚本中除了获取UI模块资源,还写了一个获取UI动态元件和本地化的json解析,暂时用不到,先不说。
好,那这个文件夹就只剩最后一个脚本了,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" };
}

SystemDefaine这个脚本,单纯是用来保存常数的,注意,这是一个静态类,暂时我们只用到了最上面的json文件路径的字符串常数。也没什么特殊的。其他的用到再说。

UIBases文件夹

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);
        }
    }
}

UIWidgetBase 是一个通过代码添加到所有以“_F”结尾的UI元件身上的脚本组件。他继承了UIMono,UIMono则实现了很多关于UI组件操作的接口,详情自己去看看UIInterface文件夹,因为过于简单,所以博主不做讲解。

我们再来看UIModuleBase:

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;
        }
    }
}

UIModuleBase这个脚本是挂载在所有UI模块上的。但是,不是直接挂载,而是每个模块自己写一个脚本继承这个脚本之后挂载。并且需要重写这个脚本的awake方法。具体的解释可以仔细看看代码以及注释,不难理解。值得一提的是,一定要记得绑定相对应的Controller,不然无法实现相关功能。

那么最后我们来说说UIControllerBase:

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()
        {
        }
    }
}

UIControllerBase跟UIModuleBase 一样,每个UI模块都需要写一个自身的控制器继承这个UIControllerBase,然后不需要挂载,因为他跟UIModuleBase 绑定在一起,可以直接调用。具体用法我们后面说。我们先接着看框架。

Managers文件夹

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);
    }
}

MainModule继承了UIModuleBase ,并挂载在UI模块mainPanel上面。

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);
        }
    }

}

MainController 继承了UIControllerBase ,并与MainModule 绑定在一起。通过uiModuleBase的GetWidget(string widgetName)方法可以拿到相应的UI元件,并通过UIMono中实现的接口方法完成所要实现的需求。这里做的是一个网络对战游戏的主界面,所以实现的都是网络相关功能。也只是一些简单的添加点击实现,获取UI输入框中的内容,更改UI输入框中的内容,都是可以一句话就解决,非常方便,也非常酷。
当然,我们还需要在游戏一开始运行的时候,先自动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);
    }
}

这个脚本很简单,只是将主模块在游戏开始运行时push出来。你也可以有自己的想法。

总结

如果你看到了这里,那么恭喜你,你已经对UI框架有了一个最最最基础的认知。这是一个很简单的UI框架,代码量总共也没多少,真正的框架可比这个复杂的多,也不是一篇博客就能说得清的,如果想深入学习,可以去找找大牛写的框架,本博客只做入门用,甚至都算不上。希望能对小白有些帮助。一起加油吧!

纯手打,码字不易,如果对你有一丝丝帮助,请点个赞,收个藏,能给个关注就更好了!有不懂得可以私我或者下方评论交流。

【Unity3D框架】关于手游的UI框架的一些思考 Unity手机游戏UI框架设计和思考。 阅读详情

相关推荐

用Unity3D实现简单的RPG游戏

用Unity3D实现简单的RPG游戏

qqrrjj2011的博客 4421

Unity进阶第四章-UI框架

UI框架

weixin_43853077的博客 2848

Unity3D之UI框架

每个游戏的有UI,合理的UI管理可以更加节省游戏资源。本篇是对UI框架的总结。   UI框架就是把所有的UI做成Prefab存进Json文件中,等触发的时候再通过点击事件实例化出相应的UI预制体。这样做的好处是,游戏面板上什么都没有,用到什么就生成什么,为了避免重复生成,采用单例的方式来实现。   一、搭建UI界面。      二、写代码:  

xiaoshuang_的博客 960

Unity-基础UI框架的编写

Unity-UI框架

qq_53663718的博客 1661

基于MVVM模式的Unity3D UI框架设计与实现

在Unity3D开发中,构建高效、可维护的UI系统是项目成功的关键。随着项目规模的增长,传统的UI开发方式往往导致代码臃肿、耦合度高、难以维护。因此,引入结构清晰、模块化的UI框架成为大型项目不可或缺的一环。Unity的UI系统从最初的IMGUI逐步演进到现在的UGUI,再到基于DOTween、UI Toolkit等第三方解决方案,其设计理念始终围绕着性能、灵活性与可扩展性展开。一个优秀的UI框架不仅能够提升开发效率,还能降低UI与业务逻辑之间的耦合度,便于团队协作与长期维护。

weixin_35433448的博客 1169

Jetson Nano系统重装的艺术:从格式化到烧录的深度解析

本文详细解析了Jetson Nano系统重装的完整流程,从SD卡格式化到系统镜像烧录的进阶技巧,包括硬件选择、软件准备、常见问题处理及深度学习环境配置。特别推荐使用三星EVO Plus SD卡和官方电源以确保稳定性,适合开发者快速掌握嵌入式设备系统重装的核心技术。

weixin_28273593的博客 610

Unity 简易的UI框架

Unity UI框架

weixin_44238530的博客 618

从原理到实现:在《图灵完备》中构建一个144*256像素、1536时钟刻/帧的视频播放器

本文详细解析了在《图灵完备》游戏中构建144*256像素、1536时钟刻/帧视频播放器的全过程。从图灵完备原理出发,通过分层架构设计、视频数据预处理和关键电路实现,展示了如何用基础电子元件模拟现代显卡功能。重点介绍了像素坐标定位系统和颜色数据处理流水线的优化方案,为游戏内复杂电子系统设计提供实践指导。

weixin_42520025的博客 445

Unity实现一个简单易用的UI框架

1、面板管理类 using System.Collections; using System.Collections.Generic; using UnityEngine; public class PanelMgr:MonoBehaviour { //单例 public static PanelMgr instance; //画板 private GameObject canvas; //面板队列,记录已经打开的界面 public List<PanelBase> dict.

游戏开发、游戏引擎开发、逆向破解爱好者 1013

大模型-CLIP 详细介绍

最先进的计算机视觉系统通常被训练来预测一组预定义的对象类别。这种受限的监督形式限制了它们的通用性和可用性,因为要指定任何其他视觉概念,就需要额外的标注数据。比如,它们可以识别“猫”、“狗”、“汽车”等。但是,这种做法有一个明显的局限性:如果你想让系统识别一个新的类别,比如说“独角兽”,你就必须提供大量带有“独角兽”标签的图片来训练系统。这不仅耗时费力,而且限制了系统的灵活性和广泛适用性。直接从原始文本中学习关于图像的信息是一种很有前途的替代方案,因为它利用了更广泛的监督来源。

晓的博客 9710

AMP架构下的核间通信:RPMSG与共享内存原理详解

本文以RK3568等异构多核硬件为例,深入浅出解析了AMP架构下核间通信的核心逻辑,阐明RPMSG并非替代共享内存,而是以共享内存为数据载体、硬件信箱为中断通知工具、VirtIO为数据管理规则,构建的标准化高层协议与框架,为开发者提供简洁API,实现不同操作系统核心间高效通信。

qq_61335271的博客 2049

【Unity3D技巧】一个简单的Unity-UI框架的实现

【Unity3D技巧】一个简单的Unity-UI框架的实现 https://www.cnblogs.com/neverdie/p/unity_ui_framework.html 如何使用 请直接导入UnityUIFramework这个UnityPackage,然后进入名为Test的Scene即可开始体验各种特性,Enjoy!你可以通过访问我的Github进行查阅和下载。 View...

kuangben2000的博客 497

手机也能跑大模型?QNN框架实战:从模型量化到Android部署全流程

本文详细介绍了如何利用QNN框架将大模型部署到Android手机端进行端侧推理。通过实战演示了从模型量化(如INT4/INT8混合精度)到Android应用集成的全流程,包括内存优化、性能调优及常见问题排查,帮助开发者实现大模型在资源受限设备上的高效本地运行。

joy55的博客 499

unity 让ui对齐_【Unity3D技巧】一个简单的Unity-UI框架的实现

【Unity3D技巧】一个简单的Unity-UI框架的实现View,Context和UI的定义UI是游戏中首要界面和它的子节点上的物体的总称,如配备列表界面中的配备列表和每个配备通常会被制作成两个Prefab,这两个Prefab被我们称作两个UI,这两个UI会对应两个UIType,在UIType里边会存储有这个UI大局唯一的姓名和途径,如下:public class UIType { public...

weixin_39821718的博客 746

scratch lenet(4): 开根号的C语言实现

使用C语言,分别使用二分法、牛顿法、卡马克快速法,求解正实数的开根号。包括公式推导、代码实现、运行结果,不使用任何第三方库和C标准库的数学库。

baiyu33的博客 4969

Eplan设计师必会—伺服安全回路的设计方法

本文介绍了在EPLAN中设计伺服STO安全回路的方法。首先需查阅伺服驱动器说明书确定STO接线端子,重点关注双通道配置。设计步骤包括:使用黑盒子功能绘制驱动器外形,正确标注STO接线点,并连接安全继电器或PLC的安全输出。关键注意事项有:必须采用双通道独立布线,确保同步动作,遵循故障安全原则,使用常闭触点串联回路。文中以西门子V90和汇川伺服为例说明具体接线要求,并附有项目实例参考。

Qinmingyue13的博客 436

ui动效 unity_简易 Unity3D UI 框架

看见一篇介绍 Unity3D UI 框架编写的文章,并且给出了示例代码。然后去了解了一下。讲道理,示例代码蛮乱的,不知道有一些是不是直接从项目代码拷贝然后简单修改,对于一个简单的框架来说,有很多不必要的混乱逻辑。而且我又喜欢粉色,所以就重新编写了,话不多说先上运行图示。具体代码点这里。 想了解更多信息具体查看代码。这篇博客主要谈一谈 UI 框架基本的需求。UI 框架有很多实现方式,比如多个界面共用...

weixin_32722209的博客 1303

YOLO26改进策略【Neck】| ICCV2025 反向卷积 Converse2D 数学逆运算提重构 + 特征域建模降伪影,提升多尺度目标检测精度

本文记录的是利用Converse2D 算子改进 YOLO26 的上采样。(Converse 2D Convolution)通过正则化最小二乘闭式解与深度化空间建模结合,实现YOLO26特征提取与上采样阶段的卷积逆运算及特征精准重构。本文利用算子,通过闭式解的非迭代计算还原卷积下采样后的原始特征,再通过可学习核与自适应正则化参数稳定逆问题求解,同时依托圆形填充与插值初始估计保留特征高频细节,对YOLO26中不同尺度检测目标的特征进行针对性重构强化、抑制卷积/转置卷积带来的伪影与信息损失干扰,在特征融合与上采样

Limiiiing的博客 653

C#高级---设计模式---外观模式

【千锋合集】史上最全Unity3D全套教程|匠心之作_哔哩哔哩_bilibili 框架启动模式 using System.Collections; using System.Collections.Generic; using UnityEngine; public class UIFrameFacade { public void FrameStart() { UIAssets.instance.LoadPanel("MainPane...

renwen1579的博客 556

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Unity3D UI框架的设计架构与编码实现详解

在设计Unity3D UI框架时,我们可以借鉴MVC(Model-View-Controller)的设计思想,将UI系统划分为模型(Model)、视图(View)和控制器(Controller)三个主要部分。最后,我们定义一个资源管理器的基类,用于加载和管理UI资源。接下来,我们定义一个事件系统的基类,用于处理UI界面之间的事件传递和交互。下面将给出Unity3D UI框架的编码实现示例,包括UI管理器、事件系统和资源管理器的实现。首先,我们定义一个UI管理器的基类,用于管理UI界面的加载、卸载和显示。

Thomas_YXQ的博客 1464

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

《Unity3D高级编程 主程手记》第四章 用户界面(五) 快速构建一个简单易用的 UI 框架

首先它需要依赖 Unity3D 的 Animator 组件 [RequireComponent (typeof(Animator))]其次它要有播放(Play)接口用来播放指定动画,这里 Play 的参数包括动画名、播放完毕后的回调函数委托等。再次在 public 变量中需要 AutoPlay 这个参数,这样美术人员就可以在 Unity3D 界面上设置自动播放而无需程序调用了。最后需要在自动播放时选择指定的动画名和是否循环播放,以及循环播放间隔。

仁希的博客 1535

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Unity3D框架学习_基于FairyGUI的UI框架

Unity3D框架学习_基于FairyGUI的UI框架 目录 1、博客介绍 2、FGUI介绍 3、框架介绍 (1)Core (2)结构 (3)FGUI工程目录 (4)命名规范 4、推送 5、结语 1、博客介绍 最近一段时间一直都在使用FGUI,真心觉得非常好用,目前还处在学习阶段,在Github上扒了一些稍作整理...

孙君的空间站 4816

【Unity3D编辑器扩展】Unity3D中实现UI界面控制,UI界面的显示和隐藏实现

在开发中,可能遇到管理很多的UI界面,但是需要切换UI界面的情况。这种情况下,通常就是在Hierarchy视图中勾选需要显示的UI界面,关闭不需要显示的UI界面。这种操作很麻烦,所以就写了一个直接在脚本组件中控制UI界面的功能,可以直接切换UI界面,提升效率。

优快云博客专家,Unity3D高级软件工程师,《Unity 3D从入门到实战》作者。 8281
上一篇: U3DBUG积累之自制动画播放时位置会跑偏
下一篇: 献给Lua小白的Lua基础入门
第五枫咏
博客等级 码龄8年 60粉丝 54原创
评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值