Last active
September 5, 2023 13:02
-
-
Save Zain-ul-din/302ac48ba121f2796d7b9caf95902d1b to your computer and use it in GitHub Desktop.
Unity UI Reactive Updates
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
namespace Randoms.Reactive { | |
[Serializable] | |
public class ReactiveVariable<T> : IReactive<T> { | |
#region Public Methods | |
public ReactiveVariable () { | |
InitSubscriber(); | |
} | |
public ReactiveVariable (T defaultValue) { | |
InitSubscriber(); | |
m_value = defaultValue; | |
} | |
public T Value { | |
get => m_value; | |
set { | |
m_value = value; | |
InvokeSubscribers(); | |
} | |
} | |
public ReactiveVariable<T> Observe(T value) { | |
m_value = value; | |
return this; | |
} | |
public ReactiveVariable<T> OnChange(object _this, Action<T> action) { | |
m_subscribers.AddLast(new Tuple<object, Action<T>>(_this, action)); | |
InvokeSubscribers(); | |
return this; | |
} | |
public ReactiveVariable<T> UnSubscribe (object _this) { | |
var itr = m_subscribers.First; | |
while(itr != null) { | |
var next_itr = itr.Next; | |
if (itr.Value.Item1 == _this) | |
m_subscribers.Remove(itr); | |
itr = next_itr; | |
} | |
return this; | |
} | |
public ReactiveVariable<T> ClearAllSubscribers () { | |
m_subscribers.Clear(); | |
return this; | |
} | |
#endregion | |
#region Internals | |
private void InvokeSubscribers () { | |
var itr = m_subscribers.First; | |
while(itr != null) | |
{ | |
if (itr.Value.Item1.ToString() == "null") | |
m_subscribers.Remove(itr); | |
else | |
itr.Value.Item2.Invoke(m_value); | |
itr = itr.Next; | |
} | |
} | |
private void InitSubscriber () { | |
m_subscribers = new LinkedList<Tuple<object, Action<T>>>(); | |
} | |
private LinkedList<Tuple<object, Action<T>>> m_subscribers; | |
[SerializeField] private T m_value; | |
#endregion | |
} | |
public interface IReactive <T> { | |
public T Value { get; set; } | |
} | |
} |
Help ful Extensions
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Randoms.Reactive
{
[Serializable]
public class ReactiveVariable<T> : IReactive<T>
{
#region Public Methods
public ReactiveVariable()
{
InitSubscriber();
}
public ReactiveVariable(T defaultValue)
{
InitSubscriber();
m_value = defaultValue;
}
public T Value
{
get => m_value;
set
{
m_value = value;
InvokeSubscribers();
}
}
public ReactiveVariable<T> Observe(T value)
{
m_value = value;
return this;
}
public ReactiveVariable<T> OnChange(object _this, Action<T> action)
{
m_subscribers.AddLast(new Tuple<object, Action<T>>(_this, action));
InvokeSubscribers();
return this;
}
public ReactiveVariable<T> UnSubscribe(object _this)
{
var itr = m_subscribers.First;
while (itr != null)
{
var next_itr = itr.Next;
if (itr.Value.Item1 == _this)
m_subscribers.Remove(itr);
itr = next_itr;
}
return this;
}
public ReactiveVariable<T> ClearAllSubscribers()
{
m_subscribers.Clear();
return this;
}
#endregion
#region Internals
private void InvokeSubscribers()
{
var itr = m_subscribers.First;
while (itr != null)
{
if (itr.Value.Item1.ToString() == "null")
m_subscribers.Remove(itr);
else
itr.Value.Item2.Invoke(m_value);
itr = itr.Next;
}
}
private void InitSubscriber()
{
m_subscribers = new LinkedList<Tuple<object, Action<T>>>();
}
private LinkedList<Tuple<object, Action<T>>> m_subscribers;
[SerializeField] private T m_value;
#endregion
#region Extensions
#endregion
}
public static class ReactiveExtensions_Internal
{
public static void BindTo<T>(this ReactiveVariable<T> self, MonoBehaviour _this, UnityEngine.UI.Text text)
{
self.OnChange(_this, (newVal) => text.text = _this.ToString());
}
}
public interface IReactive<T>
{
public T Value { get; set; }
}
}
Here are some approaches to make UI reactive inside unity
❌Common Approach
PlayerController.cs
public float health;
void OnPlayerHealthChange () {
health -= damage;
UIManager.Instance.playerHealthTxt.text = health+"";
}
UIManager.cs
public Text playerHealthTxt;
- Breaking single responsibility principle.
✔ Better Approach
PlayerController.cs
public float health;
public System.Action<float> OnHealthChange;
void OnPlayerHealthChange () {
health.Value -= damage;
OnHealthChange?.Invoke(health);
}
UIManager.cs
public Text playerHealthTxt;
public void Start () {
PlayerController.Instance.OnHealthChange += OnHealthChange;
}
public void OnHealthChange (float health) {
playerHealthTxt.text = health + "";
}
- Must be unsubscribe to avoid memory leaks
private void OnDestroy () {
PlayerController.Instance.OnHealthChange -= OnHealthChange;
}
✔✔ Better + Optimized Approach
PlayerController.cs
public ReactiveVariable<float> health;
void OnPlayerHealthChange () {
health.Value -= damage;
}
UIManager.cs
public Text playerHealthTxt;
public void Start () {
playerHealthTxt.BindTo(this, PlayerController.Instance.health);
}
+ No need to unsubscribe
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage
Demo