//参考http://www.unity.5helpyou.com/3074.html
Mvc: 分为 Model 层,View 层,Controller 控制层
Model: 处理数据 ,不保存任何View 数据,状态等。只能被Model相关访问,或者被Controller访问,会通知外部系统处理相关;逻辑。
View : 可以处理一些动画,布局,用户输入,显示屏幕的相关逻辑。
Controller:是View 与 Model之间的桥梁。负责根据View 传来的事件处理,并对Model回调的事件进行处理。持有View所需要的应用状态 .根据状态show/hides/activates/deactivates/updates View或者View的某些部分。如controller可临时将攻击按钮Distable掉,因为此时攻击处于冷却状态,冷却状态一过,controller会re-enable这个按钮.
处理用户在View中触发的事件,比如用户按下了一个按钮;处理Model触发的事件,比如player获得了 XP并触发了升级,所以controller就更新了View中的Level Number.
Mvc: 模式再加入一个中间层来进一步解耦Nguide View实现
ViewPresenter
一个ViewPresenter位于View和Controller之间,作为一个接口存在,暴露了对于一个View来说是普适的操作集合,无论View本身是基于什么库来实现的(NGUI,UGUI等)
一个游戏中的按钮,一般来说都有以下功能集
(1) 设置按钮label中的文字
(2) 修改按钮的背景图
(3) enable/disable用户输入
(4) 当用户点击按钮时,进行Notify
(5)这些与UI具体实现无关的操作,所以应该分离出来。
(6)如果需要更换GUI库,只需要修改修改ViewPresenter的实现,而不需要修改ViewPresenter的接口以及controller的任何逻辑。
现在先上代码:
1.
using System;
public class PlayerModel
{
public PlayerModel()
{
XP = 0;
Level = 1;
HitPoints = MaxHitPoints;
}
public event EventHandler XPGained;
public event EventHandler LevelUp;
public event EventHandler DamageTaken;
public event EventHandler Died;
int _hitPoints;
public int HitPoints
{
get
{
return _hitPoints;
}
set
{
var oldValue = _hitPoints;
_hitPoints = value;
if (_hitPoints < 0)
{
_hitPoints = 0;
}
if (oldValue != _hitPoints)
{
if (DamageTaken != null) DamageTaken(this, EventArgs.Empty);
if (IsDead)
{
if (Died != null) Died(this, EventArgs.Empty);
}
}
}
}
public int Level { get; private set; }
public int XP { get; private set; }
public int MaxHitPoints
{
get { return Level * 150; }
}
public bool HasLowHitPoints
{
get { return HitPoints <= MaxHitPoints * 0.25f; }
}
public bool IsDead
{
get { return HitPoints <= 0; }
}
public void TakeDamage(int hpDamage)
{
if (IsDead) return;
HitPoints -= hpDamage;
}
public void AddXp(int amount)
{
if (IsDead) return;
XP += amount;
OnPlayerXPGained();
if (XP >= (100 * Level))
{
OnPlayerLevelUp();
}
}
protected virtual void OnPlayerLevelUp()
{
Level++;
HitPoints = MaxHitPoints;
if (LevelUp != null) LevelUp(this, EventArgs.Empty);
}
protected virtual void OnPlayerXPGained()
{
if (XPGained != null) XPGained(this, EventArgs.Empty);
}
}
2.
这里注册了四个事件回调。当数据改变的时候可以触发UI相关逻辑的。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
[ExecuteInEditMode]
public class ViewPresenter : MonoBehaviour
{
protected virtual UIRect ViewRoot { get; set; }
public event EventHandler ViewDidHide;
public event EventHandler ViewDidShow;
#region Unity3D messages
void Awake()
{
// This will allow to set the view in the inspector if we want to
ViewRoot = ViewRoot ?? GetComponent<UIRect>();
// Set the alpha to cero so the item is created
// invisible. When the show method is called
// the view will be made visible using a transition.
#if UNITY_EDITOR
if (UnityEditor.EditorApplication.isPlaying)
UpdateAlpha(0);
#else
ViewRoot.alpha = 0f;
#endif
AwakeUnityMsg();
}
void Start()
{
StartUnityMsg();
}
void OnValidate()
{
AutoPopulateDeclaredWidgets();
OnValidateUnityMsg();
}
void OnDestroy()
{
OnDestroyUnityMsg();
}
#endregion
#region Unity3D Messages propagation
protected virtual void AwakeUnityMsg()
{
}
protected virtual void StartUnityMsg()
{
}
protected virtual void OnValidateUnityMsg()
{
}
protected virtual void OnDestroyUnityMsg()
{
}
#endregion
#region Autopopulate widgets
[ContextMenu("Auto populate widgets")]
public void AutoPopulateDeclaredWidgetsContextMenu()
{
AutoPopulateDeclaredWidgets();
}
/// <summary>
/// This matches the widgets found in the prefab this MB is attached to
/// with the properties defined in this file, so we can keep a reference
/// to them.
/// We use the following rules to do the matching:
/// GameObject's name == Property Name
/// GameObjects widget component type == Property type
/// </summary>
void AutoPopulateDeclaredWidgets()
{
foreach(var nguiWidget in GetDeclaredUIWidgets())
{
var childTransform = GetChildRecursive(transform, nguiWidget.Name);
if(childTransform == null)
{
continue;
}
if(nguiWidget.GetValue(this) == null)
{
nguiWidget.SetValue(this, childTransform.GetComponent(nguiWidget.FieldType));
}
}
}
/// <summary>
/// Finds all properties that derive from UIWidgets or UIWidgetContainers
/// in this object
/// </summary>
/// <returns>The declared user interface widgets.</returns>
IEnumerable<FieldInfo> GetDeclaredUIWidgets()
{
return this.GetType().GetFields(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance).Where(
m => typeof(UIWidget).IsAssignableFrom(m.FieldType)
|| typeof(UIWidgetContainer).IsAssignableFrom(m.FieldType));
}
#endregion
public virtual void Enable()
{
ViewRoot.enabled = true;
}
public virtual void Disable()
{
ViewRoot.enabled = false;
}
public virtual void Show()
{
UpdateAlpha(1f);
if(ViewDidShow != null)
{
ViewDidShow(this, EventArgs.Empty);
}
}
public virtual void Hide()
{
UpdateAlpha(0);
if(ViewDidHide != null)
{
ViewDidHide(this, EventArgs.Empty);
}
}
public static Transform GetChildRecursive(Transform trans, string name)
{
Component[] transforms = trans.GetComponentsInChildren( typeof( Transform ), true );
foreach( Transform atrans in transforms )
{
if( atrans.name == name )
{
return atrans;
}
}
return null;
}
protected void UpdateAlpha(float value)
{
ViewRoot.alpha = value;
}
}
这里主要职责就是要做相关UI View类共有的功能。(这个功能后面再说),这些与UI具体实现无关的操作,所以应该分离出来。
3.
using System;
using System.Configuration;
using UnityEngine;
public class ButtonPresenterEventArgs : System.EventArgs
{
public Vector3 MousePosition {get;private set;}
public ButtonPresenterEventArgs(Vector2 mousePosition)
{
MousePosition = mousePosition;
}
}
/// <summary>
/// View Presenter for a button with a label
/// </summary>
public class ButtonViewPresenter : ViewPresenter
{
Color _buttonOriginalHoverColor;
Color _buttonOriginalDefaultColor;
public UIButton Button;
public UILabel ButtonLabel;
public UITexture ButtonImage;
public string Text
{
get
{
return ButtonLabel != null ? ButtonLabel.text : string.Empty;
}
set
{
if(ButtonLabel == null)
{
return;
}
ButtonLabel.text = value;
}
}
public Texture ImageSprite
{
get
{
return ButtonImage != null ? ButtonImage.mainTexture : null;
}
set
{
if(ButtonImage == null)
{
return;
}
ButtonImage.mainTexture = value;
}
}
/// <summary>
/// This will allow to keep track of the status of the button in order to disable
/// the events if the button is disabled
/// </summary>
public bool IsEnabled
{
get;
private set;
}
public override void Enable()
{
IsEnabled = true;
Button.defaultColor = _buttonOriginalDefaultColor;
Button.hover = _buttonOriginalHoverColor;
Button.UpdateColor(IsEnabled);
}
public override void Disable()
{
IsEnabled = false;
Button.defaultColor = Button.disabledColor;
Button.hover = Button.disabledColor;
Button.UpdateColor(IsEnabled);
}
public event EventHandler<ButtonPresenterEventArgs> Clicked;
protected virtual void OnButtonClicked()
{
// Do not propagate the click event if the button is disabled
if(!IsEnabled)
{
return;
}
if(Clicked != null)
{
Clicked(this, new ButtonPresenterEventArgs(Input.mousePosition));
}
}
protected override void AwakeUnityMsg()
{
base.AwakeUnityMsg();
WireUIEvents();
IsEnabled = Button.isEnabled;
_buttonOriginalDefaultColor = Button.defaultColor;
_buttonOriginalHoverColor = Button.hover;
}
protected virtual void WireUIEvents()
{
// Programatically add the onClick handler if it is not set
// so the ButtonClicked event is always called (NGUI specific)
if(Button.onClick.Count <= 0)
{
Button.onClick.Add(new EventDelegate(this, "OnButtonClicked"));
}
}
}
举例一个继承于ViewPresenter。
针对实现具体的逻辑需求,如可以动画点击,等等。注册一个监听事件,在父类的Awake中已经调用 void AwakeUnityMsg();
4.
public class MainGUIViewPresenter : ViewPresenter
{
public ButtonViewPresenter AddXpButton;
public ButtonViewPresenter TakeDamageButton;
public LabelViewPresenter HitPointsLabel;
public LabelViewPresenter XpLabel;
public LabelViewPresenter LevelLabel;
public override void Show()
{
AddXpButton.Show();
XpLabel.Show();
LevelLabel.Show();
HitPointsLabel.Show();
TakeDamageButton.Show();
base.Show();
}
public override void Hide()
{
AddXpButton.Hide();
XpLabel.Hide();
LevelLabel.Hide();
HitPointsLabel.Hide();
TakeDamageButton.Hide();
base.Hide();
}
}
5.设计一个适配器模式,适配器的使用可以加强代码的简洁性,又可以获取View 相关的内容。
using System;
using UnityEngine;
public class PlayerController
{
PlayerModel Player {get;set;}
MainGUIViewPresenter MainGUI {get;set;}
public PlayerController()
{
MainGUI = CreateView("MainGUI").GetComponent<MainGUIViewPresenter>();
MainGUI.AddXpButton.Clicked += (s, e) => Player.AddXp(50);
MainGUI.AddXpButton.Clicked += (s, e) => Debug.Log("Adding XP points");
MainGUI.TakeDamageButton.Clicked += (s, e) => Player.TakeDamage(75);
MainGUI.TakeDamageButton.Clicked += (s, e) => Debug.Log("Player took damage!");
Player = new PlayerModel();
Player.XPGained += OnPlayerGainedXP;
Player.LevelUp += OnPlayerLevelUp;
Player.LevelUp += (s,e) => Debug.Log("Player leveled up!");
Player.DamageTaken += OnDamageTaken;
Player.DamageTaken += (sender, e) => Debug.Log("Damage taken");
Player.Died += (s, e) => UpdatePlayerDeadUI();
Player.Died += (s, e) => Debug.Log("Player is dead");
UpdateLevelAndXPUI();
UpdateHitPointsUI();
}
void UpdateHitPointsUI()
{
MainGUI.HitPointsLabel.Text = "Hit Points " + Player.HitPoints;
if (Player.HasLowHitPoints)
{
MainGUI.HitPointsLabel.TextColor = UnityEngine.Color.yellow;
}
else
{
MainGUI.HitPointsLabel.TextColor = UnityEngine.Color.white;
}
}
void UpdateLevelAndXPUI()
{
MainGUI.XpLabel.Text = "Experience: " + Player.XP;
MainGUI.LevelLabel.Text = "Level " + Player.Level;
}
void UpdatePlayerDeadUI()
{
MainGUI.HitPointsLabel.Text = "Dead";
MainGUI.HitPointsLabel.TextColor = UnityEngine.Color.red;
}
public void ShowView()
{
MainGUI.Show();
}
public void HideView()
{
MainGUI.Hide();
}
public void OnPlayerGainedXP(object sender, EventArgs args)
{
UpdateLevelAndXPUI();
}
public void OnDamageTaken(object sender, EventArgs args)
{
UpdateHitPointsUI();
}
public void OnPlayerLevelUp(object sender, EventArgs args)
{
UpdateLevelAndXPUI();
UpdateHitPointsUI();
}
GameObject CreateView(string viewName)
{
// Loads the prefab with the view and instantiates it inside the View hierarchy
return GameObject.Find("MainGUI");
}
}
6.现在控制器Control负责中介对 view 事件的按钮的触发,导致数据变更,数据变更回调一个view 视图改变的回调。
public class MainInitialicer : MonoBehaviour
{
PlayerController Controller;
void Start()
{
Controller = new PlayerController();
Controller.ShowView();
}
}
7.初始化Controller 。
总结: 1.这样设计可以添加使逻辑和实现分开。当我们要改Ugui库就直接改实现,而不用改其它的逻辑 2.(6)适配器的使用可以加强代码的简洁性,二可以对以后多个UI面板开发提供容易扩展。如果以后要做到几个UI面板,我们可以设计在初始化MainInitialicer 类中创建一个函数,进行多个面板控制,提供一个Key 然后值是一个
public void SwitchToUiPlane(string key)
{
switch (key)
{
case "UiPlane":
//不应该再创建了,可以创建PlayerController为引用型的、不然每次切换到这个Ui又要创建。可以变成引用类型,然后可以创建多个空物体用来赋值PlayerControl不同的逻辑控制进行管理。改变PlayerControl类的构造函数成Start初始化。
Controller = new PlayerController();
Controller.ShowView();
break;
case "UiPause":
//暂停的对应面板
break;
等等
}
}
}
有新的想法到时候再改改。