Skip to content

Instantly share code, notes, and snippets.

@Zain-ul-din
Last active September 5, 2023 13:02
Show Gist options
  • Save Zain-ul-din/302ac48ba121f2796d7b9caf95902d1b to your computer and use it in GitHub Desktop.
Save Zain-ul-din/302ac48ba121f2796d7b9caf95902d1b to your computer and use it in GitHub Desktop.
Unity UI Reactive Updates
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; }
}
}
@Zain-ul-din
Copy link
Author

Zain-ul-din commented Sep 5, 2023

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