UI框架面对的问题是每个面板用一个单例还是用一个管理器管理面板的打开关闭方式加载还是激活销毁还是隐藏加载的面板要挂载到画布上画布是否过场景不销毁如果不销毁画布是代码加载还是直接在场景里如果是直接在要避免回到初始场景有2个画布面板的打开有的覆盖在原面板上不隐藏其他面板有的隐藏其他面板。打开、隐藏面板的代码应放在哪里如果都放在触发面板里如果此面板要在不同环境下打开如设置面板触发它的面板不确定如果要隐藏上一个面板只能上一个面板隐藏自己。有些面板的刷新显示需要参数参数从外部传入无法通过Start()初始化可以面板基类定义RefreshT()但是有的面板显示又不需要参数又要传一个占位参数加载面板通过资源名还是ScriptableObject引用如果是Resources下的资源名资源名写在哪里硬编码在代码里好吗?如果资源要在Resources下按文件夹放每个面板所属的文件夹怎么记录面板的打开关闭方式加载资源、销毁对象式。省内存费性能垃圾多对象激活、失活式。省性能费内存而且编辑器里要放所有面板很乱第一次打开是加载资源关闭是失活对象以后打开是激活对象。需要在打开面板方法里根据对象是否存在分开处理。多个场景会出现的面板切场景时需要回到未加载状态提供加载面板的方法包括从Resources和AB包加载包括全屏面板和部分面板。为此需要面板预制体的路径和资源名需要一个方法知道当前哪些面板在显示可以是查看一个面板类的单例是否存在也可以用一个字典存储显示的面板。一个面板用一个单例而不用UI管理器会有什么问题假设我想让面板一开始不存在场景里第一次是加载关闭时失活以后打开都只激活。加载面板需要先判断这个面板加载过没有然后决定加载还是激活。要么用面板类的静态方法不能用实例方法因为单例还不存在要么用一个管理器。如果用静态方法加载这个方法不能重写为了避免每个面板写一遍只能写在面板基类里。public class MyUIManager : MySingletonMyUIManager { Dictionarystring,PanelBasepanelDic new Dictionarystring,PanelBase(); Canvas _canvas; public Canvas canvas { get { if (_canvas) { return _canvas; } _canvas Object.FindFirstObjectByTypeCanvas(); return _canvas; } } public void ShowT(string folderUI/)where T:PanelBase//通过资源名加载资源 { string panelNametypeof(T).Name; if (!panelDic.ContainsKey(panelName)) { GameObject panelObj MyResManager.Instance.LoadPanel(folderpanelName); PanelBase panel panelObj.GetComponentPanelBase(); panelDic.Add(panelName, panel); } else { panelDic[panelName].gameObject.SetActive(true); } } public void HideT(bool destroy false)where T:PanelBase { string panelName typeof(T).Name; if (!panelDic.ContainsKey(panelName)) return; if (destroy) { Object.Destroy(panelDic[panelName].gameObject); panelDic.Remove(panelName); } else { panelDic[panelName].gameObject.SetActive(false); } } public void RemovePanel(string panelName) { if (panelDic.ContainsKey(panelName)) { Object.Destroy(panelDic[panelName].gameObject); panelDic.Remove(panelName); } } public T GetPanelT()where T : PanelBase { string panelNametypeof (T).Name; if (panelDic.ContainsKey(panelName)) { return (T)panelDic[panelName]; } return null; } }UI管理器需要实现的功能通过上面的讨论需要满足显示面板根据是否有缓存加载或激活面板关闭面板时隐藏缓存返回已加载的任意一个面板支持面板的动态加载和本来就存在。对于本来在场景里的面板把它注册进字典支持面板显示、关闭的动画我们希望能给每个面板配置各异的显示、关闭动画那么这个动画应该是面板的属性。并且我们可能使用animator或DoTween实现动画。播放动画说到底是执行一个函数对于不确定的函数就是一个委托。但是委托不能在检查器配置。可以用UnityEvent但是它能存的函数太广泛我们不想让它能指定动画之外的函数。所以还是写一个ui动画基类。面板有显示和关闭动画显示动画是从某个状态到摆好状态关闭动画是从摆好状态到某个状态。我们可以只设定一个状态再设定是显示还是关闭。显示动画可以放在OnEnable()里而关闭动画不能放OnDisable()里否则面板会立刻隐藏看不见动画。于是想把隐藏或销毁的函数作为参数传给关闭动画。于是变成了如果面板有关闭动画则把隐藏函数作为参数传入没有则直接执行隐藏。然后面板播放完关闭动画可能在一个设定的位置再显示时不能让它从设定位置移动到当前位置要再设定好屏幕中的位置。同理对于旋转缩放关闭动画结束后要复原本来应有的状态。还有播放动画的过程中玩家按了按钮会发生不期望的行为比如面板正在播放关闭动画又按打开该面板的按钮执行显示面板面板此时也没有隐藏执行显示相对于没执行。需要在播放动画过程中暂时让所有输入控件失效。简单的方法是把GraphicRaycaster关闭播放完再打开。在游戏暂停时UI动画不能停下在DoTween使用.SetUpdate(UpdateType.Normal, true)使UI动画不受时间暂停影响。或者全局设置public MyUIManager() { DOTween.defaultTimeScaleIndependent true; }总结下来UI动画要解决的问题关闭动画时关闭面板在动画结束后执行需要作为回调传入关闭动画结束后要把面板的状态复原动画播放中要关闭所有输入控件动态生成条目刷新显示时之前的高亮是否要保持的问题。如果要保持高亮如果直接用高亮条目的引用记录高亮刷新的时候已经被销毁了。如果用int index记录高亮刷新时就把高亮的index也读取生成完条目设置高亮。HUD面板武器信息部分包括武器名自动方式枪内子弹数备用弹匣数。手榴弹只有武器名备用数让玩家类调用HUD还是HUD监听玩家类监听更解耦代码稍多。如果像下面写的为换武器、射击、换弹、拿起放下子弹都写监听函数然后添加、移除监听的代码会一大堆。全量更新武器信息需要4个参数需要用valueTuple或定义结构体或类。更换使用的武器包括收起、拿出、放下、捡起时需要更新所有4个信息切换自动方式只更新2射击只更新3换弹更新3、4拾取、放下使用的武器的备用子弹更新4。HUD是只用一个函数每次全量读取、更新这些信息还是根据以上几种情景写只更新部分信息的函数面板显示时的参数传入面板显示需要的数据、回调如果不用传入而是从其他类获取那么面板就对controller等其他类产生大量引用面板类和它做的业务绑死如果想一个面板在多个场景用处理不同业务就会出问题。如果一个面板绑死一个场景就感觉不到问题。基本上每个面板显示时都要传入数据、回调每个面板要传入的数据类型不同。不可能所有面板重写一个方法要么用泛型方法那么面板基类直接是一个泛型类面板定义时传入自己用的参数类把面板和参数类绑定在一起。这样意味着所有面板都要改成泛型基类参数类对于没有下定决心采用这种框架只想小范围试试不友好。或者先不用泛型基类不把面板和参数类绑定而是写传入参数类的Refresh方法这样可以小范围试试这种框架。然后静态按钮的回调只有第一次显示时要绑定之前如果不用传入参数则可以在Start里绑定用了传入参数Start没有参数还要防止按钮重复绑定要么每次显示把按钮回调全移除重新绑定要么弄一个bool inited标记。总之想用有参函数传入Awake、Start的生命周期函数特性不能用了。业务代码放哪面板不包含任何业务代码然后要面对业务代码放哪里放控制器控制器如何得到单例然后控制器一直存在HUD面板是否要参数输入HUD和其他面板有几个不同长期存在、很少隐藏很少全量刷新一般都是只刷新某一部分几乎没有一个HUD多个场景复用的情况