目录
一、MVC介绍
二、搭建UI界面
三、代码实现
1.Model层
2.View层
3.Controller层
四、MVC框架测试
五、知识补充
一、MVC介绍
- model:数据层。界面展示的数据(需要进行初始化、更新、保存、事件通知等操作),单例模式,不必继承MonoBehaviour。
- view:界面层。寻找UI,提供更新UI控件的方法,供controller调用。要挂在预制体上。
(1)MVC是对M和V层进行了解耦,但没有完全解耦,从而诞生了MVP:对M和V层完全解耦。
(2)MVC里view还是具备直接得到model的权限的,MVP框架是完全把视图跟模型分离了。 - controller:处理业务逻辑。控制View的显隐及更新,事件监听及取消。要挂在预制体上。
(1)注意:物体隐藏时,OnDestroy不会被调用。
(2)一个view对应一个controller。
二、搭建UI界面
搭建如下的UI界面,并制作成预制体,并放于Resources\Prefabs目录。
三、代码实现
创建三个脚本:Model.cs、View.cs、Controller.cs。并将View.cs、Controller.cs挂于预制体上。
1.Model层
using System;
using UnityEngine;public class Model
{// 单例private static Model instance;public static Model Instance{get{if (instance == null){instance = new Model();instance.Init();}return instance;}}// 数据private int coinCount;public int CoinCount {get { return coinCount; }}// 数据操作:初始化、更新、保存private void Init(){coinCount = PlayerPrefs.GetInt("CoinCount", 0);}public void UpdateCoinCount(){++coinCount;Save();}private void Save(){PlayerPrefs.SetInt("CoinCount", coinCount);CallEvent();}// 事件:用于通知Controller进行更新Viewprivate event Action<Model> ModelEvent;public void AddEventListener(Action<Model> function){ModelEvent += function;}public void RemoveEventListener(Action<Model> function){ModelEvent -= function;}private void CallEvent(){ModelEvent?.Invoke(this);}
}
2.View层
using UnityEngine;
using UnityEngine.UI;public class View : MonoBehaviour
{//提供UI控件public Text coinText;public Button addCoinButton;//提供更新UI控件的方法public void UpdateCoin(Model model){coinText.text = model.CoinCount.ToString();}
}
3.Controller层
一个UI预制体对应一个View、一个Controller。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Controller : MonoBehaviour
{//controller控制viewprivate View view;private static Controller controller;public static Controller Instance{get{return controller;}}//1.面板显示隐藏public static void Show(){if (controller == null){var temp = Resources.Load<GameObject>("Prefabs/UIPanel");var go = Instantiate(temp);go.transform.parent = GameObject.Find("Canvas").transform;go.transform.localPosition = Vector3.zero;go.transform.localScale = Vector3.one;controller = go.GetComponent<Controller>();}controller.gameObject.SetActive(true);}public static void Hide(){if (controller != null){controller.gameObject.SetActive(false);}}void Start(){view = GetComponent<View>();//2.首次更新面板数据view.UpdateCoin(Model.Instance);//3.事件监听,更新数据view.addCoinButton.onClick.AddListener(() =>{Model.Instance.UpdateCoinCount();});Model.Instance.AddEventListener(UpdateCoin);}private void UpdateCoin(Model model){//2.更新面板数据view.UpdateCoin(model);}private void OnDestroy(){Model.Instance.RemoveEventListener(UpdateCoin);}
}
四、MVC框架测试
using UnityEngine;public class TestMVC : MonoBehaviour
{void Update(){if (Input.GetMouseButtonDown(0)){Controller.Show();}if (Input.GetMouseButtonDown(1)){Controller.Hide();}}
}
五、知识补充
private event Action<Model> ModelEvent;public void AddEventListener(Action<Model> function){ModelEvent += function;}public void RemoveEventListener(Action<Model> function){ModelEvent -= function;}private void CallEvent(){ModelEvent?.Invoke(this);}
在C#中,event关键字用于声明一个事件,它本质上是一种特殊的多播委托。`ModelEvent`在这里是一个事件,当你对其使用`+=`操作符时,你实际上是将一个回调方法添加到委托的调用列表中。如果你注册了多个回调,那么这些回调方法就会被添加到`ModelEvent`的调用链中。
当调用`ModelEvent?.Invoke(this);`时,以下是在底层发生的事情:
- 空值检查:`?.`是C# 6.0中引入的空条件运算符。它在尝试调用方法或访问成员之前会检查左侧的对象是否为`null`。如果`ModelEvent`不为`null`,那么调用继续;如果为`null`,那么调用就不会执行,整个表达式的结果也是`null`。
- Invoke方法:`Invoke`是`MulticastDelegate`类的一个方法,它负责同步地调用委托链中的每个回调方法。`this`关键字是传递给每个回调方法的参数,表示事件的发起者。
- 多播委托的调用链:`ModelEvent`作为一个多播委托,可以持有对多个方法的引用。当你调用`Invoke`时,它按照注册的顺序调用这些方法。如果调用链中的任何一个方法抛出异常,那么后续的方法调用将不会执行,除非你捕获并处理了这个异常。
- 线程安全性:虽然`?.`操作符提供了对`null`的检查,但这并不保证线程安全性。如果在检查`ModelEvent`之后和调用`Invoke`之前,另一个线程将`ModelEvent`设置为`null`,那么仍然会抛出`NullReferenceException`。在多线程环境中,通常需要额外的同步机制来确保线程安全。
- 事件的触发:如果`ModelEvent`不为空,`Invoke`会触发事件,即调用所有注册的回调方法。这些方法将按照它们被添加到委托中的顺序被调用。
在C#中,事件的使用是一种很好的设计模式,它允许对象通知其他对象发生了某些事情,而不需要知道这些对象是谁或者它们要做什么。这有助于保持代码的解耦和灵活性。