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];//返回负数表示x比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();//获取public的class
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文件夹,并在其中新建IInitIHideIShowIUpdate等接口,并在接口中声明好对应的基础方法。注意其中的IUpdate的基础方法不能声明为void Update(),这和Unity的方法重复了。我们这里命名为UpdateFun

注意这里的UpdateFun并不是指每帧都更新,而是指这个Controller内或者View内有刷新的逻辑,当我们点击某些按钮时,这些刷新的逻辑会执行。

然后先让View——Interface文件夹内的IViewHide继承IHideIViewInit继承IInitIViewShow继承IShowIViewUpdate继承IUpdate,并且删掉里面声明的方法。

最后再在Controller文件夹内部新建Interface文件夹,新建IControllerIControllerInitIControllerShowIControllerHideIControllerUpdate,接口同样像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
/// <summary>
/// 显示界面
/// </summary>
/// <param name="path">界面路径</param>
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>())//View和Controller都实现了IUpdate
{
controller.AddUpdateListener(update.UpdateFun);
}
}
private void InitComponent(GameObject viewGo)
{
IInit[] inits = viewGo.GetComponents<IInit>();
foreach (var init in inits)
{
init.Init();
}
}
/// <summary>
/// 返回上一个界面
/// </summary>
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();
}
}

添加ShowAllHideAll方法,修改InitViewShowBack方法

添加AddTypeComponentAddUpdateListenerInitComponent方法,其中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>())//TODO:可能有隐患
{
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使用的是GetComponentsInChildrenViewBase使用的是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)//根据VS的指示优化一下这个方法
{
foreach (Transform trans in transform)
{
if (trans.TryGetComponent(out T view))
{
views.Add(view);
}
}
}
//+++
public virtual void UpdateFun()//这个方法由UIMgr传递给对应的Controller调用,但是只有根节点的View才能传递这个方法
{
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()
{
}
}