Unity 3D Framework Designing(1)—— MVVM 模式的设计和实施(Part 1)
初识 MVVM
谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到很好的实现。
MVVM 设计模式顾名思义,通过分离关注点,各司其职。通过 Data Binding 可达到数据的双向绑定,而命令 Command 更是将传统的 Code Behind 事件独立到 ViewModel 中。
MVVM 设计模式在 WPF 中的实现
在WPF中,你会像如下这样去定义一个专门管理视图 View 的 ViewModel:
public class SongViewModel : INotifyPropertyChanged
{
#region Construction
/// Constructs the default instance of a SongViewModel
public SongViewModel()
{
_song = new Song { ArtistName = "Unknown", SongTitle = "Unknown" };
}
#endregion
#region Members
Song _song;
#endregion
#region Properties
public Song Song
{
get
{
return _song;
}
set
{
_song = value;
}
}
public string ArtistName
{
get { return Song.ArtistName; }
set
{
if (Song.ArtistName != value)
{
Song.ArtistName = value;
RaisePropertyChanged("ArtistName");
}
}
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Methods
private void RaisePropertyChanged(string propertyName)
{
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
同时在 View 中你需要使用 Binding 将 ViewModel 的属性绑定和控件的内容相绑定:
<TextBox Content="{Binding ArtistName}" />
值得注意的是,要实现 View 和 ViewModel 双向绑定,我们的 ViewModel 必须实现 INotifyPropertyChanged 接口,由于 WPF Framework 让控件监听了 PropertyChanged 事件,当属性值发生时,触发 PropertyChanged 事件,所以控件就能自动获取到最新的值。反之,当控件的值发生改变时,例如 TextBox 触发 OnTextChanged 事件,自动将最新的值同步到 ViewModel 相应的属性中。
MVP & MVVM
Unity 3D 与 WPF/Sliverlight 不同,它没有提供类似的 Data Binding,也没有像 XAML 一样的视图语法,那么怎样才能在 Unity 3D 中去实现 MVVM 呢?
在 ASP.NET WebForm 时代,那时还没有 ASP.Net MVC 。我们为了让 UI 表现层分离,常常会使用 MVP 设计模式,以下是我在几年前画的一张老图:
MVP 设计模式核心就是,通过定义一个 View,将 UI 抽象出来,它不必关心数据的具体来源,也不必关心点击按钮之后业务逻辑的实现,它只关注 UI 交互。这就是典型的分离关注点。
其实这就是我今天想讲的主题,既然 Unity 3D 没有提供数据绑定,那么我们也可以参考之前 MVP 的设计理念:
将 UI 抽象成独立的一个个 View,将面向 Component 开发转换为面向 View 开发,每一个 View 都有独立的 ViewModel 进行管理,如下所示:
由于 Unity 3D 没有 XAML,也没有 Data Binding 技术,故只能在抽象出来的 View 中去实现类似于 WPF 的 Data Binding,Converter,Command 等。
值得注意的是,MVP 设计模式中数据的绑定是通过将具体的 View 实例传递到 Presenter 中完成的,而 MVVM 是以数据改变引发的事件中完成数据更新的。
MVVM 设计模式在 Unity 3D 中的设计与实现
再回顾一下 WPF 中 ViewModel 的写法。 ViewModel 提供了 View 需要的数据,并且 ViewModel 实现 INotifyPropertyChanged 接口 ,当数据更改时,触发了 PropertyChanged 事件,由于控件也监听了此事件,在事件的响应函数里实现数据的更新。
了解了之后,我们要考虑怎样在 Unity 3D 中去实现它。假设我们需要完成如下的一个功能,并且是使用 MVVM 设计思想实现:
首先,我们要定义一个 View,这个 View 是对 UI 元素的一个抽象,到底要抽象哪些 UI 元素呢?就这个例子而言,InputField,Label,Slider,Toggle,Button 是需要被抽象出来的。
public class SetupView
{
public InputField nameInputField;
public Text nameMessageText;
public InputField jobInputField;
public Text jobMessageText;
public InputField atkInputField;
public Text atkMessageText;
public Slider successRateSlider;
public Text successRateMessageText;
public Toggle joinToggle;
public Button joinInButton;
public Button waitButton;
}
可以看到,这是一个很简单的 View。接着我们需要定义一个专门用来管理 View 的 ViewModel,它以属性的形式提供数据,以方法的形式提供行为。
值得注意的是,ViewModel 中的属性不是特殊的属性,它必须具备当数据更改时通知订阅者这个功能,怎么通知订阅者?当然是事件,故我把此属性称为 BindableProperty 属性。
public class BindableProperty<T>
{
public delegate void ValueChangedHandler(T oldValue, T newValue);
public ValueChangedHandler OnValueChanged;
private T _value;
public T Value
{
get
{
return _value;
}
set
{
if (!object.Equals(_value, value))
{
T old = _value;
_value = value;
ValueChanged(old, _value);
}
}
}
private void ValueChanged(T oldValue, T newValue)
{
if (OnValueChanged != null)
{
OnValueChanged(oldValue, newValue);
}
}
public override string ToString()
{
return (Value != null ? Value.ToString() : "null");
}
}
接着,我们再定义一个 ViewModel,它为 View 提供了数据和行为:
public class SetupViewModel : ViewModel
{
public BindableProperty<string> Name = new BindableProperty<string>();
public BindableProperty<string> Job = new BindableProperty<string>();
public BindableProperty<int> ATK = new BindableProperty<int>();
public BindableProperty<float> SuccessRate = new BindableProperty<float>();
public BindableProperty<State> State = new BindableProperty<State>();
}
有了 View 与 ViewModel 之后,我们需要考虑:
- 怎样为 View 指定一个 ViewModel
- 当 ViewModel 属性值改变时,怎样订阅触发的 OnValueChanged 事件,从而达到 View 的数据更新
基于以上两点,我们可以定义一个通用的 View,将它命名为 UnityGuiView :
public interface IView
{
ViewModel BindingContext { get; set; }
}
public class UnityGuiView:MonoBehaviour,IView
{
public readonly BindableProperty<ViewModel> ViewModelProperty = new BindableProperty<ViewModel>();
public ViewModel BindingContext
{
get { return ViewModelProperty.Value; }
set { ViewModelProperty.Value = value; }
}
protected virtual void OnBindingContextChanged(ViewModel oldViewModel, ViewModel newViewModel)
{
}
public UnityGuiView()
{
this.ViewModelProperty.OnValueChanged += OnBindingContextChanged;
}
}
上述代码中,提供一个 BindingContext 上下文属性,类似于 WPF 中的 DataContext。 BindingContext 属性我们不能将它视为一个简单的属性 ,它是上述定义过的 BindableProperty 类型属性。那么当为一个 View 的 BindingContext 指定 ViewModel 实例时,初始化时,势必会触发 OnValueChanged 事件。
在响应函数 OnBindingContextChanged 中 ,我们可以在此对 ViewModel 中事件进行监听,从而达到数据的更新。当然这是一个虚方法,你需要在子类 View 中 Override。
所以修改定义过的 SetupView,继承自 UnityGuiView:
public class SetupView:UnityGuiView
{
...省略部分代码
public SetupViewModel ViewModel { get { return (SetupViewModel)BindingContext; } }
protected override void OnBindingContextChanged(ViewModel oldViewModel, ViewModel newViewModel)
{
base.OnBindingContextChanged(oldViewModel, newViewModel);
SetupViewModel oldVm = oldViewModel as SetupViewModel;
if (oldVm != null)
{
oldVm.Name.OnValueChanged -= NameValueChanged;
...
}
if (ViewModel!=null)
{
ViewModel.Name.OnValueChanged += NameValueChanged;
...
}
UpdateControls();
}
private void NameValueChanged(string oldvalue, string newvalue)
{
nameMessageText.text = newvalue.ToString();
}
}
由于子类 Override 了 OnBindingContextChanged 方法,故它会对 ViewModel 的属性值改变事件进行监听,当触发时,将最新的数据同步到 UI 中。
同理,考虑到双向绑定,你也可以在 View 中定义一个 OnTextBoxValueChanged 响应函数,当文本框中的数据改变时,在响应函数中就数据同步到 ViewModel 中。在这我就不累述了。
最后,在 Unity 3D 中将 SetupView 附加到 相应的 GameObject上:
最后在摄像机上加一段脚本,很简单,传入 SetupView 对象并为其绑定 ViewModel:
public SetupView setupView;
void Start()
{
//绑定上下文
setupView.BindingContext=new SetupViewModel();
}
小结
这是一个非常简单的 MVVM 框架,也证明了在 Unity 3D 中实现 MVVM 设计模式的可能性。
源代码托管在Github上,点击此了解
Unity 3D Framework Designing(1)—— MVVM 模式的设计和实施(Part 1)的更多相关文章
- Unity 3D Framework Designing(1)—— MVVM 模式的设计和实施(Part 2)
MVVM回顾 经过上一篇文章的介绍,相信你对 MVVM的设计思想有所了解.MVVM的核心思想就是解耦,View与ViewModel应该感受不到彼此的存在.View只关心怎样渲染,而ViewModel只 ...
- Unity 3D Framework Designing(3)——构建View和ViewModel的生命周期
> 对于一个View而言,本质上是一个MonoBehaviour.它本身就具备生命周期这个概念,比如,Awake,Start,Update,OnDestory等.这些是非常好的方法,可以让开发者 ...
- Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 1)
初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...
- Unity 3D Framework Designing(4)——设计可复用的SubView和SubViewModel(Part 1)
『可复用』这个词相信大家都熟悉,通过『可复用』的组件,可以大大提高软件开发效率. 值得注意的事,当我们设计一个可复用的面向对象组件时,需要保证其独立性,也就是我们熟知的『高内聚,低耦合』原则. 组件化 ...
- Unity 3D Framework Designing(7)——IoC工厂理念先行
一谈到 『IoC』,有经验的程序员马上会联想到控制反转,将创建对象的责任反转给工厂.IoC是依赖注入 『DI』 的核心,大名鼎鼎的Spring框架就是一个非常卓越的的控制反转.依赖注入框架.遗憾的是, ...
- Unity 3D Framework Designing(9)——构建统一的 Repository
谈到 『Repository』 仓储模式,第一映像就是封装了对数据的访问和持久化.Repository 模式的理念核心是定义了一个规范,即接口『Interface』,在这个规范里面定义了访问以及持久化 ...
- Unity 3D Framework Designing(6)——设计动态数据集合ObservableList
什么是 『动态数据集合』 ?简而言之,就是当集合添加.删除项目或者重置时,能提供一种通知机制,告诉UI动态更新界面.有经验的程序员脑海里迸出的第一个词就是 ObservableCollection.没 ...
- Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 2)
MVVM回顾 经过上一篇文章的介绍,相信你对MVVM的设计思想有所了解.MVVM的核心思想就是解耦,View与ViewModel应该感受不到彼此的存在. View只关心怎样渲染,而ViewModel只 ...
- Unity 3D Framework Designing(2)——使用中介者模式解耦ViewModel之间通信
当你开发一个客户端应用程序的时候,往往一个单页会包含很多子模块,在不同的平台下,这些子模块又被叫成子View(视图),或者子Component(组件).越是复杂的页面,被切割出来的子模块就越多,子模块 ...
随机推荐
- CSS3 3D变形效果
CSS3 3D变形效果 CSS3 transform3D变形 transform的含义是:改变,使-变形:转换 三维变换使用基于二维变换的相同属性,如果您熟悉二维变换,你们发现3D变形的功能和2D变换 ...
- Android SQLite总结
SQLite在Android一般应用中还是比较常用,早期的时候碰到过不少坑,其中最烦的就是多线程并发读写问题,今天正好整理一下,做个笔记,也欢迎指正.讨论和补充. 一.查询优化 1.wal模式 开启w ...
- gevent拾遗
在前文已经介绍过了gevent的调度流程,本文介绍gevent一些重要的模块,包括Timeout,Event\AsynResult, Semphore, socket patch,这些模块都涉及当 ...
- 基于Ubuntu14.04-LTS下安装docker
1.sudo apt-get update --更新系统源 2.sudo apt-get install docker.io 3.将docker库的公钥中加入到本地apt中 sudo apt-key ...
- STM32F4的FPU单元讲解
搞STM32F407单片机的时候 看见的关于STM32F4系列的FPU 单元讲解 比较精彩的博客 于是特意转载 和大家分享 转自:http://blog.renren.com/blog/256814 ...
- [看图说话] 基于Spark UI性能优化与调试——初级篇
Spark有几种部署的模式,单机版.集群版等等,平时单机版在数据量不大的时候可以跟传统的java程序一样进行断电调试.但是在集群上调试就比较麻烦了...远程断点不太方便,只能通过Log的形式,进行分析 ...
- map,set,list等集合解析以及HashMap,LinkedHashMap,TreeMap等该选谁的的区别
前言: 今天在整理一些资料时,想起了map,set,list等集合,于是就做些笔记,提供给大家学习参考以及自己日后回顾. Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允 ...
- nginx配置参数详解
配置参数详解 user nginx nginx ; Nginx用户及组:用户 组.window下不指定 worker_processes 8; 工作进程:数目.根据硬件调整,通常等于CPU数量或者2倍 ...
- JS中的onclick事件
原文链接:https://segmentfault.com/q/1010000007955542?_ea=1503986 我自己做了一下测试. 这个是在html里面直接绑定onclick事件,我打印了 ...
- select函数的用法
首先介绍阻塞方式与非阻塞方式: 阻塞方式(block),就是进程或是线程执行到这些函数时必须等待某个事件的发生.如果事件没有发生,进程或线程就被阻塞,函数不能立即返回. 非阻塞方式(non-block ...