一、首先介绍模型类Model
从上图中可以看出,Model发出的线只有一条虚线,所以Model层只是负责发送事件(消息)通知视图层改变UI的显示,而指向Model的另外两个线的是意思是视图层和控制层可以获取到Model数据,简明之意就是View和Controller可以访问到Model。Model层代码如下:
/// <summary>
/// 模型数据
/// </summary>
public abstract class Model { //模型标识
public abstract string Name { get; } //发送事件
protected void SendEvent(string eventName,object data=null)
{
MVC.SendEvent(eventName, data);
}}
二、View视图层
View视图层有四条线与之相关联,VIew层发出的线有两条,作用分别为:
1、可以访问获取查询Model类里的数据
2、可以发出用户请求事件(消息)去通知Controller层去执行
另外两条线,一条是虚线 指向View层,表示View层可以接受到Model层发送过来的通知改变的事件(消息),另外一条实 线表示Controller层可以获取访问到View,所以View类代码如下:
/// <summary>
/// 视图层
/// </summary>
public abstract class View : MonoBehaviour { // 视图标识
[HideInInspector]
public abstract string Name { get; } // 视图层关心的事件列表
[HideInInspector]
public List<string> attentionEvents = new List<string>(); // 注册视图关心的事件
public virtual void RegisterViewEvents()
{ }
//获取模型
protected Model GetModel<T>() where T:Model
{
return MVC.GetModel<T>();
} //发送消息
protected void SendEvent(string eventName,object data=null)
{
MVC.SendEvent(eventName, data);
} // 视图层事件处理
public abstract void HandleEvent(string eventName,object data);
}
注意:View层是需要继承Mono的,因为它是需要在UI上进行显示的,需要更新显示的UI直接继承View,实现里面的方法即可
三、Controller控制层
Controller层有三条线与之关联,第一条虚线表示可以接受到视图层View发出的事件,其他两条实线表示可以访问获取到Model层和View层,所以Controller层代码如下:
/// <summary>
/// 控制层
/// </summary>
public abstract class Controller
{
//注册模型
protected void RegisterModel(Model model)
{
MVC.RegisterModel(model);
} //注册视图
protected void RegisterView(View view)
{
MVC.RegisterView(view);
} //注册控制器
protected void RegisterController(string eventName,Type controllerType)
{
MVC.RegisterController(eventName, controllerType);
} //执行事件
public abstract void Execute(object data); //获取模型
protected T GetModel<T>() where T : Model
{
return MVC.GetModel<T>();
} //获取视图
protected T GetView<T>() where T : View
{
return MVC.GetView<T>();
}
}
最后,介绍最后一个关键的类,它的作用相当于一个中间类,也就是中介模式,三个类之间的交互通过这个中介类来完成,用来减少其他三个类之间的交互耦合性,代码如下:
public static class MVC {
private static Dictionary<string, Model> models = new Dictionary<string, Model>(); //名字---模型
private static Dictionary<string, View> views = new Dictionary<string, View>(); //名字---视图
private static Dictionary<string, Type> commandMap = new Dictionary<string, Type>(); //事件名字--控制器类型 //注册模型
public static void RegisterModel(Model model)
{
models[model.Name] = model;
} //注册视图
public static void RegisterView(View view)
{
if (views.ContainsKey(view.Name)) return;
views[view.Name] = view;
view.RegisterViewEvents(); //注册关心的事件
} //注册控制器
public static void RegisterController(string eventName,Type controllerType)
{
commandMap[eventName] = controllerType;
} //发送事件
public static void SendEvent(string eventName,object data=null)
{
//控制层
Type t = null;
commandMap.TryGetValue(eventName,out t);
if(t!=null)
{
object commandInsatnce = Activator.CreateInstance(t);
if (commandInsatnce is Controller)
((Controller)commandInsatnce).Execute(data);
} //视图层
foreach (View v in views.Values)
{
if (v.attentionEvents.Contains(eventName))
v.HandleEvent(eventName,data);
}
} //获取模型
public static T GetModel<T>() where T : Model
{
foreach (Model m in models.Values)
{
if (m is T)
return (T)m;
}
return null;
} //获取视图
public static T GetView<T>() where T:View
{
foreach (View v in views.Values)
{
if (v is T)
return (T)v;
}
return null;
}
}
现在,MVC最主要的几个类已经书写完毕了,那么,怎么来使用它呢,需要一个启动框架的类来负责启动这个框架程序,所以我们还需要编写几个类来方便我们可以顺利执行这个框架:
1、写一个简单的通用单例类,所有的单例类直接继承它就可以
public abstract class Singleton<T>:MonoBehaviour where T : MonoBehaviour
{
private static T m_Instance = null; public static T Instance
{
get { return m_Instance; }
} protected virtual void Awake()
{
m_Instance = this as T;
}
}
2、框架程序启动类,虽然现在不明白为什么写这个类,但是如果了解完整个流程,你就会明白这个类的作用了
public class ApplicationBase<T> : Singleton<T> where T:MonoBehaviour
{
//注册控制器
protected void RegisterController(string eventName, Type controllerType)
{
MVC.RegisterController(eventName, controllerType);
} //发送事件
protected void SendEvent(string eventName, object data = null)
{
MVC.SendEvent(eventName, data);
}
}
到现在为止,这个有关这个简单的框架的代码基本就都写完了,下面展示如何使用它:
1、首先创建一个类,名字随便了,我以Game类为例,这个类负责发送一个启动框架的消息事件,用来启动整个框架,也是程序的总入口
public class Game : ApplicationBase<Game> {
public void LoadScene(int level)
{
SceneArgs e = new SceneArgs()
{
sceneIndex = SceneManager.GetActiveScene().buildIndex //获取当前所在的场景编号
};
SendEvent(Consts.E_ExitScene, e); //发出退出当前场景事件
SceneManager.LoadScene(level, LoadSceneMode.Single);
} //这个方法比较关键,是Mono自带的方法,表示进入场景后会调用此方法,发出进入场景的事件,把场景编号参数传进去
private void OnLevelWasLoaded(int level)
{
SceneArgs e = new SceneArgs() //这个SceneArgs 后面会说到,是个发送场景号的参数类
{
sceneIndex = level
};
SendEvent(Consts.E_EnterScene, e);
} //启动入口
private void Start()
{
DontDestroyOnLoad(this.gameObject);
RegisterController(Consts.E_StartUp, typeof(StartUpCommand));
SendEvent(Consts.E_StartUp);
}}
其中里面有些常量,需要新建一个Consts类进行统一管理,这个常量类里存放了管理MVC有关的消息事件常量。里面有些常量没有用不用管它。
public static class Consts {
//Model
public const string M_GameModel = "M_GameModel"; //模型数据 //View
public const string V_UILevel = "V_UILevel"; //等级UI视图 //controller
public const string E_StartUp = "E_StartUp"; //启动框架事件
public const string E_EnterScene = "E_EnterScene"; //进入场景事件
public const string E_ExitScene = "E_ExitScene"; //离开场景事件
public const string E_LevelChange = "E_LevelChange"; //等级改变事件}
因为,Game中发出了一个E_StartUp 启动框架事件,所以会创建一个对应的Controller来执行它的事件消息,这个类主要就是负责注册项目中所有需要用到模型层和控制层,有的人说那么为什么不注册View层呢,因为视图层是在每个场景里单独显示的,所以我们就让它每个场景的View去自己单独注册处理。 代码负责举例
public class StartUpCommand : Controller {
public override void Execute(object data)
{
//注册模型
RegisterModel(new GameModel()); //注册控制器
RegisterController(Consts.E_EnterScene, typeof(EnterSceneCommand));
RegisterController(Consts.E_ExitScene, typeof(ExitSceneCommand));
RegisterController(Consts.E_AddLevel, typeof(AddLevelCommand)); Game.Instance.LoadScene(1);
}
}
所以,Game类中发出启动框架事件,上面这个StartUpCommand就会触发执行,进入到编号为1的场景,进入到场景后就会触发EnterSceneCommand这个类去执行里面的方法,主要用来对不同的场景编号注册不同的视图,代码如下:
//进入场景需要执行的Command:
public class EnterSceneCommand : Controller {
public override void Execute(object data)
{
SceneArgs e = data as SceneArgs;
switch (e.sceneIndex)
{
case 0: // Init场景
break;
case 1: // Main场景
RegisterView(Game.FindObjectOfType<UILevel>());
break;
}
}
}//退出场景需要执行的Command:
public class ExitSceneCommand : Controller {
public override void Execute(object data)
{
//这里是退出场景前需要做的操作,常用对象池进行场景中资源的回收
}}
//这个类里存放着就是需要发送的场景编号
public class SceneArgs {
public int sceneIndex;
}
然后View层就会处理对应的逻辑:
public class UILevel : View {
public Text numberText;
public Button addLevelButton;
public Text prenumberText; private void Start()
{
numberText.text = 0.ToString();
prenumberText.text = 0.ToString();
addLevelButton.onClick.AddListener(OnClickAddLevel);
} public void OnClickAddLevel()
{
SendEvent(Consts.E_AddLevel);
} public override string Name
{
get
{
return Consts.V_UILevel;
}
} public override void RegisterViewEvents()
{
attentionEvents.Add(Consts.E_LevelChange);
} public override void HandleEvent(string eventName, object data)
{
switch (eventName)
{
case Consts.E_LevelChange:
{
LevelArgs e = data as LevelArgs;
numberText.text = e.level.ToString();
prenumberText.text = e.level + "%";
}
break;
}
}}
这就是这个框架的一整套运行流程和讲解!!谢谢
总结:Game类中发出E_StartUp启动框架事件,这时StartUpCommand就会执行里面的Execute方法,就会注册所有的模型和命令,接着调用了Game类中的LoadScene方法加载场景,这个代码首先会发出退出当前场景的事件,触发ExitSceneCommand中的Execute方法,负责对退出当前场景进行一些操作,一般是用于回收资源,紧接着就进入到新的场景中,会发出进入新场景的事件,在发送中,会传入一个场景编号的参数(就是进入到哪个场景的场景编号)触发EnterSceneCommand的Execute方法,在此方法中,由于可以接受到传递进来的场景编号,所以在Execute方法里就可以根据不同场景注册对应的视图,然后每个视图再分别处理自己的逻辑。整个框架其内部都是通过事件驱动,发送事件方只需要发送事件即可,不用管是谁接收,而接收方,对哪个事件感兴趣就注册哪个,监听到事件执行对应的方法即可,不需要知道发送方是谁,这样就很好的处理了解耦性 ,这就是这个框架的好处!
为了测试这个框架的运行编写了一个小功能:链接:https://pan.baidu.com/s/1Xbc76Bx83-z5AaZeZ9YOgg 密码:5csk
对于这样一个小功能,不用框架写几行代码就可以实现,但是套用这个框架代码量就会比较多,但是它实现了解耦性,方便后期的扩展和维护!