Search Unity

Discussion Centralize your Updates using Scriptable objects!

Discussion in 'Community Learning & Teaching' started by Kuro19980915, Mar 13, 2023.

  1. Kuro19980915

    Kuro19980915

    Joined:
    Jun 2, 2022
    Posts:
    2
    If you have 100's of Mono behaviour scripts and if you want all of these scripts to run at the same interval
    One of the solutions you would do so is to add the following code to all the Mono behaviour scripts.
    Code (CSharp):
    1. void Update
    2. {
    3.     time = time + Time.deltaTime;
    4.     if(time>Interval)
    5.     {
    6.         // your code
    7.         time = 0;
    8.     }
    9. }
    where you maintain the interval as a static variable in the master data or in a scriptable object and give reference to this in all the scripts to centralize your updates and repeat this code in every script
    I do propose another way to do this using scriptable objects where you only need to use the above script once
    Let's analyze what behavior should a mono behaviour script that updates should have
    1)It should have an update function.
    2)It should have a fixed update function.
    3)It should have a late update function.
    It's not like all mono behaviour scripts need all 3 types of updates
    We also need some variable that tells which update function the script needs
    So based on this lets make a interface called IUpdateable
    Code (CSharp):
    1.  public interface IUpdateable
    2.     {
    3.         public int UpdateMask
    4.         {
    5.             get;
    6.         }
    7.         void update();
    8.         void fixedUpdate();
    9.         void lateUpdate();
    10.     }
    To check what types of update function needs to be implemented, we are gonna use a property called
    update mask where
    if first bit is on it means it needs update
    if second bit is on it means it needs fixedupdate
    if third bit is on it means it needs lateupdate
    Based on above data we set the respective bits to true
    Here in the code we check if the derived class overrides the function of parent class and set those overrides function to true
    Now Let's make the abstract class that implements IUpdateable and inherits from mono behaviour
    Code (CSharp):
    1.  public abstract class UpdateableMonoBehaviour : MonoBehaviour, IUpdateable
    2.     {
    3.         #region Fields
    4.         [SerializeField] protected UpdateV2 updater;
    5.         #endregion Fields
    6.         #region Properties
    7.         public virtual int UpdateMask
    8.         {
    9.             get
    10.             {
    11.                 int _updateMask = 0;
    12.                 if(this.GetType().GetMethod("update").DeclaringType == this.GetType())
    13.                     _updateMask = _updateMask.setBitOn(0);
    14.                 if (this.GetType().GetMethod("fixedUpdate").DeclaringType == this.GetType())
    15.                     _updateMask = _updateMask.setBitOn(1);
    16.                 if (this.GetType().GetMethod("lateUpdate").DeclaringType == this.GetType())
    17.                     _updateMask = _updateMask.setBitOn(2);
    18.                 return _updateMask;
    19.             }
    20.         }
    21.         #endregion Properties
    22.         #region Methods
    23.         protected void OnEnable() {
    24.             if(updater!=null)
    25.                 updater.AddUpdateable(this);
    26.         }
    27.         protected void OnDisable() {
    28.             if (updater != null)
    29.                 updater.RemoveUpdateable(this);
    30.         }
    31.         public virtual void finalUpdate()
    32.         {
    33.          
    34.         }
    35.  
    36.         public virtual void fixedUpdate()
    37.         {
    38.          
    39.         }
    40.  
    41.         public virtual void lateUpdate()
    42.         {
    43.          
    44.         }
    45.      
    46.         public virtual void update()
    47.         {
    48.  
    49.         }
    50.         #endregion Methods
    51.         #region Helpers
    52.         #endregion Helpers
    53.     }
    We also add onEnable and onDisable functions to subscribe/unsubscribe itself to scriptable object container
    We also added a virtual update method for each type if we need a specific update type we override that update function
    Now let's make the scriptable object to store these objects
    Code (CSharp):
    1. [CreateAssetMenu(menuName = "MyScriptable/UpdateV2")]
    2.     public class UpdateV2 : ScriptableObject
    3.     {
    4.         #region fields
    5.         private readonly List<IUpdateable> _updateList = new List<IUpdateable>();
    6.         private readonly List<IUpdateable> _fixedUpdateList = new List<IUpdateable>();
    7.         private readonly List<IUpdateable> _lateUpdateList = new List<IUpdateable>();
    8.         [SerializeField] float _updateTime = 1000;
    9.         [SerializeField] float _fixedTime = 1000;
    10.         [SerializeField] float _lateTime = 1000;
    11.         #endregion fields
    12.  
    13.         #region properties
    14.         public float UpdateTime { get { return _updateTime; }}
    15.         public float FixedTime { get { return _fixedTime; }}
    16.         public float LateTime { get { return _lateTime; }}
    17.         #endregion properties
    18.         #region methods
    19.         public void AddUpdateable(IUpdateable updateable)
    20.         {
    21.             if(updateable.UpdateMask.checkIfBitIsOn(0))
    22.             {
    23.                 if(!_updateList.Contains(updateable))
    24.                     _updateList.Add(updateable);
    25.             }
    26.             if(updateable.UpdateMask.checkIfBitIsOn(1))
    27.             {
    28.                 if(!_fixedUpdateList.Contains(updateable))
    29.                     _fixedUpdateList.Add(updateable);
    30.             }
    31.             if(updateable.UpdateMask.checkIfBitIsOn(2))
    32.             {
    33.                 if(!_lateUpdateList.Contains(updateable))
    34.                     _lateUpdateList.Add(updateable);
    35.             }
    36.         }
    37.         public void RemoveUpdateable(IUpdateable updateable)
    38.         {
    39.             if (updateable.UpdateMask.checkIfBitIsOn(0))
    40.             {
    41.                 if (_updateList.Contains(updateable))
    42.                     _updateList.Remove(updateable);
    43.             }
    44.             if (updateable.UpdateMask.checkIfBitIsOn(1))
    45.             {
    46.                 if (_fixedUpdateList.Contains(updateable))
    47.                     _fixedUpdateList.Remove(updateable);
    48.             }
    49.             if (updateable.UpdateMask.checkIfBitIsOn(2))
    50.             {
    51.                 if (_lateUpdateList.Contains(updateable))
    52.                     _lateUpdateList.Remove(updateable);
    53.             }
    54.         }
    55.         public void update()
    56.         {
    57.             if(_updateList.Count!=0)
    58.             for (int i = _updateList.Count - 1; i >= 0; i--) { _updateList[i].update(); }
    59.         }
    60.         public void fixedUpdate()
    61.         {
    62.             if (_fixedUpdateList.Count != 0)
    63.             {
    64.                 for (int i = _updateList.Count - 1; i >= 0; i--) { _fixedUpdateList[i].update(); }
    65.             }
    66.         }
    67.         public void lateUpdate()
    68.         {
    69.             if (_lateUpdateList.Count != 0)
    70.             for (int i = _updateList.Count - 1; i >= 0; i--) { _lateUpdateList[i].update(); }
    71.         }
    72.         #endregion methods
    73.     }
    here we just make a data container to store all the IUpdateable objects.We create 3 lists for an update, a fixed update, a late update respectively.and a function to add and remove IUpdateable objects . In this functions we check if a specific bit is on or not and add them to specific lists.We also declare at what interval should it update for each update functions. We also define 3 functions to loop through each updates

    Lets create a MonoBehaviour script to update our update functions
    Code (CSharp):
    1. public class updaterScript : MonoBehaviour
    2. {
    3.     // Start is called before the first frame update
    4.     [SerializeField] UpdateV2 updater;
    5.     float time = 0;
    6.     [SerializeField]float updateTime = 1000;
    7.     [SerializeField]float fixedTime = 1000;
    8.     [SerializeField]float lateTime = 1000;
    9.     void Start()
    10.     {
    11.        
    12.     }
    13.     // Update is called once per frame
    14.     void Update()
    15.     {
    16.         time = time + Time.deltaTime;
    17.         if(time>updater.UpdateTime)
    18.         {
    19.             if(updater != null)
    20.                 updater.update();
    21.  
    22.             time = 0;
    23.         }
    24.     }
    25.     void FixedUpdate()
    26.     {
    27.         time = time + Time.deltaTime;
    28.         if (time > updater.FixedTime)
    29.         {
    30.             if (updater != null)
    31.                 updater.fixedUpdate();
    32.             time = 0;
    33.         }
    34.     }
    35.     private void LateUpdate()
    36.     {
    37.         time = time + Time.deltaTime;
    38.         if (time > updater.LateTime)
    39.         {
    40.             if (updater != null)
    41.                 updater.lateUpdate();
    42.             time = 0;
    43.         }
    44.     }
    Lets make script that inherits from UpdateableMonoBehaviour that meows every 1000 frames
    Code (CSharp):
    1. public class Meower : UpdateableMonoBehaviour
    2. {  
    3.     private new void OnEnable()
    4.     {
    5.         base.OnEnable();
    6.         Debug.Log("lol");
    7.     }
    8.     // Update is called once per frame
    9.     public override void update()
    10.     {
    11.         Debug.Log("meow");
    12.        
    13.     }
    14. }
    I am still a beginner learning new things so if I have made any mistakes or any improvements or new way of doing it . Please point it out
    That's it for today
    May the code be with you
    kuro out

    Reference
    1 - https://forum.unity.com/threads/efficient-updating-take-control-of-update-functions.479509/
     
  2. AnaWilliam850

    AnaWilliam850

    Joined:
    Dec 23, 2022
    Posts:
    36
    This is an interesting approach for centralizing update functions in Unity, which can help reduce code duplication and simplify maintenance.

    The idea is to define an interface IUpdateable with methods for update, fixed update, and late update, and a property UpdateMask that indicates which of these methods should be implemented. Then, an abstract class UpdateableMonoBehaviour is created, which inherits from MonoBehaviour and implements IUpdateable. UpdateableMonoBehaviour subscribes and unsubscribes itself to a ScriptableObject called UpdateV2, which stores a list of IUpdateable objects, organized by update type (update, fixed update, or late update).

    The UpdateV2 scriptable object has methods to add or remove IUpdateable objects to the appropriate lists, depending on their UpdateMask, and properties to store the interval for each update type. The UpdateableMonoBehaviour class overrides the update methods for each update type, allowing derived classes to implement specific behavior for each one.

    Overall, this approach provides a flexible way to organize update functions in Unity, and can help reduce code duplication and improve maintenance. However, it may add some complexity to the code, and care must be taken to ensure that all IUpdateable objects are added and removed correctly from the UpdateV2 list.