本文是基于Unity3D开发游戏写的,设计理念也适用于其他引擎。代码您可以直接使用,转载时希望您附上原文链接,金大爷先表示感谢~
看完您可以学到以下内容:
框架的2个类:
gfBaseWindow和gfWindowManager,gf是GameFramework的简写,是个人代码习惯。先上代码:
gfBaseWindow.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// @CopyRight 李金朋 /// 所有Window的基类,所有子类只需要实现其abstract方法和构造方法即可。 /// 设计原则: /// 1.一个类型的Window在场景中只能new一个(否则会报错)。不同的Window显示的内容不一样,操作不一样,只要不同,就创建一个新的Window类型(无论Window功能多么简单)。 /// 如果除了显示内容,其它都一样,说明这些内容完全可以通过一个Window通过更新数据的方式完成。 /// 2.Window自身不提供控制打开、关闭等接口,全部操作都通过gfWindowManager完成。gfWindowManager会对Window的打开、关闭顺序等进行管理、控制,并保证Window操作的正确性。 /// 3.不关心Window Prefab用什么方案实现(NGUI或UGUI),但实现方案要统一。不同的实现方法,子窗口实现时对控件的操作会有所不同。 /// </summary> public abstract class gfBaseWindow { /// <summary> /// 初始化Window,从windowElements获取Window中需要操作(更新数据或状态)的组件。只会在Window创建时调用一次。 /// </summary> /// <param name="windowElements">包含WindowPrefab的所有非重复名称节点</param> protected abstract void OnWindowInitialize(Dictionary<string, GameObject> windowElements); /// <summary> /// Window打开前调用。更新数据、组件状态等逻辑写在此方法中。比如:更新控件内容,Reset动画并播放。 /// </summary> protected abstract void OnBeforeWindowOpen(); /// <summary> /// Window打开后调用。统计事件、Window打开后延时执行的等逻辑写在此方法中。比如:Window打开2秒后开始播放背景音乐。 /// </summary> protected abstract void OnAfterWindowOpen(); /// <summary> /// Window关闭前调用。统计事件等逻辑可以写在此方法中,一般不需要实现。 /// </summary> protected abstract void OnBeforeWindowClose(); /// <summary> /// Window关闭后调用。窗口关闭后要执行的逻辑可以写在此方法中。比如:窗口关闭后自动弹出广告。 /// </summary> protected abstract void OnAfterWindowClose(); /// <summary> /// Window销毁时调用。比如:清理Window中加载的资源。一般不需要实现,因为所有Window在场景切换时都会被销毁,并且Window不能主动调用Destroy。 /// </summary> protected abstract void OnWindowDestroy(); private GameObject m_WindowElementsRoot = null; private bool m_LogErrorIfWindowElementRepeated = true; /// <summary> /// 创建Window,创建成功后会添加windowManager中。 /// </summary> /// <param name="windowElementsRoot">Window Prefab的GameObject,其所有节点(自已和所有非重复名称的Child)都将添加到一个Dictionary中,做为OnWindowInitialize方法的参数。</param> /// <param name="windowManager">场景中管理Window的Manager</param> /// <param name="logErrorIfWindowElementRepeated">将所有windowElementsRoot的节点添加到Dictionary时,名称重复的节点将不会添加到Dictionary中。 /// 如果logErrorIfWindowElementRepeated值为true, 则编辑器运行会输出Error日志。</param> public gfBaseWindow(GameObject windowElementsRoot, gfWindowManager windowManager, bool logErrorIfWindowElementRepeated) { if (!gfDebug.Assert(windowManager != null, string.Format("创建Window前,必须在场景中创建gfWindowManager."))) { return; } m_WindowElementsRoot = windowElementsRoot; m_LogErrorIfWindowElementRepeated = logErrorIfWindowElementRepeated; if (m_WindowElementsRoot != null) { Transform windowTransform = m_WindowElementsRoot.transform; // 先Active,获取控件 windowTransform.localPosition = Vector3.up * -100000; m_WindowElementsRoot.SetActive(true); } Dictionary<string, GameObject> windowElements = GetAllWindowElements(m_WindowElementsRoot); OnWindowInitialize(windowElements); windowElements.Clear(); windowElements = null; if (m_WindowElementsRoot != null) { Transform windowTransform = m_WindowElementsRoot.transform; // 再Deactive m_WindowElementsRoot.SetActive(false); windowTransform = m_WindowElementsRoot.transform; windowTransform.SetParent(windowManager.windowsRoot); windowTransform.localPosition = Vector3.one; windowTransform.localScale = Vector3.one; } gfWindowManager.AddWindowAdapter addWindowAdapter = new gfWindowManager.AddWindowAdapter(); addWindowAdapter.AddWindow(windowManager, this); } public bool IsVisable() { return m_WindowElementsRoot != null && m_WindowElementsRoot.activeSelf; } /// <summary> /// 控制Window打开、关闭,并执行其流程方法。 /// </summary> /// <param name="visible"></param> private void SetVisible(bool visible) { if (visible) { OnBeforeWindowOpen(); if (m_WindowElementsRoot != null) { m_WindowElementsRoot.SetActive(true); } OnAfterWindowOpen(); } else { OnBeforeWindowClose(); if (m_WindowElementsRoot != null) { m_WindowElementsRoot.SetActive(false); } OnAfterWindowClose(); } } /// <summary> /// 将Window Prefab上的所有节点都添加到字典中。 /// </summary> /// <param name="windowElementsRoot"></param> /// <returns></returns> private Dictionary<string, GameObject> GetAllWindowElements(GameObject windowElementsRoot) { Dictionary<string, GameObject> windowElementsContainer = new Dictionary<string, GameObject>(); if (windowElementsRoot != null) { windowElementsContainer.Add(windowElementsRoot.name, windowElementsRoot); AddChildWindowElements(windowElementsContainer, windowElementsRoot); } return windowElementsContainer; } private void AddChildWindowElements(Dictionary<string, GameObject> windowElementsContainer, GameObject windowElementsRoot) { Transform windowRootTrans = windowElementsRoot.transform; int childCount = windowRootTrans.childCount; for (int i = 0; i < childCount; ++i) { Transform child = windowRootTrans.GetChild(i); if (!windowElementsContainer.ContainsKey(child.name)) { windowElementsContainer.Add(child.name, child.gameObject); } else { #if UNITY_EDITOR if (m_LogErrorIfWindowElementRepeated) { Debug.LogError(string.Format("[此错误仅在Editor下输出]: Window节点({0})中存在重复名称的节点: {1},有可能导致Window初始化错误。", windowElementsRoot.name, child.name)); } #endif continue; } if (child.childCount > 0) { AddChildWindowElements(windowElementsContainer, child.gameObject); } } } private void Destroy() { OnWindowDestroy(); Object.Destroy(m_WindowElementsRoot); } // 以前是为了实现方法隐藏的Adapter,只会在gfWindowManager中使用。 public class SetWindowVisibleAdapter { public void SetVisible(gfBaseWindow window, bool visible) { window.SetVisible(visible); } } public class DestroyWindowAdapter { public void DestroyWindow(gfBaseWindow window) { window.Destroy(); } } }
gfWindowManager.cs
using System; using System.Collections.Generic; using UnityEngine; /// <summary> /// @CopyRight 李金朋 /// 管理场景中创建的所有Window。所有Window的打开或关闭都必须通过gfWindowManager完成。 /// 设计原则: /// 1.一个场景中必须创建一个gfWindowManager,并设置windowsRoot,否则创建Window时会报错。gfWindowManager和所有的Window在场景切换时都会被Destroy。 /// 2.每个场景根据当前场景中需要的Window,各自创建Window。Window创建后会自动添加到gfWindowManager中。 /// 4.gfWindowManager不会对Window间的层级进行控制,所有Window间的层级的在设计Window Prefab时设计好,运行时不允许修改。 /// </summary> public class gfWindowManager : MonoBehaviour { public Transform windowsRoot; public static gfWindowManager Instance { get { return ms_Instance; } } private static gfWindowManager ms_Instance = null; private static Stack<gfBaseWindow> ms_VisibleWindowStack = null; private static Dictionary<Type, gfBaseWindow> ms_SearchCache = null; public T GetWindow<T>() where T : gfBaseWindow { gfBaseWindow window = null; if (ms_SearchCache.TryGetValue(typeof(T), out window)) { return window as T; } return null; } /// <summary> /// 打开一个Window。 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="closeInFocusWindow">是否关闭当前拥有焦点的Window</param> public void OpenWindow<T>(bool closeInFocusWindow) where T : gfBaseWindow { T window = GetWindow<T>(); if (window != null) { if (!ms_VisibleWindowStack.Contains(window)) { if (closeInFocusWindow) { CloseInFocusWindow(); } ms_VisibleWindowStack.Push(window); SetWindowVisible(window, true); } else { Debug.LogError(string.Format("Window({0})已打开,不能重复打开。", typeof(T))); } } else { gfDebug.Assert(false, string.Format("Scene中没有创建类型为{0}的Window。", typeof(T))); } } /// <summary> /// 关闭当前拥有焦点的Window,也即显示在最前边的Window。 /// </summary> public void CloseInFocusWindow() { if (ms_VisibleWindowStack.Count == 0) return; gfBaseWindow window = ms_VisibleWindowStack.Pop(); SetWindowVisible(window, false); } /// <summary> /// 关闭所有打开的Window。 /// </summary> public void CloseAllWindows() { while (ms_VisibleWindowStack.Count > 0) { gfBaseWindow window = ms_VisibleWindowStack.Pop(); SetWindowVisible(window, false); } } /// <summary> /// 关闭所有比Window T后打开的Window。 /// </summary> /// <typeparam name="T"></typeparam> public void CloseToWindow<T>() where T : gfBaseWindow { T targetWindow = GetWindow<T>(); if (targetWindow != null && ms_VisibleWindowStack.Contains(targetWindow)) { while (ms_VisibleWindowStack.Peek() != targetWindow) { gfBaseWindow currentWindow = ms_VisibleWindowStack.Pop(); SetWindowVisible(currentWindow, false); } } else { Debug.LogError(string.Format("Window({0})没有被Open,不能执行Close操作。", typeof(T))); } } private void SetWindowVisible(gfBaseWindow window, bool visible) { gfBaseWindow.SetWindowVisibleAdapter setWindowVisibleAdapter = new gfBaseWindow.SetWindowVisibleAdapter(); setWindowVisibleAdapter.SetVisible(window, visible); } private void Awake() { // 注意:更换场景后会销毁 ms_SearchCache = new Dictionary<Type, gfBaseWindow>(); ms_VisibleWindowStack = new Stack<gfBaseWindow>(); ms_Instance = this; gfDebug.Assert(windowsRoot != null, string.Format("gfWindowManager没有设置WindowsRoot")); } private void OnDestroy() { // 销毁所有Window ms_VisibleWindowStack.Clear(); foreach (Type key in ms_SearchCache.Keys) { gfBaseWindow window = ms_SearchCache[key]; gfBaseWindow.DestroyWindowAdapter destroyWindowAdapter = new gfBaseWindow.DestroyWindowAdapter(); destroyWindowAdapter.DestroyWindow(window); } ms_SearchCache.Clear(); } private void AddWindow(gfBaseWindow window) { Type windowType = window.GetType(); //Debug.Log("Add Window Type: " + windowType); if (!ms_SearchCache.ContainsKey(windowType)) { ms_SearchCache.Add(windowType, window); } else { gfDebug.Assert(false, string.Format("gfWindowManager中已存在类型为{0}的Window。每个Window类只允许New一次。", window.GetType())); } } public class AddWindowAdapter { public void AddWindow(gfWindowManager windowManager, gfBaseWindow window) { if (windowManager != null) { windowManager.AddWindow(window); } } } }
本框架的设计原则如下:
建议开发中的命名:
别外,想让自己开发的UI DrawCall少,执行效率高,请先看完NGUI和uGUI的源码,看完源码你就会理解,为啥运行时不修改NGUI Panel的Depth,等等。。。 别看网上那些什么NGUI Depth和z轴的关系测试,自动调整UI的层级关系之类的文章,他自己都没明白,在那瞎测试,看到结果对了就觉得可以了。人家代码都给你了,还瞎测试个毛啊。正确的方式是:看源码,理解,总结,验证,应用。
你金大爷人品太好,再给写个Demo:项目代号:ufd(UIFrameDemo),屏幕尺寸:1080x2080,包含NGUI、uGUI 2种实现。
百度网盘链接: https://pan.baidu.com/s/1guEHZwyPEv5R2wVM9pF9SQ 提取码: zmfc
原文:https://www.cnblogs.com/goldpa/p/10408853.html