其实只是写着玩,响应式编程建议使用UniRx插件(一套成熟的响应式编程解决方案),我写的主要是借鉴一下这个思想,实现的也不够优雅,不过逻辑也算严密可以正常使用.你可以查看我写的理解响应式属性的思想.
借鉴UniRx的ReactiveProperty类,且UniRx不仅有响应式属性.
using System;
using System.Linq;/// <summary>
/// 当值发生变化时触发回调(例如当玩家血量变化时更新GUI)。
/// </summary>
public class ReactiveValue<T>
{/// <summary>/// 定义过滤器委托,允许在设置新值时对值进行过滤或修正。/// </summary>private delegate T Filter(T originalValue, T newValue);/// <summary>/// 值变化时的回调委托。/// </summary>private Action<T> m_Set;/// <summary>/// 用于处理值变化前的过滤器。/// </summary>private Filter m_Filter;/// <summary>/// 当前的值。/// </summary>private T m_CurrentValue;/// <summary>/// 上一次的值,仅供内部使用。/// </summary>private T m_LastValue;/// <summary>/// 初始化值。/// </summary>/// <param name="initialValue">初始值。</param>public ReactiveValue(T initialValue){m_CurrentValue = initialValue;m_LastValue = m_CurrentValue;}/// <summary>/// 使用T的默认值初始化值/// </summary>public ReactiveValue() : this(default(T)) { }/// <summary>/// 判断当前值是否等于指定值。/// </summary>/// <param name="value">要比较的值。</param>/// <returns>如果当前值等于指定值,返回 true;否则返回 false。</returns>public bool IsEquals(T value){return m_CurrentValue != null && m_CurrentValue.Equals(value);}/// <summary>/// 添加一个回调/// </summary>/// <param name="callback">回调</param>/// <param name="immediateCall">添加了回调是否立即调用</param>public void AddChangeListener(Action<T> callback, bool immediateCall = false){if (m_Set == null || !m_Set.GetInvocationList().Contains(callback)){m_Set += callback;if (immediateCall){callback?.Invoke(m_CurrentValue);}}}/// <summary>/// 移除指定的监听器。/// </summary>/// <param name="callback">要移除的回调函数。</param>public void RemoveChangeListener(Action<T> callback){if (m_Set != null && m_Set.GetInvocationList().Contains(callback)){m_Set -= callback;}}/// <summary>/// 移除所有的监听器,谨慎使用,因为别人也进行了订阅/// </summary>public void RemoveAllChangeListeners(){m_Set = null;}/// <summary>/// 设置过滤器,过滤器将在回调前被调用,适合用于值限制(如玩家血量不能超过最大值等)。/// </summary>/// <param name="filter">过滤器委托:<原值,新值,返回值>。</param>public void SetFilter(Func<T, T, T> filter){m_Filter = new Filter(filter);}/// <summary>/// 获取当前值。/// </summary>/// <returns>返回当前值。</returns>public T Get(){return m_CurrentValue;}/// <summary>/// 设置新值,只有当新值与旧值不相同时才会触发回调。/// </summary>/// <param name="value">要设置的新值。</param>public void Set(T value){m_LastValue = m_CurrentValue;m_CurrentValue = value;if (m_Filter != null)m_CurrentValue = m_Filter(m_LastValue, m_CurrentValue);// 当新值和旧值不相等时触发回调if (m_LastValue == null || !m_LastValue.Equals(m_CurrentValue))m_Set?.Invoke(m_CurrentValue);}/// <summary>/// 强制更新值并触发回调,即使新值与旧值相同。/// </summary>/// <param name="value">要设置的新值。</param>public void SetValueForceCallBack(T value){m_LastValue = m_CurrentValue;m_CurrentValue = value;if (m_Filter != null)m_CurrentValue = m_Filter(m_LastValue, m_CurrentValue);// 强制触发回调m_Set?.Invoke(m_CurrentValue);}/// <summary>/// 设置新值但不触发回调。/// </summary>/// <param name="value">要设置的新值。</param>public void SetValueDontCallBack(T value){m_LastValue = m_CurrentValue;m_CurrentValue = value;if (m_Filter != null)m_CurrentValue = m_Filter(m_LastValue, m_CurrentValue);}
}
示例用法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class Test : MonoBehaviour
{public Text textComponent;//一般会声明在数据层,可以优雅地实现MVC等框架,这里为了方便ReactiveValue<string> t = new ReactiveValue<string>("张三");void Start(){t.AddChangeListener(Handle, true);}// Update is called once per framevoid Update(){if (Input.GetKeyDown(KeyCode.A)){if (t.IsEquals("张三")){t.Set("李四");}else{t.Set("张三");}}}void Handle(string t){textComponent.text = t;}private void OnDestroy(){t.RemoveChangeListener(Handle);}
}
UniRx实现
如果使用UniRx实现的话非常优雅.
AddTo的意思就是绑定了这个gameObject对象,当这个对象被销毁那么自动移除这个对象绑定的回调,就不需要傻傻的在OnDestroy中移除了,并且也可以使用匿名函数.
using System.Collections;
using System.Collections.Generic;
using UniRx;
using UnityEngine;
using UnityEngine.UI;public class Test : MonoBehaviour
{public Text textComponent;ReactiveProperty<string> t = new ReactiveProperty<string>("张三");void Start(){t.Subscribe(t => textComponent.text = t).AddTo(gameObject);}// Update is called once per framevoid Update(){if (Input.GetKeyDown(KeyCode.A)){if (t.Value == "张三"){t.Value = "李四";}else{t.Value = "张三";}}}}
实际是动态为gameObject对象动态添加了一个