UnityMVC

//参考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;
等等
        }
    }
}

有新的想法到时候再改改。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值