Unity中的MVC框架

news2024/12/24 20:28:38

基本概念

MVC全名是Model View Controller
是模型(model)-视图(view)-控制器(controller)的缩写
是一种软件设计规范,用一种业务逻辑、数据、界面显示 分离的方法组织代码
将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

MVC在游戏开发中不是必备的,它主要用于开发游戏UI系统逻辑

前期准备

接下来要实现一个小的UI面板,分别实现不使用MVC框架和使用MVC框架的代码,以此作为对比。

Canvas设置

非MVC框架实现

主面板逻辑

MainPanel.cs

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

public class MainPanel : MonoBehaviour
{
    //1.获得控件
    public Text txtName;
    public Text txtLev;
    public Text txtMoney;
    public Text txtGem;
    public Text txtPower;
    public Button btnRole;

    private static MainPanel panel;
    //2.添加事件
    //3.更新信息
    //4.动态显隐
    //使用静态方法,让NormalMain能够调用
    public static void ShowMe(){
        if(panel == null){
            //实例化面板对象
        GameObject res = Resources.Load<GameObject>("UI/MainPanel");
        GameObject obj = Instantiate(res);
        //设置父对象
        obj.transform.SetParent(GameObject.Find("Canvas").transform,false);

        panel = obj.GetComponent<MainPanel>();
        }
        //如果隐藏形式是setacive,则显示也要set
        panel.gameObject.SetActive(true);
        //显示完面板 更新
        panel.UpdateInfo();
        
    }
    public static void HideMe(){
        if(panel != null){
            //一. 直接删
            // Destroy(panel.gameObject);
            // panel = null;
            //二. 隐藏
            panel.gameObject.SetActive(false);
        }
    }
    // Start is called before the first frame update 
    void Start()
    {
        //2.添加事件
        btnRole.onClick.AddListener(ClickBtnRole);

    }

   private void ClickBtnRole(){
    //打开角色面板的逻辑
    Debug.Log("按钮点击");
   }

   //3.更新信息
   public void UpdateInfo(){
    //获取玩家数据 更新玩家信息
    //获取玩家数据的方式 1.网络请求  2.Json  3.xml  4.2进制  5.PlayerPrefs公共类
    
    //通过PlayerPrefs来获取本地存储的玩家信息 更新到界面上
    txtName.text = PlayerPrefs.GetString("PlayerName","阿喆不想学习");
    txtLev.text = "LV." + PlayerPrefs.GetInt("PlayerLev",1).ToString();

    txtMoney.text = PlayerPrefs.GetInt("PlayerMoney",999).ToString();
    txtGem.text = PlayerPrefs.GetInt("PlayerGem",888).ToString();
    txtPower.text = PlayerPrefs.GetInt("PlayerPower",10).ToString();
   }
}

NormalMain.cs

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.M)){
            //显示主面板
            MainPanel.ShowMe();
        }
        else if(Input.GetKeyDown(KeyCode.N)){
            //隐藏主面板
            MainPanel.HideMe();
        }
    }

角色面板逻辑

RolePanel.cs

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

public class RolePanel : MonoBehaviour
{
   //1.获得控件
    public Text txtLev;
    public Text txtHp;
    public Text txtAtk;
    public Text txtDef;
    public Text txtCrit;
    public Text txtMiss;
    public Text txtLuck;
    public Button btnClose;
    public Button btnLevUp;

    private static RolePanel panel;
    //2.添加事件
    //3.更新信息
    //4.动态显隐
    // Start is called before the first frame update 

    public static void ShowMe(){
        if(panel == null){
            //实例化面板对象
        GameObject res = Resources.Load<GameObject>("UI/RolePanel");
        GameObject obj = Instantiate(res);
        //设置父对象
        obj.transform.SetParent(GameObject.Find("Canvas").transform,false);

        panel = obj.GetComponent<RolePanel>();
        }
        //如果隐藏形式是setacive,则显示也要set
        panel.gameObject.SetActive(true);
        //显示完面板 更新
        panel.UpdateInfo();
        
    }
    public static void HideMe(){
        if(panel != null){
            //一. 直接删
            // Destroy(panel.gameObject);
            // panel = null;
            //二. 隐藏
            panel.gameObject.SetActive(false);
        }
    }

    void Start()
    {
        btnClose.onClick.AddListener(()=>{
            HideMe();
        });

        btnLevUp.onClick.AddListener(()=>{
            //升级就是数据更新
            //这里就是获取本地数据
            int lev = PlayerPrefs.GetInt("PlayerLev",1);
            int hp= PlayerPrefs.GetInt("PlayerHp",100);; 
            int def= PlayerPrefs.GetInt("PlayerDef",10);
            int atk= PlayerPrefs.GetInt("PlayerAtk",20);
            int crit= PlayerPrefs.GetInt("PlayerCrit",20);
            int miss= PlayerPrefs.GetInt("PlayerMiss",10);
            int luck= PlayerPrefs.GetInt("PlayerLuck",40);
            //然后根据升级规则去改变他
            lev += 1;
            hp += lev;
            atk += lev;
            def += lev;
            crit += lev;
            miss += lev;
            luck += lev;
            //存起来
            PlayerPrefs.SetInt("PlayerLev",lev);
            PlayerPrefs.SetInt("PlayerHp",hp);
            PlayerPrefs.SetInt("PlayerAtk",atk);
            PlayerPrefs.SetInt("PlayerDef",def);
            PlayerPrefs.SetInt("PlayerCrit",crit);
            PlayerPrefs.SetInt("PlayerMiss",miss);
            PlayerPrefs.SetInt("PlayerLuck",luck);

            //同步更新面板上的数据
            UpdateInfo();
            //更新主面板的数据
            MainPanel.Panel.UpdateInfo();
        });
    }

    //更新信息
    public void UpdateInfo(){
        txtLev.text = "LV." + PlayerPrefs.GetInt("PlayerLev",1).ToString();

        txtHp.text = PlayerPrefs.GetInt("PlayerHp",100).ToString();
        txtAtk.text = PlayerPrefs.GetInt("PlayerAtk",20).ToString();
        txtDef.text = PlayerPrefs.GetInt("PlayerDef",10).ToString();
        txtCrit.text = PlayerPrefs.GetInt("PlayerCrit",20).ToString();
        txtMiss.text = PlayerPrefs.GetInt("PlayerMiss",10).ToString();
        txtLuck.text = PlayerPrefs.GetInt("PlayerLuck",40).ToString();
       
    }
}

MainPanel更新

    public static MainPanel Panel{
        get{
            return panel;
        }
    } 

    private void ClickBtnRole(){
    //打开角色面板的逻辑
        RolePanel.ShowMe();
   }

MVC框架实现

Model数据脚本

using System.Collections;
using System.Collections.Generic;
using System.Runtime.ConstrainedExecution;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 作为一个唯一的数据模型
/// 一般情况下 要不自己是个单例模式对象 
/// 要么自己存在在一个单例模式中
/// </summary>
public class PlayerModel
{
    //数据内容
    private string playerName;
    //用属性是为了能让外部得到它但不能改变他
    public string PlayerName{
        get{
            return playerName;
        }
    }
    private int lev;
    public int Lev{
        get{
            return lev;
        }
    }
    private int money;
    public int Money{
        get{
            return money;
        }
    }
    private int gem;
    public int Gem{
        get{
            return gem;
        }
    }
    private int power;
    public int Power{
        get{
            return power;
        }
    }
    private int hp;
    public int Hp{
        get{
            return hp;
        }
    }
    private int atk;
    public int Atk{
        get{
            return atk;
        }
    }
    private int def;
    public int Def{
        get{
            return def;
        }
    }
    private int crit;
    public int Crit{
        get{
            return crit;
        }
    }
    private int miss;
    public int Miss{
        get{
            return miss;
        }
    }
    private int luck;
    public int Luck{
        get{
            return luck;
        }
    }

    //通知外部更新的事件
    //通过它来与外部建立联系 而不是直接获取外部的面板
    private event UnityAction<PlayerModel> updateEvent;

    //在外部第一次获取这个数据 如何获取
    //通过单例模式 来达到数据的唯一性 和数据获取
    private static PlayerModel data = null;

    public static PlayerModel Data{
        get{
            if(data == null){
                data = new PlayerModel();
                data.Init();
            }
            return data;
        }

    }


    //数据相关的操作
    //   初始化
    public void Init(){
        playerName = PlayerPrefs.GetString("PlayerName","阿喆不想学习");
        lev = PlayerPrefs.GetInt("PlayerLev",1);
        money = PlayerPrefs.GetInt("PlayerMoney",9999);
        gem = PlayerPrefs.GetInt("PlayerGem",8888);
        power = PlayerPrefs.GetInt("PlayerPower",99);
        hp= PlayerPrefs.GetInt("PlayerHp",100);; 
        def= PlayerPrefs.GetInt("PlayerDef",10);
        atk= PlayerPrefs.GetInt("PlayerAtk",20);
        crit= PlayerPrefs.GetInt("PlayerCrit",20);
        miss= PlayerPrefs.GetInt("PlayerMiss",10);
        luck= PlayerPrefs.GetInt("PlayerLuck",40);
    }
    //   更新  在这里是升级
    public void LevUp(){
        //升级 改变内容
        lev += 1;
        hp += lev;
        atk += lev;
        def += lev;
        crit += lev;
        miss += lev;
        luck += lev;
        //改变后保存
        SaveData();
    }
    //   保存
    public void SaveData(){
        //把这些数据内容 存储到本地
        PlayerPrefs.SetString("PlayerName",playerName);
        PlayerPrefs.SetInt("PlayerLev",lev);
        PlayerPrefs.SetInt("PlayerMoney",money);
        PlayerPrefs.SetInt("PlayerGem",gem);
        PlayerPrefs.SetInt("PlayerPower",power);
      
        PlayerPrefs.SetInt("PlayerHp",hp);
        PlayerPrefs.SetInt("PlayerAtk",atk);
        PlayerPrefs.SetInt("PlayerDef",def);
        PlayerPrefs.SetInt("PlayerCrit",crit);
        PlayerPrefs.SetInt("PlayerMiss",miss);
        PlayerPrefs.SetInt("PlayerLuck",luck);

        UpdateInfo();
    }

    public void AddEventListener(UnityAction<PlayerModel> function){
        updateEvent += function;
    }

    public void RemoveEventListener(UnityAction<PlayerModel> function){
        updateEvent -= function;
    }

    //通知外面更新数据的方法
    private void UpdateInfo(){
        //找到对应的 使用数据的脚本 去更新数据
        if(updateEvent != null){
            updateEvent(this);
        }
    }
}

View界面脚本

MainView.cs

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

public class MainView : MonoBehaviour
{
    //1.找控件
    public Text txtName;
    public Text txtLev;
    public Text txtMoney;
    public Text txtGem;
    public Text txtPower;
    public Button btnRole;
    public Button btnSkill;

    //2.提供面板更新的方法给外部
    public void UpdateInfo(PlayerModel data){
        txtName.text = data.PlayerName;
        txtLev.text = "LV." + data.Lev;
        txtMoney.text = data.Money.ToString();
        txtGem.text = data.Gem.ToString();
        txtPower.text = data.Power.ToString();
    }
}

RoleView.cs

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

public class RoleView : MonoBehaviour
{
    //1.找控件
    
    public Text txtLev;
    public Text txtHp;
    public Text txtAtk;
    public Text txtDef;
    public Text txtCrit;
    public Text txtMiss;
    public Text txtLuck;
    public Button btnClose;
    public Button btnLevUp;

    //2.提供面板更新的相关方法给外部
    public void UpdateInfo(PlayerModel data){
        txtLev.text = "LV." + data.Lev;
        txtHp.text = data.Hp.ToString();
        txtAtk.text = data.Atk.ToString();
        txtDef.text = data.Def.ToString();
        txtCrit.text = data.Crit.ToString();
        txtMiss.text = data.Miss.ToString();
        txtLuck.text = data.Luck.ToString();
    }
}

Controller业务逻辑

MainController

using System.Collections;
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEngine;

/// <summary>
/// Controller要处理的东西 就是业务逻辑
/// </summary>
public class MainController : MonoBehaviour
{
    //能够在Controller中得到界面才行
    private MainView mainView;
    //面板之间的交互都是通过Controller来实现,我们不想让mainView也变成静态被其它访问,
    //因此可以设置个静态的 Controller,因为Controller也要被外部访问
    private static MainController controller = null;
    public static MainController Controller{
        get{
            return controller;
        }
    }
    //1.界面的显隐
     public static void ShowMe(){
        if(controller == null){
            //实例化面板对象
        GameObject res = Resources.Load<GameObject>("UI/MainPanel");
        GameObject obj = Instantiate(res);
        //设置父对象
        obj.transform.SetParent(GameObject.Find("Canvas").transform,false);

        controller = obj.GetComponent<MainController>();
        }
        //如果隐藏形式是setacive,则显示也要set
        controller.gameObject.SetActive(true);

        
    }
    public static void HideMe(){
        if(controller != null){

            controller.gameObject.SetActive(false);
        }
    }

    private void Start(){
        //获取同样挂载在一个对象上的 view脚本
        mainView = this.GetComponent<MainView>();
        //第一次更新
        mainView.UpdateInfo(PlayerModel.Data);

        //2.界面 事件的监听 来处理对应的业务逻辑
        mainView.btnRole.onClick.AddListener(ClickRoleBtn);

        //PlayerModel.Data.AddEventListener(mainView.UpdateInfo);
        PlayerModel.Data.AddEventListener(UpdateInfo);
    }
    
    private void ClickRoleBtn(){
        RoleController.ShowMe();
    }
    //3. 界面的更新
    private void UpdateInfo(PlayerModel data)
    {
        if(mainView != null){
            mainView.UpdateInfo(data);
        }    
    }

    private void OnDestroy() {
        PlayerModel.Data.RemoveEventListener(UpdateInfo);
    }
}


RoleController

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

public class RoleController : MonoBehaviour
{
    private RoleView roleView;

    private  static RoleController controller = null;
    public static RoleController Controller{
        get{
            return controller;
        }
    }

     public static void ShowMe(){
        if(controller == null){
            //实例化面板对象
        GameObject res = Resources.Load<GameObject>("UI/RolePanel");
        GameObject obj = Instantiate(res);
        //设置父对象
        obj.transform.SetParent(GameObject.Find("Canvas").transform,false);

        controller = obj.GetComponent<RoleController>();
        }
        //如果隐藏形式是setacive,则显示也要set
        controller.gameObject.SetActive(true);

        
    }
    public static void HideMe(){
        if(controller != null){
            controller.gameObject.SetActive(false);
        }
    }
    void Start()
    {
        roleView = this.GetComponent<RoleView>();
        //第一次更新面板
        roleView.UpdateInfo(PlayerModel.Data);

        roleView.btnClose.onClick.AddListener(ClickCloseBtn);
        roleView.btnLevUp.onClick.AddListener(ClickLevUpBtn);
        //PlayerModel.Data.AddEventListener(roleView.UpdateInfo);
        PlayerModel.Data.AddEventListener(UpdateInfo);
    }

    private void ClickCloseBtn(){
        HideMe();
    }

    private void ClickLevUpBtn(){
        //通过数据模块 进行升级 达到数据改变
        PlayerModel.Data.LevUp();
        
    }
    // Update is called once per frame
    private void UpdateInfo(PlayerModel data)
    {
        if(roleView != null){
            roleView.UpdateInfo(data);
        }    
    }

    private void OnDestroy() {
        PlayerModel.Data.RemoveEventListener(UpdateInfo);
    }
}

对比与总结

好处

1.各司其职,互不干涉 --编程思路更清晰
2.有利开发中的分工 -- 多人协同开发时,同步并行
3.有利于组件重用  -- 项目换皮时,功能变化小时,提高开发效率

坏处

1.增加了程序文件的体量  -- 脚本由一变三
2.增加了结构的复杂性  --对于不清楚MVC原理的人不友好
3.效率相对较低     -- 对象之间的相互跳转,始终伴随着一定开销

扩展

MVC的美中不足

M和V之间存在着联系,也就是数据和界面之间存在着耦合性,当数据结构改变时会牵扯界面逻辑随之改动。
在MVC中当需求变化时,需要维护的对象数量会增加

如这一段

改变了PlayerModel中的变量的话,则MainView里的这个函数可能也要调整。

MVX

数据和界面是必备的内容
我们可以通过改变X元素来优化原本的MVC
也就是改变联系和处理M(数据)和V(界面)的方式

MVP:切断View和Model的耦合,让Presenter处理一切
MVVM:MVP的升级版,让ViewModel和V进行双向数据绑定,更新VM等同于更新V,反之同理
MVE:用EventCenter事件中心来分发消息

学习MVX的目的
不要拘泥于框架结构和设计模式要找到一个适合自己项目
一个稳定的,有序的,能满足项目需求的实现方式

MVP

全称为模型(Model)-视图(View)一主持人(Presenter)

Model提供数据,View负责界面,Presenter负责逻辑的处理

它是MVC的一种变式,是针对MVC中M和V存在耦合的优化

与MVC的区别:

在MVC中View会直接从Model中读取数据而不是通过 Controller
而在MVP中View并不直接使用Model,它们之间的通信是通过Presenter来进行的,所有的交互都发生在Presenter内部。

同样是上面的项目

MVP_MainView.cs

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

public class MVP_MainView : MonoBehaviour
{
     //1.找控件
    public Text txtName;
    public Text txtLev;
    public Text txtMoney;
    public Text txtGem;
    public Text txtPower;
    public Button btnRole;
    public Button btnSkill;

    // //2.提供面板更新的方法给外部
    // public void UpdateInfo(string name, int lev, int money, int gem, int power){
    //     txtName.text = name;
    //     txtLev.text = "LV." + lev;
    //     txtMoney.text = money.ToString();
    //     txtGem.text = gem.ToString();
    //     txtPower.text = power.ToString();
    // }
}

MVP_RoleView.cs

public class MVP_RoleView : MonoBehaviour
{
 //1.找控件
    
    public Text txtLev;
    public Text txtHp;
    public Text txtAtk;
    public Text txtDef;
    public Text txtCrit;
    public Text txtMiss;
    public Text txtLuck;
    public Button btnClose;
    public Button btnLevUp;

    //2.提供面板更新的相关方法给外部
    //方法可选 到时候可以直接在P里面通过访问控件 去修改

}

MainPresenter.cs

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

public class MainPresenter : MonoBehaviour
{
 //能够在Presenter中得到界面才行
    private MVP_MainView mainView;
    //面板之间的交互都是通过Controller来实现,我们不想让mainView也变成静态被其它访问,
    //因此可以设置个静态的 Controller,因为Controller也要被外部访问
    private static MainPresenter presenter = null;
    public static MainPresenter Presenter{
        get{
            return presenter;
        }
    }
    //1.界面的显隐
     public static void ShowMe(){
        if(presenter == null){
            //实例化面板对象
        GameObject res = Resources.Load<GameObject>("UI/MainPanel");
        GameObject obj = Instantiate(res);
        //设置父对象
        obj.transform.SetParent(GameObject.Find("Canvas").transform,false);

        presenter = obj.GetComponent<MainPresenter>();
        }
        //如果隐藏形式是setacive,则显示也要set
        presenter.gameObject.SetActive(true);

        
    }
    public static void HideMe(){
        if(presenter != null){

            presenter.gameObject.SetActive(false);
        }
    }

    private void Start(){
        //获取同样挂载在一个对象上的 view脚本
        mainView = this.GetComponent<MVP_MainView>();
        //第一次更新
        //mainView.UpdateInfo(PlayerModel.Data);
        //通过P自己的更新方法来更新
        UpdateInfo(PlayerModel.Data);
        //2.界面 事件的监听 来处理对应的业务逻辑
        mainView.btnRole.onClick.AddListener(ClickRoleBtn);

        //PlayerModel.Data.AddEventListener(mainView.UpdateInfo);
        PlayerModel.Data.AddEventListener(UpdateInfo);
    }
    
    private void ClickRoleBtn(){
        //RoleController.ShowMe();
        RolePresenter.ShowMe();
    }
    //3. 界面的更新
    private void UpdateInfo(PlayerModel data)
    {
        if(mainView != null){
            //mainView.UpdateInfo(data);
            //以前是把数据M传到V中去更新,现在全部由P来做
            mainView.txtName.text = data.PlayerName;
            mainView.txtLev.text = "LV." + data.Lev;
            mainView.txtMoney.text = data.Money.ToString();
            mainView.txtGem.text = data.Gem.ToString();
            mainView.txtPower.text = data.Power.ToString();
        }    
    }

    private void OnDestroy() {
        PlayerModel.Data.RemoveEventListener(UpdateInfo);
    }
}

RolePresenter.cs

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

public class RolePresenter : MonoBehaviour
{
    private MVP_RoleView roleView;

    private  static RolePresenter presenter = null;
    public static RolePresenter Presenter{
        get{
            return presenter;
        }
    }

     public static void ShowMe(){
        if(presenter == null){
            //实例化面板对象
        GameObject res = Resources.Load<GameObject>("UI/RolePanel");
        GameObject obj = Instantiate(res);
        //设置父对象
        obj.transform.SetParent(GameObject.Find("Canvas").transform,false);

        presenter = obj.GetComponent<RolePresenter> ();
        }
        //如果隐藏形式是setacive,则显示也要set
        presenter.gameObject.SetActive(true);

        
    }
    public static void HideMe(){
        if(presenter != null){
            presenter.gameObject.SetActive(false);
        }
    }
    void Start()
    {
        roleView = this.GetComponent<MVP_RoleView>();
        //第一次更新面板
        UpdateInfo(PlayerModel.Data);

        roleView.btnClose.onClick.AddListener(ClickCloseBtn);
        roleView.btnLevUp.onClick.AddListener(ClickLevUpBtn);
        //PlayerModel.Data.AddEventListener(roleView.UpdateInfo);
        PlayerModel.Data.AddEventListener(UpdateInfo);
    }

    private void ClickCloseBtn(){
        HideMe();
    }

    private void ClickLevUpBtn(){
        //通过数据模块 进行升级 达到数据改变
        PlayerModel.Data.LevUp();
        
    }
    // Update is called once per frame
    private void UpdateInfo(PlayerModel data)
    {
        if(roleView != null){
            //直接在p中得到V界面的控件,断开M和V的联系
            roleView.txtLev.text = "LV." + data.Lev;
            roleView.txtHp.text = data.Hp.ToString();
            roleView.txtAtk.text = data.Atk.ToString();
            roleView.txtDef.text = data.Def.ToString();
            roleView.txtCrit.text = data.Crit.ToString();
            roleView.txtMiss.text = data.Miss.ToString();
            roleView.txtLuck.text = data.Luck.ToString();
        }    
    }

    private void OnDestroy() {
        PlayerModel.Data.RemoveEventListener(UpdateInfo);
    }
}

MVP同样由缺点,Presenter中的逻辑很多。

但是之后逻辑修改也只用在Presenter中。

MVVM

全称为模型(Model)-视图(View)-视图模型(ViewModel)
Model提供数据,View负责界面,ViewModel负责逻辑的处理
MVVM的由来是MVP(Model-View-Presenter)模式与WPF结合应用时发展演变过来的一种新型框架

数据绑定

将一个用户界面元素(控件)的属性 绑定到 一个类型(对象)实例上的某个属性的方法。

如果开发者有一个MainViewMode类型的实例,那么他就可以把MainViewMode的“Lev”属性绑定到一个Ul中Text的“Text”属性上。“绑定”了这2个属性之后,对Text的Text属性的更改“传播”MainViewMode的Lev属性,而对MainViewMode的Lev属性的更改同样会“传播”到Text的Text属性

MVVM在Unity中水土不服

因为View对象始终由我们来书写,没有UI配置文件(如WPF中的XAML),要想实现传播需要事件/委托,很麻烦。

硬要在Unity中实现MVVM,需要写三模块,并且还要对V和VM进行数据绑定,工作量大,好处也不够明显

Unity的第三方MVVM框架
Loxodon Framework
https://github.com/vovgou/loxodon-framework
uMVVM
https://github.com/MEyes/uMVVM

MVE

全称为模型(Model)-视图(View)-事件中心(EventCenter)
Model提供数据,View负责界面,EventCenter负责数据传递

好处:

利用事件中心的观察者模式
让M和V层的之间的关系更加灵活多变
减少了目前数据层的负载
将数据层事件全部交由事件中心处理

总结

铁打的M和V,流水的X
数据和界面是必备的内容
我们可以通过改变X元素来优化原本的MVC
也就是改变联系和处理M(数据)和V(界面)的方式

不要拘泥于框架结构和设计模式要找到一个适合自己项目的
一个稳定的,有序的,能满足项目需求的实现方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1718637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ch4网络层---计算机网络期末复习(持续更新中)

网络层概述 将分组从发送方主机传送到接收方主机 发送方将运输层数据段封装成分组 接收方将分组解封装后将数据段递交给运输层网络层协议存在于每台主机和路由器上 路由器检查所有经过它的IP分组的分组头 注意路由器只有3层(网络层、链路层、物理层) 网络层提供的服务 一…

无人售货机零售业务成功指南:从市场分析到创新策略

在科技驱动的零售新时代&#xff0c;无人售货机作为一种便捷购物解决方案&#xff0c;正逐步兴起&#xff0c;它不仅优化了消费者体验&#xff0c;还显著降低了人力成本&#xff0c;提升了运营效能。开展这项业务前&#xff0c;深入的市场剖析不可或缺&#xff0c;需聚焦消费者…

命令模式(行为型)

目录 一、前言 二、命令模式 三、总结 一、前言 命令模式&#xff08;Command Pattern&#xff09;是一种行为型设计模式&#xff0c;命令模式将一个请求封装为一个对象&#xff0c;从而可以用不同的请求对客户进行参数化&#xff1b;对请求排队或记录请求日志&#xff0c;以…

【C++】C++入门2.0

各位读者老爷好&#xff0c;本鼠最近浅学了一点C的入门知识&#xff01;利用本博客作为笔记的同时也希望得到各位大佬的垂阅&#xff01; 目录 1. 引用 1.1.引用的概念 1.2.引用的特性 1.3.引用的使用场景 1.4.引用的易错点 1.5.引用的优势 1.6.引用和指针 2.内联函数 …

B端UI设计,演绎高情逸态之妙

B端UI设计&#xff0c;演绎高情逸态之妙

汽车IVI中控开发入门及进阶(二十三):i.MX8

前言: IVI市场的复杂性急剧增加,而TimeToMarket在几代产品中从5年减少到2-3年。Tier1正在接近开放系统的模型(用户可以安装应用程序),从专有/关闭源代码到标准接口/开放源代码,从软件堆栈对系统体系结构/应用层/系统验证和鉴定的完全所有权,越来越依赖第三方中间件和平…

STM32自己从零开始实操03:输出部分原理图

一、继电器电路 1.1指路 延续使用 JZC-33F-012-ZS3 继电器&#xff0c;设计出以小电流撬动大电流的继电器电路。 &#xff08;提示&#xff09;电路需要包含&#xff1a;三极管开关电路、续流二极管、滤波电容、指示灯、输出部分。 1.2数据手册重要信息提炼 联系排列&…

Rainbond 携手 TOPIAM 打造企业级云原生身份管控新体验

TOPIAM 企业数字身份管控平台&#xff0c; 是一个开源的IDaas/IAM平台、用于管理账号、权限、身份认证、应用访问&#xff0c;帮助整合部署在本地或云端的内部办公系统、业务系统及三方 SaaS 系统的所有身份&#xff0c;实现一个账号打通所有应用的服务。 传统企业 IT 采用烟囱…

NSS题目练习5

[NISACTF 2022]babyupload 打开后尝试上传php&#xff0c;jpg&#xff0c;png文件都没成功 查看源代码发现有个/source文件 访问后下载压缩包发现有一个python文件 搜索后知道大致意思是&#xff0c;上传的文件不能有后缀名&#xff0c;上传后生成一个uuid&#xff0c;并将uuid…

redis缓存token设置jwt令牌过期时间

登录接口 在上文中 我们已经设置了自定义登录接口自定义拦截器jwt登录校验接口模拟账号登录_jwt自定义拦截器-CSDN博客https://blog.csdn.net/2202_75352238/article/details/138424691?spm1001.2014.3001.5501 但是上文jwt过期时间是由yml文件中配置的&#xff0c;比较不优雅…

Amis源码构建 sdk版本

建议在linux环境下构建&#xff08;mac环境下也可以&#xff09;&#xff0c;需要用到sh脚本&#xff08;amis/build.sh&#xff09;。 Js sdk打包是基于fis进行编译打包的&#xff0c;具体可见fis-conf.js&#xff1a; amis-master源码下载:https://github.com/baidu/amis g…

【OceanBase诊断调优】—— obdiag 工具助力OceanBase数据库诊断调优(DBA 从入门到实践第八期)

1. 前言 昨天给大家分享了【DBA从入门到实践】第八期&#xff1a;OceanBase数据库诊断调优、认证体系和用户实践 中obdiag的部分&#xff0c;今天将其中的内容以博客的形式给大家展开一下&#xff0c;方便大家阅读。 2. 正文 在介绍敏捷诊断工具之前&#xff0c;先说说OceanBa…

【C语言】常见的动态内存的错误

前言 在动态内存函数的使用过程中我们可能会遇到一些错误&#xff0c;这里将常见的错误进行总结。 对NULL解引用 请看以下代码&#xff1a; 可以看到&#xff0c;这时我们的malloc开辟是失败的&#xff0c;所以返回的是空指针NULL&#xff0c;而我们却没有进行检查&#xff0…

使用PNP管控制MCU是否需要复位

这两台用到一款芯片带电池&#xff0c;希望电池还有电芯片在工作的时候插入电源不要给芯片复位&#xff0c;当电池没电&#xff0c;芯片不在工作的时候&#xff0c;插入电源给芯片复位所以使用一个PNP三极管&#xff0c;通过芯片IO控制是否打开复位&#xff0c;当芯片正常工作的…

反激电源压敏电阻设计

压敏电阻的作用&#xff1a;浪涌防护。在电源出现浪涌冲击时&#xff0c;保护核心器件不受到损坏。其实类似于稳压二极管 瞬间的瞬态波 1 压敏电压 单位是&#xff0c;虽然压敏电阻可以吸收很大的浪涌能量&#xff0c;但是不能承受mA以上的持续电流。压敏电压计算公式 2 通流容…

(函数)字符串拼接(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h> # include <string.h>//声明字符串拼接函数&#xff1b; void splice(char a[100], char b[100]);int main() {//初始化变量值&#xff1b;char a[100] …

unity打包的WebGL部署到IIS问题

部署之后会出错&#xff0c;我遇到的有以下几种&#xff1b; 进度条卡住不动 明明已经部署到了IIS上&#xff0c;为什么浏览网页的时候还是过不去或者直接报错。 进度条卡住不动的问题其实就是wasm和data的错误。 此时在浏览器上按F12进入开发者模式查看错误&#xff08;下图…

【前端】Vuex笔记(超详细!!)

最近花了两周时间&#xff0c;完完全全的跟着Vuex官方的视频学完了Vuex并且详详细细的做了笔记&#xff0c;其中总结部分是我对于整个视频课程的总结&#xff0c;视频部分是跟着视频做的笔记&#xff0c;如果总结部分有不懂的话&#xff0c;直接去视频部分查找对应的笔记即可&a…

uniapp的tooltip功能放到表单laber

在uniapp中&#xff0c;tooltip功能通常是通过view组件的hover-class属性来实现的&#xff0c;而不是直接放在form的label上。hover-class属性可以定义当元素处于hover状态时的样式类&#xff0c;通过这个属性&#xff0c;可以实现一个类似tooltip的效果。 以下是一个简单的例…

9.Halcon3D点云力矩求解-平面拟合用法

1.实现效果 我们在使用3d相机对产品进行扫描生成点云的时候,由于安装问题,所以我们不可能保证每次产品扫描出来都在坐标系中位置和姿态非常标准。 上述算法描述的就是在某一个维度或者某几个维度上将点云数据和坐标系对齐; 至于怎么对齐,如何实现就是今天的内容。 本人能…