Unity——基于MVC的UI框架

前言

今天来学习一下MVC框架思想在Unity项目中的应用


MVC框架

概念

MVC全名是Model View Controller,是模型(Model)-视图(View)-控制器(Controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

Model(模型) 是应用程序中用于处理应用程序数据逻辑的部分。
通常模型对象负责在数据库中存取数据。
View(视图) 是应用程序中处理数据显示的部分。
通常视图是依据模型数据创建的。
Controller(控制器) 是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。比如一批统计数据可以分别用柱状图、饼图来表示。C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。

模型-视图-控制器(MVC)是Xerox PARC在二十世纪八十年代为编程语言Smalltalk-80发明的一种软件设计模式,已被广泛使用。后来被推荐为Oracle旗下Sun公司Java EE平台的设计模式,并且受到越来越多的使用ColdFusion和PHP的开发者的欢迎。模型-视图-控制器模式是一个有用的工具箱,它有很多好处,但也有一些缺点。

MVC与设计模式的关系

MVC是一种设计模式,但是却不在Gof总结过的23种设计模式中,所以确切说MVC不是一个设计模式,而是多种设计模式的组合,而不仅仅只是一个设计模式。
组成MVC的三个模式分别是组合模式、策咯模式、观察者模式,MVC在软件开发中发挥的威力,最终离不开这三个模式的默契配合。 那些崇尚设计模式无用论的程序员,请了解只要你们使用MVC,就离不开设计模式。
组合模式只在视图层活动, 视图层的实现用的就是组合模式,当然,这里指的实现是底层的实现,是由编程框架厂商做的事情,用不着普通程序员插手。

组合模式的类层次结构是树状的, 而我们做Web时视图层是html页面,html的结构不正是树状的吗,这其实就是一个组合模式的应用,只是浏览器厂商已经把界面相关的工作帮我们做掉了,但它确确实实是我们应用MVC的其中一部分,只是我们感觉不到罢了,这也是我们觉得View是实现起来最简单最没有歧义的一层的原因。

除网页以外的其他用户界面程序,如WPF、Android、ASP.NET等等都是使用树状结构来组织界面控件对象的,因为组合模式就是从界面设计的通用解决方案总提炼出来的。所以与其说MVC选择了组合模式,还不如说组合模式是必定会存在MVC中的,因为只要涉及到用户界面,组合模式就必定存。事实上即使不理解组合模式,也不影响程序员正确的使用MVC,组合模式本就存在于程序员接触不到的位置。

然而,观察者模式和策略模式就显得比较重要,是实实在在MVC中接触的到的部分。

观察者模式有两部分组成,被观察的对象和观察者,观察者也被称为监听者。对应到MVC中,Model是被观察的对象,View是观察者,Model层一旦发生变化,View层即被通知更新。View层和Model层互相之间是持有引用的。 我们在开发Web MVC程序时,因为视图层的html和Model层的业务逻辑之间隔了一个http,所以不能显示的进行关联,但是他们观察者和收听者的关系却没有改变。当View通过http提交数据给服务器,服务器上的Model接受到数据执行某些操作,再通过http响应将结果回送给View,View(浏览器)接受到数据更新界面,这不正是一个接受到通知并执行更新的行为吗,是观察者模式的另一种表现形式。

但是,脱离Web,当通过代码去纯粹的表示一个MVC结构的时候,View和Model间无疑是观察者和被观察的关系,是以观察者模式为理论基础的。即使在Web中因为http壁垒的原因导致真正的实现有点走样,但是原理核心和思路哲学却是不变的。

最后是策略模式。策略模式是View和Controller之间的关系,Controller是View的一个策略,Controller对于View是可替换的, View和Controller的关系是一对多,在实际的开发场景中,也经常会碰到一个View被多个Controller引用,这即使策咯模式的一种体现,只是不那么直观而已。

总结一下,关于MVC各层之间关系所对应的设计模式

View层,单独实现了组合模式

Model层和View层,实现了观察者模式

View层和Controller层,实现了策咯模式

MVC就是将这三个设计模式在一起用了,将这三个设计模式弄明白,MVC将毫无神秘感可言。如果不了解这三个设计模式去学习MVC,那不管怎么学总归是一知半解,用的时候也难免不会出想问题。


Unity中的MVC框架

预览

借助网上找的一张官剑铭老师的图片,很好的概括了Unity中的MVC

目录结构

我们以一个武器商店为例子,下面是我的目录结构:

文件详解

其中:UIRoot是我们的UI框架预制体:

StoreWindow是一个Panel的预制体,这个Panel就是我们的商店Panel,即本篇示例的主要面板:

另外,目录SingleIns中是我们两个单例我们的模板类,一个是继承于MonoBehavior的:

public class Singleton<T> where T:new()//T 约束 只能是class类型的
{static T instance;public static T Instance{get {if (instance==null){instance = new T();}return instance;}}
}
public class MonoSingleton<T> : MonoBehaviour where T:MonoBehaviour
{static T instance;public static T Instance{get {if (MonoSingletonObject.go==null){MonoSingletonObject.go = new GameObject("MonoSingletonObject");DontDestroyOnLoad(MonoSingletonObject.go);}if (MonoSingletonObject.go!=null&& instance==null){instance= MonoSingletonObject.go.AddComponent<T>();}return instance;}}//有时候  有的组件场景切换的时候回收的public static bool destroyOnLoad = false;//添加场景切换时候的事件public void AddSceneChangedEvent() {//SceneManager自带属性activeSceneChanged,是一个委托,可以添加绑定方法SceneManager.activeSceneChanged += OnSceneChanged;}private void OnSceneChanged(Scene arg0, Scene arg1){if (destroyOnLoad==true){if (instance!=null){DestroyImmediate(instance);//立即销毁Debug.Log(instance == null);}}}
}//缓存一个游戏物体
public class MonoSingletonObject
{public static GameObject go;
}

MVCLibrary目录下有脚本Type.cs文件,内部定义了两个枚举,WindowType类对应的每一个值就是每一个UI界面;ScenesType是不同的场景,用来做预加载处理:

/// <summary>
/// 窗体类型
/// </summary>
public enum WindowType
{LoginWindow,StoreWindow,TipsWindow,
}
/// <summary>
/// 场景类型,目的:根据提供场景类型进行预加载
/// </summary>
public enum ScenesType
{None,Login,Battle,
}

UIRoot.cs用来管理UIRoot预制体,管理UIRoot中的三层面板,主要方法SetParent来给Panel设置所属父面板:

public class UIRoot
{//UIRoot本尊static Transform transfowm;//回收的窗体:回收池static Transform recyclePool;//前台显示/工作的窗体static Transform workstation;//提示类型的窗体static Transform noticestation;static bool isInint = false;public static void Init() {if (transfowm == null) {GameObject obj=Resources.Load<GameObject>("UI/UIRoot");transfowm = GameObject.Instantiate(obj).transform;}if (recyclePool == null){recyclePool = transfowm.Find("recyclePool");}if (workstation == null){workstation = transfowm.Find("workstation");}if (noticestation == null){noticestation = transfowm.Find("noticestation");}isInint = true;}//对窗体的父panel设置public static void SetParent(Transform window,bool isOpen,bool isTipsWindow=false) {if (!isInint) {   //没有初始化Init();}if (isOpen)     //是一个开启的面板{ if (isTipsWindow)  //是一个提示面板{//窗体父Panel是noticestation//第二个参数意思是“是否启用世界坐标”window.SetParent(noticestation, false);}else{//窗体父Panel是workstationwindow.SetParent(workstation, false);}}else {//窗体父Panel是recyclePoolwindow.SetParent(recyclePool, false);}}
}

WindowManager.cs是对窗口的总管理类,这个单例类中定义了对Panel的打开、关闭等方法,来对各个Panel控制。

View.cs目录下的BaseWindow.cs是各个Panel管理类的总父类:

namespace Game.View
{public class BaseWindow{//窗体protected Transform transform;//资源名称protected string resName;//是否常驻protected bool resident;//当前是否可见protected bool visible = false;//窗体类型protected WindowType selfType;//场景类型protected ScenesType scenesType;//UI控件protected Button[] buttonList;protected Text[] textList;//需要给子类提供的接口//初始化protected virtual void Awake(){//参数为true表示包括隐藏的物体buttonList = transform.GetComponentsInChildren<Button>(true);textList = transform.GetComponentsInChildren<Text>(true);//注册UI事件(细节由子类实现)RegisterUIEvent();}//UI事件的注册protected virtual void RegisterUIEvent() { }//添加监听游戏事件protected virtual void OnAddListener() { }//移除游戏事件protected virtual void OnRemoveListener() { }//每次打开protected virtual void OnEnable() { }//每次关闭protected virtual void OnDisable() { }//每帧更新public virtual void Update(float deltaTime) { }//-----------针对WindowManager的方法 (被WindowManager调用)public void Open(){if (transform == null){if (Create()){Awake();  //初始化}}if (!transform.gameObject.activeSelf){UIRoot.SetParent(transform, true, selfType == WindowType.TipsWindow);transform.gameObject.SetActive(true);visible = true;OnEnable(); //调用激活时的事件OnAddListener();  //添加事件}}public void Close(bool isForceClose = false){if (transform.gameObject.activeSelf){OnRemoveListener();  //移除事件的监控OnDisable();  //隐藏的事件if (!isForceClose)  //非强制{if (resident){transform.gameObject.SetActive(false);//将窗口从work区域放到recycle区域UIRoot.SetParent(transform, false, false);}else{GameObject.Destroy(transform.gameObject);transform = null;}}else{GameObject.Destroy(transform.gameObject);transform = null;}}//不可见的状态visible = false;}public void PreLoad(){if (transform == null){if (Create()){}}}//获取场景类型public ScenesType GetScenesType(){return scenesType;}//获取窗口类型public WindowType GetWindowType(){return selfType;}//获取根节点public Transform GetRoot(){return transform;}//是否可见public bool IsVisible(){return visible;}//是否常驻public bool IsResident(){return resident;}//--------内部---------public bool Create(){//资源名称为空,则无法创建if (string.IsNullOrEmpty(resName)){return false;}//窗体引用为空,则创建实例if (transform == null){//根据资源名称加载物体GameObject obj = Resources.Load<GameObject>(resName);if (obj == null){Debug.LogError($"未找到UI预制件{selfType}");return false;}transform = GameObject.Instantiate(obj).transform;transform.gameObject.SetActive(false);UIRoot.SetParent(transform, false, selfType == WindowType.TipsWindow);return true;}return true;}}
}

另一个View目录下的文件是我们的商店实例StoreWindow的管理类,WindowManager调用该类的方法Open(继承于BaseWindow的方法)来加载预置体StoreWindow,这个类还实现这个Panel的具体的方法:

namespace Game.View
{public class StoreWindow : BaseWindow{public StoreWindow(){resName = "UI/Window/StoreWindow";resident = true;visible = false;selfType = WindowType.StoreWindow;scenesType = ScenesType.Login;}protected override void Awake(){base.Awake();}protected override void OnAddListener(){base.OnAddListener();}protected override void OnDisable(){base.OnDisable();}protected override void OnEnable(){base.OnEnable();}protected override void OnRemoveListener(){base.OnRemoveListener();}protected override void RegisterUIEvent(){base.RegisterUIEvent();foreach (Button btn in buttonList){switch (btn.name){case "BuyButton":btn.onClick.AddListener(()=> {OnBuyButton(btn);});break;}}}public override void Update(float deltaTime){base.Update(deltaTime);//每帧监听,按下C关闭此窗口if (Input.GetKeyDown(KeyCode.C)){Close();}}private void OnBuyButton(Button btn){Debug.Log("点击了BuyButton");//通过Control修改Modelif (StoreCtrl.Instance.Sell(1)){int count = StoreCtrl.Instance.GetProp(1).Count;btn.transform.parent.Find("Tips").GetComponent<Text>().text = "已购买倚天剑,倚天剑剩余" + count;}elsebtn.transform.parent.Find("Tips").GetComponent<Text>().text = "购买失败";}}
}

上面的View目录就是这样,主要是对视图的管理,我们再来看Model目录下的StoreModel.cs,这个文件就是StoreWindow对应的数据模块:

namespace Game.Model
{public class StoreModel : Singleton<StoreModel>{private Dictionary<int, Prop> propDic = new Dictionary<int, Prop>();private Prop yiTian = new Prop(1,"倚天剑",7,300);public StoreModel() {//给商店添加商品Add(yiTian);}//售卖public bool Sell(int propId) {//商品数量大于0且玩家金币大于价格,这里金币我定死了301if (propDic[propId].Count > 0 && 301>propDic[propId].Price) {//商店装备数减一propDic[propId].Count--;//玩家的得到该装备return true;}return false;}public void Add(Prop prop){if (!propDic.ContainsKey(prop.Id)){propDic[prop.Id] = prop;}}public Prop GetProp(int id){return propDic[id];}}
}//道具类
public class Prop {private int id;private string name;private int count;private double price;public int Id {get { return id; }set { id=value; }}public string Name{get { return name; }set { name = value; }}public int Count{get { return count; }set { count = value; }}public double Price{get { return price; }}public Prop(int id, string name,int count,double price) {this.id = id;this.name = name;this.count = count;this.price = price;}
}

而最后,我们的View不能直接和Model交互,必须要利用中间人——Controller,下面就是StoreCtrl.cs的内容,连接了视图与数据模块,提供的方法都是对于View的接口,由View下的各个Panel的管理类控制:

namespace Game.Ctrl
{public class StoreCtrl : Singleton<StoreCtrl>{//给Store View分配的接口,用来给Store Model添加道具proppublic void SaveProp(Prop prop){StoreModel.Instance.Add(prop);}public bool Sell(int id) {return StoreModel.Instance.Sell(id);}public Prop GetProp(int id){return StoreModel.Instance.GetProp(id);}}
}

效果


商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

Unity——基于MVC的UI框架相关推荐

  1. 【PC端vue ui框架学习】vue项目如何使用基于vue的UI框架Element

    看了下iView之后,顺便看了看同样基于vuejs的ui框架Element,那么在vue项目中应该如何使用Element呢?以下做简单的记录. 官网定义:Element,一套为开发者.设计师和产品经理 ...

  2. 【PC端vue ui框架学习】vue项目如何使用基于vue的UI框架iview

    今晚看了一下基于vuejs的ui框架iview,感觉UI挺好看的,那么在vue项目中应该如何使用iview呢?以下做简单的记录. 首先安装iview: $ npm install iview --sa ...

  3. Unity 基于EventTrigger的UI事件系统

    一.主要思路 其实主要原理就是通过继承EventTrigger并重写相应的函数实现的,只是对其做了一个封装,更方便使用了: 当对某个GameObject使用UIEventManager.Get()函数 ...

  4. 基于vue的UI框架集锦(移动端+pc端)

    1. vonic 一个基于 vue.js 和 ionic 样式的 UI 框架,用于快速构建移动端单页应用,很简约,是我喜欢的风格 star 2.3k 中文文档 在线预览 2.vux 基于WeUI和Vu ...

  5. 基于Ant Design UI框架的React项目

    概述 这款基于React开发的UI框架,界面非常简洁美观,在这篇文章中我主要为大家介绍一下如何用Ant开始搭建React项目 详细 代码下载:http://www.demodashi.com/demo ...

  6. unity 前端场景搭建UI框架的设计

    在 Unity 前端场景中搭建 UI 框架时,可以采用以下设计方案: 基础组件库:设计一套基础组件库,包括常用的 UI 控件,如文本.按钮.图像等,组件库的设计应该尽量简单易用,方便开发者快速搭建 U ...

  7. 基于Vue前端UI框架比较

    Vue3相对于vue2的优缺点 优点: 性能提升,主要体现在打包体积(减少了40%左右),渲染速度(快了55%),更新速度(100%)及内存使用(减少了50%)几方面. 由于增加了compositio ...

  8. 【移动端vue ui框架学习】vue项目如何使用基于vue的UI框架mint ui

    看了下饿了么团队开发的PC端vue ui框架Element ui之后,顺便看了看同团队开发的移动端的vue ui框架mint ui,那么在vue项目中应该如何使用mint ui呢?以下做简单的记录. ...

  9. 基于MVC模式Struts框架研究

    不做web开发多年了, 可偶尔去面试的时候, 还是会问道Struts实现mvc的细节. 很多东西都模糊了. 在网上找了下, 觉得这篇不错, 记下来防止以后用的着. ' 分布式企业应用软件结构复杂.涉及 ...

最新文章

  1. Test Writer For Windows Server 2003 R2
  2. 电脑硬件检测_【学无止境】电脑硬件维修测试学习资料(附送各类PC检修资源)...
  3. 设计网站导航|强大且智能的设计师导航
  4. Protobuf C++类中成员函数GetCachedSize()与ByteSize()的区别
  5. 全球高精度5米 12.5米 30米地形(DEM)高程数据(tif格式)下载
  6. 2022Java后端之美团笔试题
  7. React-Native强制关闭软键盘
  8. JPEG图像存储格式
  9. 5G加速实现沉浸式体验
  10. 科创板拟上市企业申联生物和传音控股已提交注册
  11. 深大uooc学术道德与学术规范教育第十章
  12. MySQL及达梦数据库数字金额转人民币大写
  13. [精简]什么是和弦?
  14. 第七届 Sky Hackathon 笔记集合贴
  15. 消息队列系列之分布式消息队列Kafka
  16. 微信登陆接收不到onResp回调
  17. (深度学习论文精读总结)You Only Look Once: Unified, Real-Time Object Detection
  18. 2019微生物组—宏基因组分析技术研讨会第四期
  19. 知识点记录--x86与arm
  20. 亚马逊卖家怎样让茶叶乘风破浪,远销海外?-跨境创业找众光

热门文章

  1. 使用“黑魔法”优雅的修改第三方依赖包
  2. WINDOWS系统机器学习基础环境安装教程
  3. js中的对象、函数定义以及形参和实参
  4. c++ 字符串相关函数
  5. 卷积矩阵及其运算实例
  6. M3U8在线MP4格式
  7. C++打卡20-【排序模板】快速排序
  8. 微信公众号服务器配置详解一览
  9. matlab典型相关函数,典型相关分析(Matlab实现函数)
  10. 这些年,建立的做人做事的准则