Unity应用架构设计(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上,点击此了解

本博客为 木宛城主原创,基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 木宛城主(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。

本文转自木宛城主博客园博客,原文链接:http://www.cnblogs.com/OceanEyes/p/unity3d_framework_designing_get_started_with_mvvm_part1.html,如需转载请自行联系原作者
目录
相关文章
|
29天前
|
运维 Cloud Native 持续交付
深入理解云原生架构及其在现代企业中的应用
随着数字化转型的浪潮席卷全球,企业正面临着前所未有的挑战与机遇。云计算技术的迅猛发展,特别是云原生架构的兴起,正在重塑企业的IT基础设施和软件开发模式。本文将深入探讨云原生的核心概念、关键技术以及如何在企业中实施云原生策略,以实现更高效的资源利用和更快的市场响应速度。通过分析云原生架构的优势和面临的挑战,我们将揭示它如何助力企业在激烈的市场竞争中保持领先地位。
|
6天前
|
容灾 网络协议 数据库
云卓越架构:云上网络稳定性建设和应用稳定性治理最佳实践
本文介绍了云上网络稳定性体系建设的关键内容,包括面向失败的架构设计、可观测性与应急恢复、客户案例及阿里巴巴的核心电商架构演进。首先强调了网络稳定性的挑战及其应对策略,如责任共担模型和冗余设计。接着详细探讨了多可用区部署、弹性架构规划及跨地域容灾设计的最佳实践,特别是阿里云的产品和技术如何助力实现高可用性和快速故障恢复。最后通过具体案例展示了秒级故障转移的效果,以及同城多活架构下的实际应用。这些措施共同确保了业务在面对网络故障时的持续稳定运行。
|
2月前
|
Cloud Native 安全 持续交付
深入理解微服务架构及其在现代软件开发中的应用
深入理解微服务架构及其在现代软件开发中的应用
51 4
|
2月前
|
监控 持续交付 API
深入理解微服务架构及其在现代应用开发中的应用
深入理解微服务架构及其在现代应用开发中的应用
30 4
|
2月前
|
运维 Kubernetes Docker
深入理解容器化技术及其在微服务架构中的应用
深入理解容器化技术及其在微服务架构中的应用
65 1
|
5月前
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
239 6
|
5月前
|
图形学 缓存 算法
掌握这五大绝招,让您的Unity游戏瞬间加载完毕,从此告别漫长等待,大幅提升玩家首次体验的满意度与留存率!
【8月更文挑战第31天】游戏的加载时间是影响玩家初次体验的关键因素,特别是在移动设备上。本文介绍了几种常见的Unity游戏加载优化方法,包括资源的预加载与异步加载、使用AssetBundles管理动态资源、纹理和模型优化、合理利用缓存系统以及脚本优化。通过具体示例代码展示了如何实现异步加载场景,并提出了针对不同资源的优化策略。综合运用这些技术可以显著缩短加载时间,提升玩家满意度。
342 5
|
4月前
|
测试技术 C# 图形学
掌握Unity调试与测试的终极指南:从内置调试工具到自动化测试框架,全方位保障游戏品质不踩坑,打造流畅游戏体验的必备技能大揭秘!
【9月更文挑战第1天】在开发游戏时,Unity 引擎让创意变为现实。但软件开发中难免遇到 Bug,若不解决,将严重影响用户体验。调试与测试成为确保游戏质量的最后一道防线。本文介绍如何利用 Unity 的调试工具高效排查问题,并通过 Profiler 分析性能瓶颈。此外,Unity Test Framework 支持自动化测试,提高开发效率。结合单元测试与集成测试,确保游戏逻辑正确无误。对于在线游戏,还需进行压力测试以验证服务器稳定性。总之,调试与测试贯穿游戏开发全流程,确保最终作品既好玩又稳定。
186 4
|
4月前
|
前端开发 图形学 开发者
【独家揭秘】那些让你的游戏瞬间鲜活起来的Unity UI动画技巧:从零开始打造动态按钮,提升玩家交互体验的绝招大公开!
【9月更文挑战第1天】在游戏开发领域,Unity 是最受欢迎的游戏引擎之一,其强大的跨平台发布能力和丰富的功能集让开发者能够迅速打造出高质量的游戏。优秀的 UI 设计对于游戏至关重要,尤其是在手游市场,出色的 UI 能给玩家留下深刻的第一印象。Unity 的 UGUI 系统提供了一整套解决方案,包括 Canvas、Image 和 Button 等组件,支持添加各种动画效果。
202 3
|
4月前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
233 3

热门文章

最新文章