Unity MVC框架代码详解(适合中小项目开发,解耦合,事件发送消息监听机制)

5 篇文章 0 订阅

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值