其实只是写着玩,响应式编程建议使用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 frame
void 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 frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
if (t.Value == "张三")
{
t.Value = "李四";
}
else
{
t.Value = "张三";
}
}
}
}
实际是动态为gameObject对象动态添加了一个