Unity中的MVC思想

MVC的基本概念

MVC的历史

MVC思想的出现并不是专为开发游戏,它是一种通用的软件开发思想,是应用软件开发和网页开发最常用和流行的通用开发框架。

 这样的网页、软件开发习惯发展成为了MVC思想。

MVC的基本概念

MVC=Model(模型) View(视图) Controller(控制器)

一种软件设计规范,特点是将业务逻辑、数据、界面显示分离的组织代码方法;通过将业务逻辑聚集到一个部件里,实现在改进和个性化定制界面及用户交互时,不需要重新编写业务逻辑

 ​​​​​​

MVC在游戏中的应⽤

 例如:编写一个面板按键功能

  • 不使用MVC结构:将按键的显示、控件获取、事件触发等放入一个脚本中;

  •  问题:相互联系、耦合过高、难以更改,不方便后续的优化。

  • 使用MVC结构:将按键的显示、控件获取、事件触发、状态更新等分离;

MVC结构⼀般流程

优点:

降低耦合,方便修改,逻辑清晰

缺点:

脚本变多,体量增大,流程复杂

综上:MVC结构很适合游戏UI系统庞杂,且有反复修改迭代需求的UI系统逻辑。

MVC的基本实例

需要完成的功能:

MVC示范yong'li

如上视频,下文将通过以不用MVC结构和使用MVC结构两种方法来实现以上视频中的UI功能;即,菜单界面的显示和关闭,显示内容的更新与升级等功能导致的数据更改。

不使⽤MVC结构的UI实现

NormalMain脚本:挂接于MainCamera的脚本

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

public class NormalMain : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.M))
        {
            //显示面板
            MainPanel.ShowMe();
        }
        if (Input.GetKeyDown(KeyCode.N))
        {
            //隐藏面板
            MainPanel.HideMe();
        }
    }
}

MainPanel脚本:挂接与主界⾯UI上的脚本

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

public class MainPanel : MonoBehaviour
{
    //传统UI写法(不使用MVC)

    //控件
    public Text txtName;
    public Text txtlev;
    public Text txtMoney;
    public Text txtGem;
    public Text txtPower;

    public Button btnRole;

    private static MainPanel panel;

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


    //动态显隐
    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>();
        }
        panel.gameObject.SetActive(true);
        //显示面板后更新信息
        panel.UpdateTnfo();
    }
    public static void HideMe()
    {
        if(panel != null)
        {
            //失活隐藏
            panel.gameObject.SetActive(false);
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        //事件
        btnRole.onClick.AddListener(() =>
        {
            //打开角色面板逻辑
            RolePanel.ShowMe();
        });
        
    }

    //更新信息
    public void UpdateTnfo()
    {
        //玩家信息获取和更新
        txtName.text = PlayerPrefs.GetString("PlayerName", "Toka");
        txtlev.text = "LEVEL."+PlayerPrefs.GetInt("PlayerLev", 1).ToString();
        txtMoney.text = PlayerPrefs.GetInt("PlayerMoney", 123).ToString();
        txtGem.text = PlayerPrefs.GetInt("PlayerGem", 456).ToString();
        txtPower.text = PlayerPrefs.GetInt("PlayerPower", 9).ToString();
    }
}

RolePanel脚本:挂接与⻆⾊UI上的脚本

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

public class RolePanel : MonoBehaviour
{
    //控件
    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;

    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>();
        }
        panel.gameObject.SetActive(true);
        //显示面板后更新信息
        panel.UpdateInfo();
    }

    public static void HideMe()
    {
        if (panel != null)
        {
            //失活隐藏
            panel.gameObject.SetActive(false);
        }
    }


    // Start is called before the first frame update
    void Start()
    {
        //事件
        btnClose.onClick.AddListener(() =>
        {
            //关闭角色面板逻辑
            HideMe();
        });
        btnLevUp.onClick.AddListener(() =>
        {
            //更新升级后数据
            ClickLevUp();
        });
    }

    public void ClickLevUp()
    {
        //获取数据
        int lev = PlayerPrefs.GetInt("PlayerLev", 1);
        int hp = PlayerPrefs.GetInt("PlayerHp", 100);
        int atk = PlayerPrefs.GetInt("PlayerAtk", 10);
        int def = PlayerPrefs.GetInt("PlayerDef", 10);
        int crit = PlayerPrefs.GetInt("PlayerCrit", 10);
        int miss = PlayerPrefs.GetInt("PlayerMiss", 10);
        int luck = PlayerPrefs.GetInt("PlayerLuck", 50);
        //升级变化
        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.UpdateTnfo();
    }

    //更新信息
    public void UpdateInfo()
    {
        //玩家信息获取和更新
        txtLev.text = "LEVEL."+PlayerPrefs.GetInt("PlayerLev",1);
        txtHp.text = PlayerPrefs.GetInt("PlayerHp", 100).ToString();
        txtAtk.text = PlayerPrefs.GetInt("PlayerAtk", 10).ToString();
        txtDef.text = PlayerPrefs.GetInt("PlayerDef", 10).ToString();
        txtCrit.text = PlayerPrefs.GetInt("PlayerCrit", 10).ToString();
        txtMiss.text = PlayerPrefs.GetInt("PlayerMiss", 10).ToString();
        txtLuck.text = PlayerPrefs.GetInt("PlayerLuck", 50).ToString();

    }
}

显而易见的,不采用MVC思想的UI逻辑写法中,当玩家的信息发生更改,UI界面内容需要更新时,必然需要获取相关的信息(红底),而这些信息与数据却声明在不同的脚本的不同地方(绿底),导致编程变得繁琐,代码缺乏逻辑性。上文中例子所实现的UI功能实际上是非常简单且小体量的,尚且如此混乱。如果要实现一个本身就很庞杂的UI逻辑,采用传统的一个面板对应一个面板逻辑脚本的写法,逻辑上的复杂和混乱程度可想而知。

使⽤MVC结构的UI实现(源码)

MVCTest

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

public class MVCTest : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.M))
        {
            //显示面板
            MainController.ShowMe();
        }
        if (Input.GetKeyDown(KeyCode.N))
        {
            //隐藏面板
            MainController.HideMe();
        }
    }
}

Model

数据:负责增删改查获取界面上所需要的数据

PlayerModel

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

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", "Toka");
        lev = PlayerPrefs.GetInt("PlayerLev",1);
        money = PlayerPrefs.GetInt("PlayerMoney",123);
        gem = PlayerPrefs.GetInt("PlayerGem",456);
        power = PlayerPrefs.GetInt("PlayerPower",9);

        hp = PlayerPrefs.GetInt("PlayerHp",100);
        atk = PlayerPrefs.GetInt("PlayerAtk",10);
        def = PlayerPrefs.GetInt("PlayerDef", 10);
        crit = PlayerPrefs.GetInt("PlayerCrit", 10);
        miss = PlayerPrefs.GetInt("PlayerMiss", 10);
        luck = PlayerPrefs.GetInt("PlayerLuck", 50);
    }

    //更新 升级
    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;
    }

    //通知外部更新
    //与外部联系,用以封装M层,而非直接获取外部面板
    private void UpdateInfo()
    {
        //寻找对应数据脚本更新数据
        if (updateEvent != null)
        {
            updateEvent(this);
        }
    }

    
}

View

界面:负责获取控件,更新控件信息

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

public class MainView : MonoBehaviour
{
    //获取控件
    public Button btnRole;
    public Button btnSill;

    public Text txtName;
    public Text txtLev;
    public Text txtMoney;
    public Text txtGem;
    public Text txtPower;


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

RoleView

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

public class RoleView : MonoBehaviour
{
    //获取控件
    public Button btnClose;
    public Button btnLevUp;

    public Text txtLev;
    public Text txtHp;
    public Text txtAtk;
    public Text txtDef;
    public Text txtCrit;
    public Text txtMiss;
    public Text txtLuck;


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

Controller

控制:负责业务逻辑处理界面事件监听,触发数据更新,触发界面更新

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

public class MainController : MonoBehaviour
{
    //获取界面
    private MainView mainView;

    //C层是V和M的中介
    private static MainController controller =null;

    //V将不再直接访问M,而是通过C中的Cotroller来访问数据
    public static MainController Controller
    {
        get
        {
            return controller;
        }
    }

    //界面显隐
    public static void ShowMe()
    {
        if  90(0controller == null)
        {
            GameObject res = Resources.Load<GameObject>("UI/MainPanel");
            GameObject obj = Instantiate(res);
            obj.transform.SetParent(GameObject.Find("Canvas").transform, false);

            controller = obj.GetComponent<MainController>();
        }
        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);

        //事件监听和业务逻辑处理
        mainView.btnRole.onClick.AddListener(ClickRoleBtn);

        PlayerModel.Data.AddEventListener(UpdateInfo);
    }

    private void ClickRoleBtn()
    {
        //利用Contorller显示角色面板
        RoleController.ShowMe();
    }

    //界面更新
    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;

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>();
        }
        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(UpdateInfo);
    }

    private void ClickLevUpBtn()
    {
        //通过数据模块M升级
        PlayerModel.Data.LevUp();
    }

    private void ClickCloseBtn()
    {
        HideMe();
    }

    private void UpdateInfo(PlayerModel data)
    {
        if(roleView != null)
        {
            roleView.UpdateInfo(data);
        }
    }

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

对⽐

脚本⽅⾯

  • 普通做法

一个面板对应一个脚本,一个脚本处理一个面板相关逻辑

  • MVC

数据、界面、业务逻辑三者分离

UI逻辑⽅⾯

  • 普通做法

  • MVC

运作流程

  • 普通做法

所有的工作混在在一个面板逻辑脚本中

  • MVC

有清晰的运作逻辑 

MVC的优缺点

  • 优点

  1. 各司其职,互不干涉——编程思路更清晰

  2. 有利于开发中的分工——多人协作开发时,同步并行

  3. 有利于组件重组——项目换皮时,功能变化小时,提高开发效率

  • 缺点

  1. 增加了程序文件体量——脚本由一变三

  2. 增加了结构复杂性——对于不清楚MVC思想的人不友好

  3. 效率相对较低——对象之间相互跳转,始终伴随一定开销(只是相较而言,但实际上多出的开销对于现在的计算机设备来讲很微小)

什么时候采⽤MVC开发UI逻辑

根据项⽬中成员技术来决定

团队中的人员是否熟知MVC结构,采用MVC结构是否会徒增成本

根据项⽬本⾝规模来决定

如果开发的UI项目过小,仅仅只有几个面板,一两组数据,采用MVC反而会将其结构变得更复杂

根据开发团队规模来决定

团队规模是否比较大,需要采用MVC结构使分工更明确,以便更好地实现协作并行开发,提高团队开发效率

UI结构拓展和思维发散

MVC的不⾜

M于V之间存在着联系,也就是数据和界面之间存在着耦合性,当数据结构改变时会牵扯到界面逻辑随之改变。

在MVC中当需求变化时,需要维护的对象将增加。

修改数据Model可能涉及到View和Controller都要修改,这是因为界面和数据还存在一定耦合。

解决这一问题的方案便是MVX

MVX

MVX是什么

数据和界面是不可缺少的必备内容,我们可以通过改变X元素来优化原本的MVC结构,也就是说改变联系和处理M与V的方式。

MVX有哪些

MVP
切断View和Model的耦合,让Presener处理一切;
MVVM
MVP的升级版,让ViewModel和V进行栓下数据绑定,更新VN等同于更新V,反之亦然;
MVE
用EventCenter事件中心来分发消息,来联系M与V;

总结

本质上,MVC是一种组织代码的结构也是一种思想,在学习MVC结构的同时,不能拘泥于MVC结构和设计模式。不要为了框架而框架,需要灵活和理解各种MVX,找到一个适合自己项目的,稳定的,有序的,能满足需求的实现方式。学习和运用框架结构和设计模式的根本目的应该是提高项目的开发能力与开发效率,而非学习框架本身。

真正适合开发的MVC思想⸺PureMVC

PureMVC是什么

基于MVC思想和一些基础设计模式建立的一个1轻量级的应用设计框架;

一个免费的开源框架;

最初是在Flash中执行ActionScript3语言所使用的,现在已经一移植到了几乎所有主流平台(包括u3d)。

PureMVC如何获取

官网:http://puremvc.org/

点击可下载

 中文文档

PureMVC基本结构

 

 PureMVC实际上就是基于MVC思想作为核心在外部包裹上设计模式组成的框架。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: UnityMVC模式是一种用于开发游戏和应用程序的软件架构模式,其MVC代表模型-视图-控制器。这种模式通过将应用程序分离为三个互相独立的组件,以提高应用程序的可维护性、可扩展性和可重用性。 模型是Unity的数据层,这里包含了所有游戏对象的数据。游戏状态、游戏逻辑等都在模型实现。视图层负责用户界面,包括所有可触摸对象的操作、视觉效果和GUI元素。控制器实现用户输入事件的处理。一旦用户与控制器互动,控制器就会向游戏的其他部分发送消息以响应用户输入。 在UnityMVC架构通过使用一组特殊的组件将这三个部分联系起来。组件类似于对象之间的接口,它们在模型、视图和控制器之间传递消息。这些消息使得一个组件可以更新另一个组件的状态,从而使得整个应用程序变得更加强大和灵活。 总之,UnityMVC结构提供了一种能够简化大型游戏和应用程序开发的强大工具。通过将模型、视图和控制器分离为独立的组件,UnityMVC架构可以减少代码的冗余程度,提高代码的可读性和可维护性。 ### 回答2: 在UnityMVC代表的是一种软件架构模式,即Model-View-Controller,通过将应用程序分为三个主要部件,帮助开发者进行程序的管理和维护。 Model层是处理程序数据的部分,通常包括游戏世界的各种实体和数据类型。View层代表着游戏界面,在Unity可以通过Unity UI工具创建GUI样式的用户界面。Controller层则是处理输入、对象管理、流程控制等交互逻辑的部分。 在Unity使用MVC模式的好处是可以明确代码的职责和功能,分离视图与数据逻辑,增加代码的可扩展和可维护性。在处理复杂逻辑时,MVC能够清晰地定义模块,并帮助开发者更容易地组织和重用代码。同时,MVC也能够使代码分为多个部分,便于团队协作与分工。 在实现MVC模式时,Unity还提供了一些相关的功能,如通过GameObject和Component组件来实现视图和控制器的绑定,通过ScriptableObject来创建独立的数据模型,使得MVC结构更加灵活与可扩展。不过,在使用MVC模式的同时,开发者还需要注意代码的耦合度,避免过度强制组织代码结构,从而影响游戏性能和开发效率。 ### 回答3: Unity是一个非常流行的游戏引擎MVC(模型-视图-控制器)是一种常用的软件架构模式。在Unity实现MVC结构可以有效地管理游戏对象和游戏逻辑。 MVC将应用程序分为三个主要部分:模型、视图和控制器。模型表示应用程序的数据和业务逻辑,视图表示用户界面,控制器是模型和视图之间的桥梁,它处理用户输入并更新模型和视图。 在Unity,模型可以是C#脚本,它们管理游戏对象的状态和行为。视图是Unity的场景,它包含游戏对象和用户界面元素。控制器可以是用户输入或脚本,它们通过操作模型和视图来实现游戏逻辑。 实现MVC结构可以使游戏开发更加清晰和可维护。开发者可以更好地管理游戏逻辑和用户界面。例如,通过将游戏逻辑放在模型,可以使逻辑更容易测试和重用。通过将用户界面放在视图,可以更容易地更新和修改用户界面。 在Unity实现MVC结构需要开发者具备一定的软件架构和代码编写能力,但是它可以提高代码的可读性和可维护性,帮助开发者开发更加健壮和可扩展的游戏应用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值