MVC模式在UI里的应用

In a similar way to other parts of a game, user interfaces usually go through several iterations until we get the user experience right; it is vital that we can iterate over different ideas as fast as possible. Using a MVC pattern to organize the view is a proven method to decouple how things are shown in the screen from the logic that controls how that view behaves upon interaction with the user, and how it should change depending on the application state. This organizational pattern provides several benefits:

  • We can change how a UI widget looks like without modifying one single line of code
  • We can share logic code in different controls and create complex views easily.
  • Last but not least, we could change the underlying implementation of the view with relatively little effort. We are currently using the NGUI library to build our interfaces, but we are looking forward to work with the upcoming UnityGUI.

Code Example

We’ve provided an example Unity3D project available at our public git repository. Keep in mind that we cannot provide the code for the NGUI library, so you’ll need add your own copy to the Assets/Libs folder in the project if you want to compile and run it. We will refer to this example through the rest of the post, so feel free to download the code an check it while you read.

General overview

Here’s a general diagram that shows how the different parts of this pattern interact in a high level view. A detailed explanation for each component follows.

Model

The traditional MVC Model we all know and love:

  • Holds no view data nor view state data.
  • Is accessed by the Controller and other Models only
  • Will trigger events to notify external system of changes.

The Model is implemented with Plain Old C# Objects (POCOs)

Here‘s a link to the PlayerModel class representing Player data. The PlayerModel class stores several data for the player, including HitPoints, XP and its Level, accessible using two properties. We can add XP points, an action that raises an XPGainedevent from the model. When we trigger enough experience to advance a level, the Level property is updated and raises aLevelUp event.

View

Conceptually the View is just something that will be rendered in the screen. The responsibilities of a view are:

  • Handle references to elements needed for drawing (Textures, FXs, etc)
  • Perform Animations
  • Layouts
  • Receive User Input

In this particular case the View is implemented using NGUI, so it is just a prefab inside the Unity project. But that’s an implementation detail that we need to decouple, more on this later. Getting more academic this is a case of a Passive View. This means that the view knows nothing about other parts of the project, either data or logic. Thus, some other code must explicitly tell the view what to display, what animation to play, etc.

Controller

The Controller is the link between the Model and the View. It holds the state of the View and updates it depending on that state and on external events:

  • Holds the application state needed for that view
  • Controls view flow
  • Shows/hides/activates/deactivates/updates the view or parts of the view depending on the state. For example the controller can temporarily disable the special attack button because the player is in the cooldown time, after that time the controller re-enables it.
  • Load/Instantiate needed assets, for example to show particles, change sprites dynamically, etc
  • Handles events either triggered by the player in the View (e.g. the player touched a button) or triggered by the Model (e.g. the player has gained XP and that triggered a Level Up event so the controller updates the level Number in the view)

These are the three basic elements that defines a MVC pattern. However, in this case we felt we need to add another indirection layer to further decouple the specific NGUI View implementation from our code. We call that part:

ViewPresenter

ViewPresenter sits between the View and the Controller and it is an interface that will expose common operations that are generic to a View, no matter how is implemented internally.

For example, a button to be used in a game can have the following functionality:

  • Set the text for the button’s label
  • Change the background image
  • Enable / disable user input
  • Notify when an user clicks the button

These are implementation-independent operations that you can find in every button in any UI toolkit. It is implemented as a MonoBehaviour and it will be attached to an NGUI View prefab, so we can use the GameObject.GetComponent() method to retrieve it in order to access to the functionality it provides from the Controller. As the ViewPresenter is the bridge between or game code and UI code it cannot be completely independant of the underlying implementation which is used to render things on the screen. In our case it will need to keep references to NGUI widgets (UIButtonUILabel, etc) in order to interact with them. The ViewPresenter is basically an Adapter pattern: we create our custom interface in order to access code external to our application. Those references must be set explicitly using either code or the inspector

Fortunately it can be partially automated: please check out the ViewPresenter.AutoPopulateDeclaredWidgets() method in the sample code for further info. Although the ViewPresenter is coupled to an specific UI system we create an interface consumed by our Controller which is a big gain: if we need to change the GUI toolkit used to render things in the screen we only need to modify the implementation keeping the public API untouched so we don’t need to touch any controller logic.

It is called ViewPresenter because it is similar in concept to the Presenter in a Model-View-Presenter pattern, but with one important difference: the Presenter is allowed to access the Model while the ViewPresenter can‘t.

Click here to see the example code of a PlayerController that will handle the view for the XP system

The ViewPresenter can hold some state if it is related to the way things are presented to the player. For example, the view could store the value of different colors to tint the Health Point indicator and change it depending on the health level. Those values can even be exposed as public properties to allow changing them in realtime using the inspector. However it must not hold any application logic state, leave that to the Controller, the ViewPresenter should not even know when a health level is considered “low”.

For example, if we extend our PlayerController to handle the Hit Points we can add a method that will change the color of the label when it is in low health:

public class PlayerController
{   // ...
    void UpdateHitPointsUI()
    {
        if (Player.HasLowHitPoints)
        {
            HitPointsViewLabel.ShowLowHealthColor();
        }
        else
        {
            HitPointsViewLabel.ShowNormalHealthColor();
        }
    }

}

This approach can be a bit overkill unless you want to create a very specific or complex custom widget and reuse it in your project. In the sample code we go for the easy route: the controller just changes a UnityEngine.Color property in the ViewPresenter.

Handling UI events

NGUI provides a nice event system that allows triggering a method in any MonoBehaviour we define as the handler of the event. This decouples the MonoBehaviour that generates the event from the one that will handle it. However, with great power comes a great chance of messing up all the architecture; as you can use ANY MonoBehaviour as a handler, it‘s really easy -and handy- to link in the inspector whatever MonoBehaviour you have in your scene to get things done, creating a dependency that can be difficult to track when you have a complex View created using several controls. Our dependency graph can become complex really fast.

To prevent Chaos from spreading among our codebase we follow a really easy rule: All View UI events will be handled by the ViewPresenter attached to that View. The ViewPresenter will capture the NGUI events and then raise a vanilla .NET event in response. The rest of the code subscribes to that .NET event. We do it so because that way we decouple the specific implementation of UI events from NGUI, and because this way we have the events wired by code, not in the inspector. This is -in my opinion- a safer approach: is more explicit, you can easily search for the code that handles that event using your IDE, is type-safe (if you delete the reference to the MonoBehavior that handles the event you’ll only notice when your control stop working in play mode) and allows to set the arguments we want to send with the event. Of course we need to wire the NGUI event with the ViewPresenter but we can automate it: check the ButtonViewPresenter.WireUIEvents() method in the example code

Creating complex views.

Now that we have some building blocks, it is easy to create more complex views by composition: add a new view formed by several UI prefabs, and create a new ViewPresenter for that composite view exposing the child ViewPresenters this view uses so you can access them from a controller:

Check out the code for this view by clicking here.

Finishing words

Please, tell use what do you think of this approach, or tell us about your approach when creating UIs for your game. Feedback is always welcome!

By the way, if you don’t have NGUI it would be a great exercise to change the example project to use the immediate GUI API from Unity3D: you just need to replace the NGUI Widget references and implement your OnGUI() method in each ViewPresenter. Don‘t hesitate to send us a pull request if you decide to do it ;)

时间: 2024-11-05 14:57:08

MVC模式在UI里的应用的相关文章

(架构)UI开发的MVC模式

原文:http://engineering.socialpoint.es/MVC-pattern-unity3d-ui.html 动机 和游戏开发的其他模块类似,UI一般需要通过多次迭代开发,直到用户体验近似OK.另外至关重要的是, 我们想尽快加速迭代的过程.使用MVC模式来进行设计,已经被业界证明了是可以解耦屏幕上的显示,如何控制用户的输入对显示的改变,以及如何根据应用的状态进行改变.MVC模式提供了以下好处: 可以修改UI的外观,而不用修改一行代码: 在不同的组件里面可以共享同一套逻辑代码,

Unity3d中UI开发的MVC模式

原文:http://engineering.socialpoint.es/MVC-pattern-unity3d-ui.html 动机 和游戏开发的其他模块类似,UI一般需要通过多次迭代开发,直到用户体验近似OK.另外至关重要的是, 我们想尽快加速迭代的过程.使用MVC模式来进行设计,已经被业界证明了是可以解耦屏幕上的显示,如何控制用户的输入对显示的改变,以及如何根据应用的状态进行改变.MVC模式提供了以下好处: (1) 可以修改UI的外观,而不用修改一行代码 (2) 在不同的组件里面可以共享同

iOS里面MVC模式详解

MVC是IOS里面也是很多程序设计里面的一种设计模式,M是model,V是view,C是controller.MVC模式在ios开发里面可谓是用得淋漓尽致. 以下是对斯坦福大学ios开发里面MVC模式的一段话的翻译 主要的宗旨是把所有的对象分为3个阵营,model阵营,view阵营,或者是controller阵营 model(APP的目的) 举个例子,你要做一个打飞机的游戏,那么这个就是太空中这辆飞船的位置,什么机型,每个飞船有多少机枪,护甲有多少等等.这就是model所做的事,而飞机在屏幕上的

javaBean、jstl、MVC模式

JavaBean 无参.getXXX和setXXX.private,并且对外提供一个public方法给其他类调用    比如person类    private String name Jsp 标签 <jsp:useBean id="" class=""  scope="">:表示查找id后面的类,如果没有的话就创建. id后面跟的是实例对象的名称,比如person       class后面的完整类名  比如com.cn.dom.p

(转)浅析三层架构与MVC模式的区别

MVC模式介绍: MVC全名是Model ViewController,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用于组织代码用一种业务逻辑和数据显示分离的方法,这个方法的假设前提是如果业务逻辑被聚集到一个部件里面,而且界面和用户围绕数据的交互能被改进和个性化定制而不需要重新编写业务逻辑MVC被独特的发展起来用于映射传统的输入.处理和输出功能在一个逻辑的图形化用户界面的结构中. MVC是表现层的架构,MVC的Model实际上是View Mod

技术总结--android篇(一)--MVC模式

先介绍下MVC模式:MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑.数据.界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑.MVC被独特的发展起来用于映射传统的输入.处理和输出功能在一个逻辑的图形化用户界面的结构中. 使用MVC模式编写代码,能使代码更加美观,更容易管理,扩展性强. 对于Android来说,

(06/05/11) MVC模式

MVC并不属于GOF的23个设计模式之列,但是它在GOF的书中作为一个重要的例子被提出来,并给予了很高的评价.一般的来讲,我们认为GOF的23个模式是一些中级的模式,在它下面还可以抽象出一些更为一般的低层的模式,在其上也可以通过组合来得到一些高级的模式.MVC就可以看作是一些模式进行组合之后的结果. MVC定义:即Model-View-Controller,把一个应用的输入.处理.输出流程按照Model.View.Controller的方式进行分离,这样一个应用被分成三个层,即模型层.视图层.控

【案例分享】使用ActiveReports报表工具,在.NET MVC模式下动态创建报表

提起报表,大家会觉得即熟悉又陌生,好像常常在工作中使用,又似乎无法准确描述报表.今天我们来一起了解一下什么是报表,报表的结构.构成元素,以及为什么需要报表. 什么是报表 简单的说:报表就是通过表格.图表等形式来动态显示数据,并为使用者提供浏览.打印.导出和分析的功能,可以用公式表示为: 报表 = 多样的布局 + 动态的数据 + 丰富的输出 报表通常包含以下组成部分: 报表首页:在报表的开始处,用来显示报表的标题.图形或说明性文字,每份报表只有一个报表首页. 页眉:用来显示报表中的字段名或对记录的

【Unity】基于MVC模式的背包系统 UGUI实现

本文基于MVC模式,用UGUI初步实现了背包系统. 包含点击和拖拽两种逻辑,先献上源代码,工程和分析稍后补充. Model 层 using UnityEngine; using UnityEngine.UI; using System.Collections; using System.Collections.Generic; /// <summary> /// 脚本功能:MVC模式--Model层,定义物品结构,保存物品数据 /// 添加对象:Bag 背包(Canvas下的空对象) ///