談起 MVVM 設計模式,可能第一映像你會想到 WPF/Sliverlight,他們提供了的數據綁定(Data Binding),命令(Command)等功能,這讓 MVVM 模式獲得很好的實現。MVVM 設計模式顧名思義,經過分離關注點,各司其職。經過 Data Binding
可達到數據的雙向綁定,而命令 Command
更是將傳統的 Code Behind 事件獨立到 ViewModel 中。
git
在WPF中,你會像以下這樣去定義一個專門管理視圖 View 的 ViewModel:github
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 相應的屬性中。框架
Unity與 WPF/Sliverlight 不一樣,它沒有提供相似的 Data Binding,也沒有像 XAML 同樣的視圖語法,那麼怎樣才能在 Unity 3D 中去實現 MVVM 呢?mvvm
在 ASP.NET WebForm 時代,那時尚未 ASP.Net MVC 。咱們爲了讓 UI 表現層分離,經常會使用 MVP 設計模式,如下是我在幾年前畫的一張老圖:ide
MVP 設計模式核心就是,經過定義一個 View,將 UI 抽象出來,它沒必要關心數據的具體來源,也沒必要關心點擊按鈕以後業務邏輯的實現,它只關注 UI 交互。這就是典型的分離關注點。函數
其實這就是我今天想講的主題,既然 Unity 3D 沒有提供數據綁定,那麼咱們也能夠參考以前 MVP 的設計理念:ui
將 UI 抽象成獨立的一個個 View,將面向 Component 開發轉換爲面向 View 開發,每個 View 都有獨立的 ViewModel 進行管理,以下所示:this
因爲 Unity沒有 XAML,也沒有 Data Binding 技術,故只能在抽象出來的 View 中去實現相似於 WPF 的 Data Binding,Converter,Command 等。spa
值得注意的是,MVP 設計模式中數據的綁定是經過將具體的 View 實例傳遞到 Presenter 中完成的,而 MVVM 是以數據改變引起的事件中完成數據更新的。
再回顧一下 WPF 中 ViewModel 的寫法。 ViewModel 提供了 View 須要的數據,而且 ViewModel 實現 INotifyPropertyChanged 接口 ,當數據更改時,觸發了 PropertyChanged 事件,因爲控件也監聽了此事件,在事件的響應函數裏實現數據的更新。
瞭解了以後,咱們要考慮怎樣在 Unity中去實現它。假設咱們須要完成以下的一個功能,而且是使用 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,將它命名爲 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;
}
}複製代碼
因此修改定義過的 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中實現 MVVM 設計模式的可能性。
源代碼託管在Github上,點擊此瞭解
歡迎關注個人公衆號: