目录
归档
8137
2018 年 02 月 26 日
基于ET框架和UGUI的简单UI框架实现(渐渐写)

前言

此框架对应Et2.0版本,目前已过时!请跳转到最新的et3.3版本:http://www.tinkingli.com/?p=270

本框架是在ET框架上进行修改的,使用的是Unity自带的UGUI。
其实ET本身就带有简单的UI框架,建议学习之前先看懂ET里面的UI组件,主要看UIComponent,IUIFactory,UI及与其相关的类,明白ET中整个UI的运作流程。
其实ET中UI组件一般来说也是够用了,不过我是由原本项目移植到ET框架,我打算部分沿用原框架中的UI架构,再结合ET中的UI组件,就衍生出了这篇文章,一个“基于ET框架和UGUI的简单UI框架”。

框架介绍

本框架特点:

1、层级分明,本框架把UI分成5层,层级依次递增,在Unity面板设置好属性后自动加载到该层

2、关闭界面不直接销毁UI物体,而是调用Close方法,并把界面移到UIHiden层进行隐藏,等待下次Show调用,这样可以避免重复加载和实例化UI物体,当你部分UI界面可能出现多次关闭和打开操作时,能减轻运行压力

3、只需进行一次获取引用操作,一般我们会在Awake中通过ReferenceCollector获取需要引用到的物体(例如:返回按钮,输入文本框等),而Show中负责展示界面逻辑(文本框内容初始化,弹出动画等),因为Awake只会调用一次,而Show则每次打开都会调用,所以能减少GetComponent,以及从ReferenceCollector中取出物体的调用,节省性能的开销

4、拓展方便,假如想要拓展什么想要的功能,大部分都可直接在UIBaseComponent中直接修改即可(下面实例拓展了两个我用到的事件OnCloseOneTime和OnClose,大家用不到可直接去掉)

5、当然也可以手动调用UIComponent.Remove()方法进行真正的移除UI操作(直接销毁物体),有需要的也可以自行实现一个定时卸载操作,就是关闭(Close)一定时间后没有再打开(Show)的UI实行移除(Remove),节约内存

本框架主要是先把所有UI分成5层:
不同层根据名字有不同作用,UIHiden比较特殊,而另外的4层根据名称意思,层级逐层递增
UIHiden:隐藏层,当调用Close的时候,实际上是把UI物体移到该层中进行隐藏
Bottom:底层,一般用来放置最底层的UI
Medium:中间层,比较常用,大部分界面均是放在此层
Top:上层,一般是用来放各种弹窗,小窗口之类的
TopMost:最上层,一般用来做各种遮罩层,屏蔽输入,或者切换动画等

Unity工程中的修改

1.在Model命名空间下添加WindowLayer枚举

namespace Model
{
    //窗体的层级类型
    public enum WindowLayer
    {
        UIHiden = 0,
        Bottom = 1,
        Medium = 2,
        Top = 3,
        TopMost = 4,
    }
}

2.修改CanvasConfig类,添加UiWindowLayer字段,该字段表明此UI属于哪一层。

public class CanvasConfig: MonoBehaviour
{
	public string CanvasName;
	public WindowLayer UiWindowLayer = WindowLayer.Medium;
}

然后直接可以在Unity面板中修改层级

TIM截图20180225093837

UIComponent中的修改(Hotfix工程)

1.添加两个方法,初始化UI层级和修改UI层级

 

//初始化UI层级

private void InstantiateUi(Transform parent)
        {
            WindowLayer[] _names = new WindowLayer[] {
                WindowLayer.UIHiden,
                WindowLayer.Bottom,
                WindowLayer.Medium,
                WindowLayer.Top,
                WindowLayer.TopMost
            };
            Camera _cam = new GameObject().AddComponent<Camera>();
            _cam.clearFlags = CameraClearFlags.Depth;
            _cam.cullingMask = 1 << LayerMask.NameToLayer("UI");
            _cam.orthographic = true;
            _cam.depth = 10;
            _cam.name = "UiCamera";
            _cam.transform.SetParent(parent);
            _cam.transform.localPosition = Vector3.zero;

            foreach (var layer in _names)
            {
                var it = layer.ToString();
                GameObject _go = new GameObject();
                this.m_allLayers.Add(layer, _go);
                Canvas _canvas = _go.AddComponent<Canvas>();
                _canvas.renderMode = RenderMode.ScreenSpaceCamera;
                _canvas.worldCamera = _cam;
                _canvas.sortingOrder = (int)layer;
                CanvasScaler _scale = _go.AddComponent<CanvasScaler>();
                _scale.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
                _scale.referenceResolution = new Vector2(1920, 1080);
                _scale.matchWidthOrHeight = 1;
                GraphicRaycaster _graphic = _go.AddComponent<GraphicRaycaster>();
                _go.name = it;
                _go.transform.SetParent(parent);
                _go.transform.localPosition = Vector3.zero;
                if (layer == WindowLayer.UIHiden)
                {
                    _go.layer = LayerMask.NameToLayer("UIHiden");
                    _graphic.enabled = false;
                }
                else
                {
                    _go.layer = LayerMask.NameToLayer("UI");
                }
            }
        }

//修改UI层级
private void SetViewParent(UI ui, WindowLayer layer)
	    {
	        RectTransform _rt = ui.GameObject.GetComponent<RectTransform>();
	        _rt.SetParent(m_allLayers[layer].transform);
	        _rt.anchorMin = Vector2.zero;
	        _rt.anchorMax = Vector2.one;
	        _rt.offsetMax = Vector2.zero;
	        _rt.offsetMin = Vector2.zero;
	        _rt.pivot = new Vector2(0.5f, 0.5f);

	        _rt.localScale = Vector3.one;
	        _rt.localPosition = Vector3.zero;
	        _rt.localRotation = Quaternion.identity;
	    }

2.修改Awake方法,调用InstantiateUi

public void Awake()
{
	this.Root = GameObject.Find("Global/UI/");
	this.InstantiateUi(Root.transform);
	this.Load();
}

运行效果如图:
v2-2d746c2880ab6df59b2500ee71551cdd_hd
3.修改Create方法

public UI Create(UIType type)
	    {
	        try
	        {
	            UI ui;

	            if (uis.ContainsKey(type))
	            {
	                ui = uis[type];
	            }
	            else
	            {
	                ui = UiTypes[type].Create(this.GetParent<Scene>(), type, Root);
	                uis.Add(type, ui);
	            }

	            // 设置Ui层级
	            SetViewParent(ui, ui.GameObject.GetComponent<CanvasConfig>().UiWindowLayer);
	            //调用Show方法
                    ui.UiComponent.Show();

	            return ui;
	        }
	        catch (Exception e)
	        {
	            throw new Exception($"{type} UI 错误: {e.ToStr()}");
	        }
	    }

4.增加Close方法(关闭UI界面)

    public void Close(UIType type)
	    {
	        UI ui;
	        if (!uis.TryGetValue(type, out ui))
	        {
	            return;
	        }
	        uis[type].UiComponent.Close();
	        SetViewParent(uis[type], WindowLayer.UIHiden);
	    }

后面我会贴出整个修改后的UIComponent

其他修改

1.添加UIBaseComponent组件,每个UI界面预制体只能存在一个此组件,用于管理此UI主要行为,如:Show,Close等
PS:OnCloseOneTime和OnClose是因为我项目用到才加的,各位各取所需,不用的可以去掉,或者想要什么功能都可以在这个组件中拓展

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Hotfix
{
     public abstract class UIBaseComponent:Component
     {
         //单次关闭界面调用,调用后清空
         public event Action OnCloseOneTime;
         //每次关闭界面调用,调用后不清空
         public event Action OnClose;

        public virtual void Show()
        {
            GetParent<UI>().GameObject.SetActive(true);
        }

        public virtual void Close()
        {
            GetParent<UI>().GameObject.SetActive(false);

            if (OnCloseOneTime!=null)
            {
                OnCloseOneTime.Invoke();
                OnCloseOneTime = null;
            }
            OnClose?.Invoke();
        }
    }
}

2.UI类中添加UiComponent属性,并添加一系列AddUiComponent方法
UiComponent:代表当前UI中的主ui组件,Show和Close均是调用于此
AddUiComponent:用于给UiComponent属性赋值

    public UIBaseComponent UiComponent { get; private set; }

        /// <summary>
        /// 添加主UI组件,继承自UIBaseComponent
        /// </summary>
        /// <typeparam name="K"></typeparam>
        /// <returns></returns>
	    public K AddUiComponent<K>() where K : UIBaseComponent, new()
	    {	        
            UiComponent = this.AddComponent<K>();
	        return (K) UiComponent;
	    }

	    public K AddUiComponent<K, P1>(P1 p1) where K : UIBaseComponent, new()
	    {
	        UiComponent = this.AddComponent<K, P1>(p1);
	        return (K)UiComponent;
        }

	    public K AddUiComponent<K, P1, P2>(P1 p1, P2 p2) where K : UIBaseComponent, new()
	    {
	        UiComponent = this.AddComponent<K, P1, P2>(p1,p2);
	        return (K)UiComponent;
        }

	    public K AddUiComponent<K, P1, P2, P3>(P1 p1, P2 p2, P3 p3) where K : UIBaseComponent, new()
	    {
	        UiComponent = this.AddComponent<K, P1, P2, P3>(p1,p2,p3);
	        return (K)UiComponent;
        }

		public override void Dispose()
		{
			if (this.Id == 0)
			{
				return;
			}
			
			base.Dispose();

		    //设置为空
		    UiComponent = null;

            foreach (UI ui in this.children.Values)
			{
				ui.Dispose();
			}
			
			UnityEngine.Object.Destroy(GameObject);
			children.Clear();
		}

3.新建一个测试类UITestComponent

public class UITestComponent: UIBaseComponent
    {
        public static AccountPanelClose OnAccountPanelClose;
        public static UIAccountInfo Info;

        private Button _backBtn;//返回按钮

        //只在初始化的时候调用一次,用于获取各种引用
        public void Awake()
        {
            ReferenceCollector rc = this.GetParent<UI>().GameObject.GetComponent<ReferenceCollector>();

            //获取返回按钮引用
            _backBtn = rc.GetUnityComponent<Button>("backBtn");
            _backBtn.onClick.AddListener(OnClickBack);
        }

        //每次弹出该UI调用一次
        public override void Show()
        {
            base.Show();

        }

        //每次关闭该UI调用一次
        public override void Close()
        {
            base.Close();

        }

        //点击返回
        public void OnClickBack()
        {
            Hotfix.Scene.GetComponent<UIComponent>().Close(UIType.UITest);
        }
    }
}

4.添加UITestFactory工厂,用于加载并实例化该类,(根据需要可进行适当修改)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model;
using UnityEngine;

namespace Hotfix
{
    [UIFactory((int)UIType.UIAccount)]
    public class UITestFactory : IUIFactory
    {
        public UI Create(Scene scene, UIType type, GameObject gameObject)
        {
            try
            {
                ResourcesComponent resourcesComponent = Game.Scene.GetComponent<ResourcesComponent>();
                resourcesComponent.LoadBundle($"{type}.unity3d");
                GameObject bundleGameObject = resourcesComponent.GetAsset<GameObject>($"{type}.unity3d", $"{type}");
                GameObject newUi = UnityEngine.Object.Instantiate(bundleGameObject);
                newUi.layer = LayerMask.NameToLayer(LayerNames.UI);
                UI ui = EntityFactory.Create<UI, Scene, UI, GameObject>(scene, null, newUi);

                //调用前面添加的方法,UITestComponent继承自UIBaseComponent
                ui.AddUiComponent<UITestComponent>();
                return ui;
            }
            catch (Exception e)
            {
                Log.Error(e.ToStr());
                return null;
            }
        }

        public void Remove(UIType type)
        {
            Game.Scene.GetComponent<ResourcesComponent>().UnloadBundle($"{type}.unity3d");
        }
    }
}

总结

以上就是本框架的全部内容了,想要添加UI自身的功能可以在UIBaseComponent中拓展,想要添加管理UI的功能可以在UIComponent中拓展,这里仅仅提供一个思路给大家,还是那句话,各取所需,各位可以根据自己项目实际情况进行一定的改良。

附录(补充两个修改后的类)

1.UIComponent

using System;
using System.Collections.Generic;
using System.Linq;
using Model;
using UnityEngine;
using UnityEngine.UI;

namespace Hotfix
{
	[ObjectEvent]
	public class UIComponentEvent : ObjectEvent<UIComponent>, IAwake, ILoad
	{
		public void Awake()
		{
			this.Get().Awake();
		}

		public void Load()
		{
			this.Get().Load();
		}
	}

	/// <summary>
	/// 管理所有UI
	/// </summary>
	public class UIComponent: Component
	{
		private GameObject Root;
		private Dictionary<UIType, IUIFactory> UiTypes;
		private readonly Dictionary<UIType, UI> uis = new Dictionary<UIType, UI>();

	    private Dictionary<WindowLayer, GameObject> m_allLayers = new Dictionary<WindowLayer, GameObject>();

        public override void Dispose()
		{
			if (Id == 0)
			{
				return;
			}

			base.Dispose();

			foreach (UIType type in uis.Keys.ToArray())
			{
				UI ui;
				if (!uis.TryGetValue(type, out ui))
				{
					continue;
				}
				uis.Remove(type);
				ui.Dispose();
			}
		}

		public void Awake()
		{
			this.Root = GameObject.Find("Global/UI/");
		    this.InstantiateUi(Root.transform);
            this.Load();
		}


		public void Load()
		{
            UiTypes = new Dictionary<UIType, IUIFactory>();
            
            Type[] types = DllHelper.GetHotfixTypes();

			foreach (Type type in types)
			{
				object[] attrs = type.GetCustomAttributes(typeof (UIFactoryAttribute), false);
				if (attrs.Length == 0)
				{
					continue;
				}
                
				UIFactoryAttribute attribute = attrs[0] as UIFactoryAttribute;
				if (UiTypes.ContainsKey((UIType)attribute.Type))
				{
                    Log.Debug($"已经存在同类UI Factory: {attribute.Type}");
					throw new Exception($"已经存在同类UI Factory: {attribute.Type}");
				}
				object o = Activator.CreateInstance(type);
				IUIFactory factory = o as IUIFactory;
				if (factory == null)
				{
					Log.Error($"{o.GetType().FullName} 没有继承 IUIFactory");
					continue;
				}
				this.UiTypes.Add((UIType)attribute.Type, factory);
			}
		}

        #region 修改

        private void InstantiateUi(Transform parent)
        {
            WindowLayer[] _names = new WindowLayer[] {
                WindowLayer.UIHiden,
                WindowLayer.Bottom,
                WindowLayer.Medium,
                WindowLayer.Top,
                WindowLayer.TopMost
            };
            Camera _cam = new GameObject().AddComponent<Camera>();
            _cam.clearFlags = CameraClearFlags.Depth;
            _cam.cullingMask = 1 << LayerMask.NameToLayer("UI");
            _cam.orthographic = true;
            _cam.depth = 10;
            _cam.name = "UiCamera";
            _cam.transform.SetParent(parent);
            _cam.transform.localPosition = Vector3.zero;

            foreach (var layer in _names)
            {
                var it = layer.ToString();
                GameObject _go = new GameObject();
                this.m_allLayers.Add(layer, _go);
                Canvas _canvas = _go.AddComponent<Canvas>();
                _canvas.renderMode = RenderMode.ScreenSpaceCamera;
                _canvas.worldCamera = _cam;
                _canvas.sortingOrder = (int)layer;
                CanvasScaler _scale = _go.AddComponent<CanvasScaler>();
                _scale.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
                _scale.referenceResolution = new Vector2(1920, 1080);
                _scale.matchWidthOrHeight = 1;
                GraphicRaycaster _graphic = _go.AddComponent<GraphicRaycaster>();
                _go.name = it;
                _go.transform.SetParent(parent);
                _go.transform.localPosition = Vector3.zero;
                if (layer == WindowLayer.UIHiden)
                {
                    //GameTool.SetLayer(_go, WindowLayer.UIHiden.ToString());
                    _go.layer = LayerMask.NameToLayer("UIHiden");
                    _graphic.enabled = false;
                }
                else
                {
                    //GameTool.SetLayer(_go, "UI");
                    _go.layer = LayerMask.NameToLayer("UI");
                }
            }
        }

        public UI Create(UIType type)
	    {
	        try
	        {
	            UI ui;

	            if (uis.ContainsKey(type))
	            {
	                ui = uis[type];
	            }
	            else
	            {
	                ui = UiTypes[type].Create(this.GetParent<Scene>(), type, Root);
	                uis.Add(type, ui);
	            }

	            // 设置canvas
	            SetViewParent(ui, ui.GameObject.GetComponent<CanvasConfig>().UiWindowLayer);
	            ui.UiComponent.Show();

	            return ui;
	        }
	        catch (Exception e)
	        {
	            throw new Exception($"{type} UI 错误: {e.ToStr()}");
	        }
	    }

	    public void Close(UIType type)
	    {
	        UI ui;
	        if (!uis.TryGetValue(type, out ui))
	        {
	            return;
	        }
	        uis[type].UiComponent.Close();
	        SetViewParent(uis[type], WindowLayer.UIHiden);
	    }

	    private void SetViewParent(UI ui, WindowLayer layer)
	    {
	        RectTransform _rt = ui.GameObject.GetComponent<RectTransform>();
	        _rt.SetParent(m_allLayers[layer].transform);
	        _rt.anchorMin = Vector2.zero;
	        _rt.anchorMax = Vector2.one;
	        _rt.offsetMax = Vector2.zero;
	        _rt.offsetMin = Vector2.zero;
	        _rt.pivot = new Vector2(0.5f, 0.5f);

	        _rt.localScale = Vector3.one;
	        _rt.localPosition = Vector3.zero;
	        _rt.localRotation = Quaternion.identity;
	    }

        #endregion

        public void Add(UIType type, UI ui)
		{
			this.uis.Add(type, ui);
		}

		public void Remove(UIType type)
		{
			UI ui;
			if (!uis.TryGetValue(type, out ui))
			{
				return;
			}
            UiTypes[type].Remove(type);
            uis.Remove(type);
            ui.Dispose();
            //SetViewParent(ui,WindowLayer.UIHiden);
        }

        public void RemoveAll()
		{
			foreach (UIType type in this.uis.Keys.ToArray())
			{
				UI ui;
				if (!this.uis.TryGetValue(type, out ui))
				{
					continue;
                }
                this.uis.Remove(type);
				ui.Dispose();
			}
		}

		public UI Get(UIType type)
		{
			UI ui;
			this.uis.TryGetValue(type, out ui);
			return ui;
		}

		public List<UIType> GetUITypeList()
		{
			return new List<UIType>(this.uis.Keys);
		}
	}
}

2.UI

using System.Collections.Generic;
using UnityEngine;

namespace Hotfix
{
	[Model.ObjectEvent]
	public class UIEvent : ObjectEvent<UI>, IAwake<Scene, UI, GameObject>
	{
		public void Awake(Scene scene, UI parent, GameObject gameObject)
		{
			this.Get().Awake(scene, parent, gameObject);
		}
	}
	
	
	public sealed class UI: Entity
	{
		public Scene Scene { get; set; }

		public string Name
		{
			get
			{
				return this.GameObject.name;
			}
		}

		public GameObject GameObject { get; private set; }

		public Dictionary<string, UI> children = new Dictionary<string, UI>();

        #region 修改

        public UIBaseComponent UiComponent { get; private set; }

        /// <summary>
        /// 添加主UI组件,继承自UIBaseComponent
        /// </summary>
        /// <typeparam name="K"></typeparam>
        /// <returns></returns>
	    public K AddUiComponent<K>() where K : UIBaseComponent, new()
	    {	        
            UiComponent = this.AddComponent<K>();
	        return (K) UiComponent;
	    }

	    public K AddUiComponent<K, P1>(P1 p1) where K : UIBaseComponent, new()
	    {
	        UiComponent = this.AddComponent<K, P1>(p1);
	        return (K)UiComponent;
        }

	    public K AddUiComponent<K, P1, P2>(P1 p1, P2 p2) where K : UIBaseComponent, new()
	    {
	        UiComponent = this.AddComponent<K, P1, P2>(p1,p2);
	        return (K)UiComponent;
        }

	    public K AddUiComponent<K, P1, P2, P3>(P1 p1, P2 p2, P3 p3) where K : UIBaseComponent, new()
	    {
	        UiComponent = this.AddComponent<K, P1, P2, P3>(p1,p2,p3);
	        return (K)UiComponent;
        }

        #endregion

        public void Awake(Scene scene, UI parent, GameObject gameObject)
		{
			this.children.Clear();
			
			this.Scene = scene;

			if (parent != null)
			{
				gameObject.transform.SetParent(parent.GameObject.transform, false);
			}
			this.GameObject = gameObject;
		}

		public override void Dispose()
		{
			if (this.Id == 0)
			{
				return;
			}
			
			base.Dispose();

		        UiComponent = null;

			foreach (UI ui in this.children.Values)
			{
				ui.Dispose();
			}
			
			UnityEngine.Object.Destroy(GameObject);
			children.Clear();
		}

		public void SetAsFirstSibling()
		{
			this.GameObject.transform.SetAsFirstSibling();
		}

		public void Add(UI ui)
		{
			this.children.Add(ui.Name, ui);
		}

		public void Remove(string name)
		{
			UI ui;
			if (!this.children.TryGetValue(name, out ui))
			{
				return;
			}
			this.children.Remove(name);
			ui.Dispose();
		}

		public UI Get(string name)
		{
			UI child;
			if (this.children.TryGetValue(name, out child))
			{
				return child;
			}
			GameObject childGameObject = this.GameObject.transform.Find(name)?.gameObject;
			if (childGameObject == null)
			{
				return null;
			}
			child = EntityFactory.Create<UI, Scene, UI, GameObject>(this.Scene, this, childGameObject);
			this.Add(child);
			return child;
		}
	}
}

转载原创文章请注明,转载自: Tinkingli博客 » 基于ET框架和UGUI的简单UI框架实现(渐渐写)
1条评论
Loading...

发表评论

电子邮件地址不会被公开。 必填项已用*标注