unity 里的mvc unity mvvm框架_图层

一、首先介绍模型类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

对于这样一个小功能,不用框架写几行代码就可以实现,但是套用这个框架代码量就会比较多,但是它实现了解耦性,方便后期的扩展和维护!