Controller负责修改Model,Model负责通知View层刷新,Controller层和View层是不交互的。
View:数据的显示、动画效果等
Controller:数据的计算、数据的修改、点击逻辑
我们使用UIManager
作为中转站,一个点击事件的流转过程如下:
点击事件——Controller执行逻辑——UIManager(通知View进行刷新)——View
设置初始化优先级
当一个Prefab可以绑定多个脚本时,需要指定每一个脚本的初始化顺序,这里View脚本的初始化要在Controller脚本之前。
修改BindPrefab特性
在里面添加Priority属性,数字越小,代表优先级越高,默认是100.
1 2 3 4 5 6 7 8 9 10 11 12 13
| using System;
[AttributeUsage(AttributeTargets.Class)] public sealed class BindPrefabAttribute : Attribute { public string Path { get;private set; } public int Priority { get;private set; } public BindPrefabAttribute(string path, int priority = 100) { Path = path; Priority = priority; } }
|
添加Consts类
游戏中常用的常量类,这里先把View脚本和Controller脚本的优先级设定好
1 2 3 4 5
| public class Consts { public const int BIND_PREFAB_PRIORITY_VIEW = 0; public const int BIND_PREFAB_PRIORITY_CONTROLLER = 1; }
|
之后修改各个Controller和View脚本的BindPrefab
特性,把优先级指定好。
修改BindUtil
我们需要先修改BindUtil
,让一个Prefab可以动态绑定多个脚本,并且缓存好脚本的优先级。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| using System.Collections.Generic; using UnityEngine; using System;
public class BindUtil { private static Dictionary<string, List<Type>> m_PrefabAndScriptMap = new Dictionary<string, List<Type>>(); private static Dictionary<Type,int> m_PriorityMap = new Dictionary<Type,int>(); public static void Bind(BindPrefabAttribute data, Type type) { string path = data.Path; if (!m_PrefabAndScriptMap.ContainsKey(path)) { m_PrefabAndScriptMap.Add(path, new List<Type>()); } if (!m_PrefabAndScriptMap[path].Contains(type)) { m_PrefabAndScriptMap[path].Add(type); m_PriorityMap.Add(type, data.Priority); } m_PrefabAndScriptMap[path].Sort(new BindPropertyComparer()); } public static List<Type> GetPrefabType(string path) { List<Type> type; if (m_PrefabAndScriptMap.TryGetValue(path, out type)) { return type; } else { Debug.LogError("当前Prefab并没有被指定到具体的Script" + path); return null; } } public class BindPropertyComparer : IComparer<Type> { public int Compare(Type x, Type y) { if (x == null) return 1; if (y == null) return -1; return m_PriorityMap[x] - m_PriorityMap[y]; } } }
|
将Type的缓存方式改为List
添加m_PriorityMap
,用来缓存每一个Type的优先级
添加BindPropertyComparer
内部类,用来对每个prefab对应的View和Controller进行排序,确保View优先级更高
修改InitCustomAttributes
修改其中的调用参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| using System.Reflection; using System;
public class InitCustomAttributes { public void Init() { Assembly assembly = Assembly.GetAssembly(typeof(BindPrefabAttribute)); Type[] types = assembly.GetExportedTypes(); foreach (Type t in types) { foreach (Attribute attr in Attribute.GetCustomAttributes(t,true)) { if (attr is BindPrefabAttribute) { BindPrefabAttribute data = (BindPrefabAttribute)attr; BindUtil.Bind(data, t); } } } } }
|
分离基础接口
我们之前分离了IView
接口,由于Controller和View联系紧密,可能也需要和View一样实现Init、Show、Hide等基础方法,我们这里使用一种主接口的方式来联系这两部分的接口。
在Scripts文件夹内新建Interface文件夹,并在其中新建IInit
、IHide
、IShow
、IUpdate
等接口,并在接口中声明好对应的基础方法。注意其中的IUpdate
的基础方法不能声明为void Update()
,这和Unity的方法重复了。我们这里命名为UpdateFun
。
注意这里的UpdateFun
并不是指每帧都更新,而是指这个Controller内或者View内有刷新的逻辑,当我们点击某些按钮时,这些刷新的逻辑会执行。
然后先让View——Interface文件夹内的IViewHide
继承IHide
、IViewInit
继承IInit
、IViewShow
继承IShow
、IViewUpdate
继承IUpdate
,并且删掉里面声明的方法。
最后再在Controller文件夹内部新建Interface文件夹,新建IController
、IControllerInit
、IControllerShow
、IControllerHide
、IControllerUpdate
,接口同样像View一样继承。
IController
1 2 3 4
| public interface IController : IControllerInit, IControllerShow, IControllerHide, IControllerUpdate { void AddUpdateListener(System.Action update); }
|
UIMgr
重构UIMgr,UIMgr现在有了更多的职责,需要管理每个UI的Controller和View
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
|
public IView Show(string path) { if(uiStack.Count > 0) { string pre = uiStack.Peek(); HideAll(viewsDic[pre]); } IView view = InitView(path); if (view != null) { ShowAll(view); uiStack.Push(path); viewsDic[path] = view; } return view; } private IView InitView(string path) { if (viewsDic.ContainsKey(path)) { return viewsDic[path]; } else { GameObject go = LoadMgr.Instance.LoadPrefab(path,Canvas.transform); if (go != null) { AddTypeComponent(go, path); AddUpdateListener(go); InitComponent(go); IView view = go.GetComponent<IView>(); if (view != null) { return view; } else Debug.LogError("此Prefab没有对应的View脚本:" + path); return null; } else Debug.LogError("没有找到对应的Prefab:" + path); return null; } } private void AddTypeComponent(GameObject viewGo, string path) { foreach (var type in BindUtil.GetPrefabType(path)) { viewGo.AddComponent(type); } } private void AddUpdateListener(GameObject viewGo) { var controller = viewGo.GetComponent<IController>(); if (controller == null) { Debug.LogWarning("当前UIPrefab没有Controller组件:" + viewGo.name); return; }
foreach (IUpdate update in viewGo.GetComponents<IUpdate>()) { controller.AddUpdateListener(update.UpdateFun); } } private void InitComponent(GameObject viewGo) { IInit[] inits = viewGo.GetComponents<IInit>(); foreach (var init in inits) { init.Init(); } }
public void Back() { if(uiStack.Count <=1) { return; } string name = uiStack.Pop(); HideAll(viewsDic[name]);
name = uiStack.Peek(); ShowAll(viewsDic[name]); } private void ShowAll(IView view) { foreach (IShow show in view.GetTrans().GetComponents<IShow>()) { show.Show(); } } private void HideAll(IView view) { foreach (IHide hide in view.GetTrans().GetComponents<IHide>()) { hide.Hide(); } }
|
添加ShowAll
和HideAll
方法,修改InitView
、Show
和Back
方法
添加AddTypeComponent
、AddUpdateListener
和InitComponent
方法,其中AddUpdateListener
就是让一个UI的Controller的添加Button监听方法起作用。
修改IView和ViewBase
给IView
添加返回Transform
的方法,因为目前我们引入了Controller,单纯返回IView
会导致Controller的逻辑执行不全,所以返回Transform,从Transform能获取到所有的组件,当然包括Controller。
1 2 3 4
| public interface IView : IViewUpdate, IViewInit, IViewShow, IViewHide { UnityEngine.Transform GetTrans(); }
|
1 2 3 4 5 6 7 8
| public abstract class ViewBase : MonoBehaviour, IView { public Transform GetTrans() { return transform; } }
|
ControllerBase
像ViewBase
一样,我们所有的Controller的生命周期管理都在ControllerBase
里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UI;
public abstract class ControllerBase : MonoBehaviour, IController { private List<IControllerUpdate> _updates; private List<IControllerInit> _inits; private List<IControllerShow> _shows; private List<IControllerHide> _hides; private System.Action _onUpdate; public virtual void Init() { InitChild(); InitAllComponents(); InitComponents(); AddUpdateAction(); } protected abstract void InitChild(); private void AddUpdateAction() { foreach (Button button in GetComponentsInChildren<Button>()) { button.onClick.AddListener(() => { _onUpdate?.Invoke(); }); } } private void InitAllComponents() { InitComponent(out _inits, this); InitComponent(out _shows, this); InitComponent(out _hides, this); InitComponent(out _updates, this); } private void InitComponent<T>(out List<T> components, T removeObj) { components = transform.GetComponentsInChildren<T>().ToList(); components.Remove(removeObj); } private void InitComponents() { foreach (var component in _inits) { component.Init(); } } public virtual void Show() { foreach (var component in _shows) { component.Show(); } } public virtual void Hide() { foreach (var component in _hides) { component.Hide(); } }
public virtual void UpdateFun() { foreach (var component in _updates) { component.UpdateFun(); } } public void AddUpdateListener(System.Action update) { _onUpdate += update; } }
|
注意这里InitComponent
使用的是GetComponentsInChildren
而ViewBase
使用的是foreach Transform
ViewBase
修改其中一些逻辑,转移到Controller中执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private void InitViewInterface<T>(List<T> views) { foreach (Transform trans in transform) { if (trans.TryGetComponent(out T view)) { views.Add(view); } } }
public virtual void UpdateFun() { foreach (IViewUpdate viewUpdate in _viewUpdates) { viewUpdate.UpdateFun(); } }
|
删除AddUpdateAction
方法。修改InitViewInterface
方法。
将之前UpdateAction
中的逻辑转移到UpdateFun
中执行,删除掉UpdateAction
方法
StartController
理论上讲,我们每一个Prefab上面都会动态挂载一个View和一个Controller,所以每一个Controller都需要指定BindPrefab
特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| using UnityEngine;
[BindPrefab(Path.START_VIEW,Consts.BIND_PREFAB_PRIORITY_CONTROLLER)] public class StartController : ControllerBase { public override void Init() { base.Init(); Debug.Log("Controller Start"); }
protected override void InitChild() { } }
|