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 Jul 13, 2023

Usage

using Randoms.Reactive;

public ReactiveVariable<float> score;

// update value
score.Value += 1;

// listen for changes
// ! Note: it gets automatically unsubscribed when the object no longer remains in the memory.
score.OnChange(this, (value)=> {  
   // Do something
});

// unsubscribe manually
score.UnSubscribe(this);

// or remove all subscribers
score.ClearAllSubscribers();

Demo

image

@Zain-ul-din
Copy link
Author

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; }
    }
}

@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