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
界面:负责获取控件,更新控件信息
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
控制:负责业务逻辑处理界面事件监听,触发数据更新,触发界面更新
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的优缺点
-
优点
-
各司其职,互不干涉——编程思路更清晰
-
有利于开发中的分工——多人协作开发时,同步并行
-
有利于组件重组——项目换皮时,功能变化小时,提高开发效率
-
缺点
-
增加了程序文件体量——脚本由一变三
-
增加了结构复杂性——对于不清楚MVC思想的人不友好
-
效率相对较低——对象之间相互跳转,始终伴随一定开销(只是相较而言,但实际上多出的开销对于现在的计算机设备来讲很微小)
什么时候采⽤MVC开发UI逻辑
根据项⽬中成员技术来决定
团队中的人员是否熟知MVC结构,采用MVC结构是否会徒增成本
根据项⽬本⾝规模来决定
如果开发的UI项目过小,仅仅只有几个面板,一两组数据,采用MVC反而会将其结构变得更复杂
根据开发团队规模来决定
团队规模是否比较大,需要采用MVC结构使分工更明确,以便更好地实现协作并行开发,提高团队开发效率
UI结构拓展和思维发散
MVC的不⾜
M于V之间存在着联系,也就是数据和界面之间存在着耦合性,当数据结构改变时会牵扯到界面逻辑随之改变。
在MVC中当需求变化时,需要维护的对象将增加。
修改数据Model可能涉及到View和Controller都要修改,这是因为界面和数据还存在一定耦合。
解决这一问题的方案便是MVX
MVX
MVX是什么
数据和界面是不可缺少的必备内容,我们可以通过改变X元素来优化原本的MVC结构,也就是说改变联系和处理M与V的方式。
MVX有哪些
总结
本质上,MVC是一种组织代码的结构也是一种思想,在学习MVC结构的同时,不能拘泥于MVC结构和设计模式。不要为了框架而框架,需要灵活和理解各种MVX,找到一个适合自己项目的,稳定的,有序的,能满足需求的实现方式。学习和运用框架结构和设计模式的根本目的应该是提高项目的开发能力与开发效率,而非学习框架本身。
真正适合开发的MVC思想⸺PureMVC
PureMVC是什么
基于MVC思想和一些基础设计模式建立的一个1轻量级的应用设计框架;
一个免费的开源框架;
最初是在Flash中执行ActionScript3语言所使用的,现在已经一移植到了几乎所有主流平台(包括u3d)。
PureMVC如何获取
官网:http://puremvc.org/
点击可下载
中文文档
PureMVC基本结构
PureMVC实际上就是基于MVC思想作为核心在外部包裹上设计模式组成的框架。