Unity MVC框架的搭建和使用

什么是MVC

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

如图:
在这里插入图片描述
Model是数据模型,用来存储数据信息的。
View是视图界面,也就是平时我们看到的UI界面,用于显示界面信息的。
Controller是控制器,用于处理逻辑代码的。

当需要在界面上更新金币的显示时,用户发送请求,即SendEvent(),通过参数传递数据,通过名字进行标识是哪个view发过来的,事先要将view的名字与相应的controller进行绑定,然后对应的controller接收到发送过来的数据,进行相应的逻辑处理,即进行金币更新,通知view进行进行金币更新后的显示。

Model、View、Controller都定义成抽象类,因为它们都有一个抽象的属性或方法,定义成抽象类的话,子类继承自父类,子类必须实现父类的抽象属性或者方法,因为View和Model都需要一个名字来标识,区别出来是哪个View,然后注册这个View,将该View的名字和该View进行绑定。

Model代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class Model
{
    //名字标识
    public abstract string Name { get; }

    /// <summary>
    /// 发送事件
    /// </summary>
    protected void SendEvent(string eventName,object data=null)
    {
        MVC.SendEvent(eventName, data);
    }
}

View代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class View : MonoBehaviour
{
    //名字标识
    public abstract string Name { get; }

    /// <summary>
    /// 事件关心列表
    /// </summary>
    [HideInInspector]
    public List<string> attentionList = new List<string>();

    public virtual void RegisterAttentionEvent()
    {

    }
    /// <summary>
    /// 处理事件
    /// </summary>
    public abstract void HandleEvent(string name, object data);

    /// <summary>
    /// 发送事件
    /// </summary>
    protected void SendEvent(string eventName, object data = null)
    {
        MVC.SendEvent(eventName, data);
    }

    protected T GetModel<T>()
        where T:Model
    {
        return MVC.GetModel<T>() as T;
    }
}

Controller代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public abstract class Controller
{
    public abstract void Execute(object data);

    protected T GetModel<T>()
        where T : Model
    {
        return MVC.GetModel<T>() as T;
    }

    protected T GetView<T>()
        where T : View
    {
        return MVC.GetView<T>() as T;
    }

    /// <summary>
    /// 注册模型
    /// </summary>
    protected void RegisterModel(Model model)
    {
        MVC.RegisterModel(model);
    }

    /// <summary>
    /// 注册视图
    /// </summary>
    protected void RegisterView(View view)
    {
        MVC.RegisterView(view);
    }

    /// <summary>
    /// 注册controller
    /// </summary>
    protected void RegisterController(string eventName,Type controllerType)
    {
        MVC.RegisterController(eventName, controllerType);
    }
}

在View中,有个string类型的attentionList,它是用来存储需要View执行的事件的名字,View中的public virtual void RegisterAttentionEvent()是虚方法,可重写,重写的时候可以在里面添加关心事件,即比如attentionList.Add(Consts.E_UpdateDis);然后public abstract void HandleEvent(string name, object data)是抽象方法,必须重写,当需要发送请求时,即SendEvent(eventName,data),首先是执行Controller里面的逻辑代码,然后就是执行View中重写的HandleEvent()方法,在HandleEvent方法中,就可以执行你想要的操作了。

在Controller中,不需要定义名字进行标识,到时候会在一个Consts类中定义一堆的Controller的名字进行标识,然后通过那个名字,和它对应的Controller的类型进行绑定,通过反射即可拿到该Controller的实例。
public abstract void Execute(object data)是必须重写的,因为每个Controller中进行的逻辑处理都是不一样的。每次SendEvent后,其中的参数名字和它对应的Controller对应上了,就会执行该Controller。由于继承了Controller的子类中可能会GetModel、GetView、RegisterModel、RegisterView、RegisterController,所以提前写好对应的方法,简化操作。

然后,需要一个脚本统一的管理Model、View和Controller,所以新建一个名为MVC的类统一管理它们。
代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class MVC
{
    //资源
    public static Dictionary<string, Model> Models = new Dictionary<string, Model>();
    public static Dictionary<string, View> Views = new Dictionary<string, View>();
    public static Dictionary<string, Type> CommanMap = new Dictionary<string, Type>();

    /// <summary>
    /// 注册view
    /// </summary>
    /// <param name="view"></param>
    public static void RegisterView(View view)
    {
        //防止view重复注册
        if(Views.ContainsKey(view.Name))
        {
            Views.Remove(view.Name);
        }
        view.RegisterAttentionEvent();
        Views[view.Name] = view;
    }

    /// <summary>
    /// 注册model
    /// </summary>
    /// <param name="model"></param>
    public static void RegisterModel(Model model)
    {
        Models[model.Name] = model;
    }

    /// <summary>
    /// 注册controller
    /// </summary>
    /// <param name="eventName"></param>
    /// <param name="controllerType"></param>
    public static void RegisterController(string eventName,Type controllerType)
    {
        CommanMap[eventName] = controllerType;
    }

    /// <summary>
    /// 获取model
    /// </summary>
    /// <returns></returns>
    public static T GetModel<T>()
        where T:Model
    {
        foreach (var m in Models.Values)
        {
            if(m is T)
            {
                return (T)m;
            }
        }
        return null;
    }

    /// <summary>
    /// 获取view
    /// </summary>
    /// <returns></returns>
    public static T GetView<T>()
        where T : View
    {
        foreach (var v in Views.Values)
        {
            if (v is T)
            {
                return (T)v;
            }
        }
        return null;
    }

    public static void SendEvent(string eventName,object data=null)
    {
        //controller执行
        if(CommanMap.ContainsKey(eventName))
        {
            Type t = CommanMap[eventName];
            Controller c = Activator.CreateInstance(t) as Controller;
            c.Execute(data);
        }

        //view处理
        foreach (var v in Views.Values)
        {
            if(v.attentionList.Contains(eventName))
            {
                //执行
                v.HandleEvent(eventName, data);
            }
        }

    }
}

Model和View都是通过它们自身的名字和自身绑定在一起,通过字典存储起来,对于Controller的话,由于Controller自身没有名字,所以在Consts类中自己定义名字给它,然后将这个名字和这个Controller的类型绑定在一起。在注册View的时候,public static void RegisterView(View view),是进入了哪个场景时就注册哪个场景的View,由于可能重复进入一个场景,而原本却已经存在了,所以在注册View之前,进行判断,如果字典里已经存在这个View了,那就先移除该View,再进行重新注册,而注册之前,也先进行关心事件的注册,即view.RegisterAttentionEvent();
Model和Controller都是直接注册了,不需要进行判断,因为View和Controller都是一开始就只注册一次而已,以后不会有重复注册的可能。
对于发送请求的代码public static void SendEvent(string eventName,object data=null),需要传入事件的名字,object data=null表示传入的数据是object类型的,=null表示可不传,即该参数可为空。
Type t = CommanMap[eventName];
Controller c = Activator.CreateInstance(t) as Controller;
通过Activator反射类可获取到该Controller,然后执行该Controller中的c.Execute(data)方法,接着就是执行View中的HandleEvent(eventName, data)方法。

以上就搭建好了整个MVC框架,接下来就是如何使用该套MVC框架了。

首先在一个无摄像机、无灯光的0号场景中新建一个空物体,可命名为Game,然后挂载一个同名的脚本。
代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.SceneManagement;

[RequireComponent(typeof(ObjectPool))]		//这三个是在该物体上添加这三个脚本
[RequireComponent(typeof(Sound))]			//如需了解我这两个ObjectPool和Sound是什么代码的,可以移步去https://blog.csdn.net/weixin_43839583/article/details/103408332了解Sound
[RequireComponent(typeof(StaticData))]	//移步去https://blog.csdn.net/weixin_43839583/article/details/103405831了解ObjectPool
public class Game : MonoSingleton<Game>
{
    //全局访问
    [HideInInspector]
    public ObjectPool objectPool;
    [HideInInspector]
    public Sound sound;
    [HideInInspector]
    public StaticData staticData;		//先定义这个类出来,现在是空类,但后面可能会存些静态数据

    private void Start()
    {
        DontDestroyOnLoad(gameObject);//表示这个游戏物体在跳转场景时不会被销毁。
        objectPool = ObjectPool.Instance;
        sound = Sound.Instance;
        staticData = StaticData.Instance;

        

        //初始化注册StartUpController,在该Controller中又注册其他所有的Controller
        RegisterController(Consts.E_StartUp, typeof(StartUpController));

        //游戏启动,执行StartUpController中的方法,即注册其他所有的Controller方法。
        SendEvent(Consts.E_StartUp);

        //跳转场景
        Game.Instance.LoadLevel(1);
    }

    public void LoadLevel(int level)
    {
        //发送退出场景事件
        ScenesArgs e = new ScenesArgs();
        //获取当前场景索引
        e.scenesIndex = SceneManager.GetActiveScene().buildIndex;
        SendEvent(Consts.E_ExitScenes, e);
        //发送加载新场景事件
        //SceneManager.LoadScene(level, LoadSceneMode.Single);
        StartCoroutine(StartLoadNewScenes(level));
    }

    /// <summary>
    /// 进入新场景
    /// </summary>
    IEnumerator StartLoadNewScenes(int level)
    {
        Debug.Log("进入新场景:" + level);

        AsyncOperation op = SceneManager.LoadSceneAsync(level);
        while(!op.isDone)
        {
            yield return 0;
        }
        //发送进入场景事件
        ScenesArgs e = new ScenesArgs();
        //获取当前场景索引
        e.scenesIndex = level;
        SendEvent(Consts.E_EnterScenes, e);
    }

    /// <summary>
    /// 发送事件
    /// </summary>
    protected void SendEvent(string eventName, object data = null)
    {
        MVC.SendEvent(eventName, data); 
    }

    /// <summary>
    /// 注册controller
    /// </summary>
    protected void RegisterController(string eventName, Type controllerType)
    {
        MVC.RegisterController(eventName, controllerType);
    }
}

为什么不直接使用加载场景的方法呢?因为我们需要在加载场景之前,把当前场景(退出的场景)的下标发给对应的退出场景的Controller进行执行相应的操作,就是在这个Controller中,判断如果这个场景是游戏中的场景,则要执行清空对象池的操作,就是将所有用到对象池技术的物体都隐藏,不这样做的话,会出问题。
然后通过异步加载场景,这样可以获取到加载场景的进度,因为我们要在完全加载完场景的时候发送进入场景的请求,这个时候,EnterScenesController会接收到进入了哪个场景的消息,然后执行注册该场景中相应的View。

到此为止,MVC的使用也已经结束了,在UI中,我们要改变UI界面,就要先发送事件SendEvent(),然后Controller中进行相应的逻辑处理,或者是添加关心事件attentionList,执行View中关心事件的处理。

平时要多多使用MVC框架才比较好的理解这套框架该怎么使用,MVC框架可以有效的降低耦合度,是高内聚、低耦合的一种优化。

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值