Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Unity Messages for non MonoBehaviours

Discussion in 'Scripting' started by SquarePieStudios, Jun 24, 2015.

  1. SquarePieStudios

    SquarePieStudios

    Joined:
    Apr 22, 2015
    Posts:
    33
    Hey all,

    I wrote up a script that captures Unity's messages (Update, FixedUpdate, OnApplicationPause, etc..) and forwards them to any subscribers. I wanted to get share it with the community and get some thoughts on ways to improve it.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. namespace SPStudios.Tools {
    5.     /// <summary>
    6.     /// <para>A singleton that listens for Unity messages and forwards them to listeners</para>
    7.     /// <para>Allows for listening to Unity messages from any class</para>
    8.     /// </summary>
    9.     public class UnityMessageForwarder : MonoBehaviour {
    10.         private static UnityMessageForwarder _instance;
    11.         public static UnityMessageForwarder Instance {
    12.             get {
    13.                 if(_instance == null) {
    14.                     _instance = FindObjectOfType<UnityMessageForwarder>();
    15.  
    16.                     if(_instance == null) {
    17.                         GameObject go = new GameObject();
    18.                         _instance = go.AddComponent<UnityMessageForwarder>();
    19.                         go.name = _instance.GetType().ToString();
    20.                     }
    21.                 }
    22.  
    23.                 return _instance;
    24.             }
    25.         }
    26.         /// <summary>
    27.         /// Different types of messages that are configured to be listend to
    28.         /// </summary>
    29.         public enum MessageType {
    30.             FixedUpdate,
    31.             LateUpdate,
    32.             OnApplicationPause,
    33.             OnApplicationQuit,
    34.             OnGUI,
    35.             Update,
    36.         }
    37.  
    38.         /// <summary>
    39.         /// A callback delegate for when the Unity message has been broadcast
    40.         /// </summary>
    41.         public delegate void MessageCallback();
    42.  
    43.         /// <summary>
    44.         /// A dictionary mapping of message types and their corresponding delegate callbacks
    45.         /// </summary>
    46.         private Dictionary<MessageType, MessageCallback> _messageCallbacks = new Dictionary<MessageType, MessageCallback>() {
    47.             { MessageType.FixedUpdate, null },
    48.             { MessageType.LateUpdate, null },
    49.             { MessageType.OnApplicationPause, null },
    50.             { MessageType.OnApplicationQuit, null },
    51.             { MessageType.OnGUI, null },
    52.             { MessageType.Update, null },
    53.         };
    54.  
    55.         /// <summary>
    56.         /// Broadcasts a message to all listeners
    57.         /// (Note): Do not use this function unless you know what you're doing
    58.         /// </summary>
    59.         /// <param name="messageType">The message type to broadcast</param>
    60.         public static void BroadcastMessage(MessageType messageType) {
    61.             Instance.BroadcastUnityMessage(messageType);
    62.         }
    63.         private void BroadcastUnityMessage(MessageType messageType) {
    64.             MessageCallback callback;
    65.             if(_messageCallbacks.TryGetValue(messageType, out callback)) {
    66.                 if(callback != null) {
    67.                     callback();
    68.                 }
    69.             }
    70.         }
    71.  
    72.         //Functions for setting and disabling listeners
    73.         #region Listener Functions
    74.         /// <summary>
    75.         /// Sets up a callback for a unity message
    76.         /// </summary>
    77.         /// <param name="messageType">The unity message to listen to</param>
    78.         /// <param name="callback">The callback</param>
    79.         public static void AddListener(MessageType messageType, MessageCallback callback) {
    80.             Instance.AddListenerToUnityMessage(messageType, callback);
    81.         }
    82.         private void AddListenerToUnityMessage(MessageType messageType, MessageCallback callback) {
    83.             if(!_messageCallbacks.ContainsKey(messageType)) {
    84.                 _messageCallbacks.Add(messageType, null);
    85.             }
    86.             _messageCallbacks[messageType] += callback;
    87.         }
    88.         /// <summary>
    89.         /// Removes a callback from a unity message
    90.         /// </summary>
    91.         /// <param name="messageType">The unity message being listened to</param>
    92.         /// <param name="callback">The callback to remove</param>
    93.         public static void RemoveListener(MessageType messageType, MessageCallback callback) {
    94.             Instance.RemoveListenerToUnityMessage(messageType, callback);
    95.         }
    96.         private void RemoveListenerToUnityMessage(MessageType messageType, MessageCallback callback) {
    97.             if(_messageCallbacks.ContainsKey(messageType)) {
    98.                 _messageCallbacks[messageType] -= callback;
    99.             }
    100.         }
    101.         #endregion
    102.  
    103.         //The unity callback messages to forward to listeners
    104.         #region Unity Event Message functions
    105.         private void FixedUpdate() {
    106.             BroadcastMessage(MessageType.FixedUpdate);
    107.         }
    108.         private void LateUpdate() {
    109.             BroadcastMessage(MessageType.LateUpdate);
    110.         }
    111.         private void OnApplicationPause(bool pauseStatus) {
    112.             //pauseStatus can be easily accessed by checking Application.isPlaying
    113.             BroadcastMessage(MessageType.OnApplicationPause);
    114.         }
    115.         private void OnApplicationQuit() {
    116.             BroadcastMessage(MessageType.OnApplicationQuit);
    117.         }
    118.         private void OnGUI() {
    119.             BroadcastMessage(MessageType.OnGUI);
    120.         }
    121.         private void Update() {
    122.             BroadcastMessage(MessageType.Update);
    123.         }
    124.         #endregion
    125.     }
    126. }
    Here's a test script that shows how to use it

    Code (CSharp):
    1. using UnityEngine;
    2. using SPStudios.Tools;
    3.  
    4. using MessageType = SPStudios.Tools.UnityMessageForwarder.MessageType;
    5.  
    6. public class TestScript1 : MonoBehaviour {
    7.     public TestClassWithEvents events;
    8.     private void Awake() {
    9.         events = new TestClassWithEvents();
    10.     }
    11.  
    12.     private Rect _buttonRect = new Rect(Screen.width / 3.5f, Screen.height / 3.5f, Screen.width / 4, Screen.height / 4);
    13.     private readonly GUIContent _unsubscribeLabel = new GUIContent("Disable listeners");
    14.     private void OnGUI() {
    15.         if(GUI.Button(_buttonRect, _unsubscribeLabel)) {
    16.             events.Unsubscribe();
    17.         }
    18.     }
    19. }
    20.  
    21. public class TestClassWithEvents {
    22.     private const int TEST_LAMBDA_PARAM = 17;
    23.     public TestClassWithEvents() {
    24.         //Standard listerner adding
    25.         UnityMessageForwarder.AddListener(MessageType.Update, UpdateFunction);
    26.         UnityMessageForwarder.AddListener(MessageType.FixedUpdate, FixedUpdateFunction);
    27.         UnityMessageForwarder.AddListener(MessageType.OnGUI, OnGUIFunction);
    28.         UnityMessageForwarder.AddListener(MessageType.LateUpdate, LateUpdateFunction);
    29.  
    30.         //Lambda functions do not work well with the UnityMessageForwarder if you want to remove it later
    31.         //The callback will work, but RemoveListener will not work.
    32.         UnityMessageForwarder.AddListener(MessageType.Update, () => UpdateFunctionWithParam(TEST_LAMBDA_PARAM));
    33.     }
    34.  
    35.     public void Unsubscribe() {
    36.         Debug.LogError("Unsubscribing called now");
    37.         //RemoveListeners
    38.         UnityMessageForwarder.RemoveListener(MessageType.Update, UpdateFunction);
    39.         UnityMessageForwarder.RemoveListener(MessageType.FixedUpdate, FixedUpdateFunction);
    40.         UnityMessageForwarder.RemoveListener(MessageType.OnGUI, OnGUIFunction);
    41.         UnityMessageForwarder.RemoveListener(MessageType.LateUpdate, LateUpdateFunction);
    42.  
    43.         //This won't actually do anything
    44.         UnityMessageForwarder.RemoveListener(MessageType.Update, () => UpdateFunctionWithParam(TEST_LAMBDA_PARAM));
    45.     }
    46.  
    47.     private void UpdateFunction() {
    48.         Debug.Log("UpdateFunction called");
    49.     }
    50.     private void UpdateFunctionWithParam(int intParam) {
    51.         Debug.Log(string.Format("UpdateFunctionWithParam called -- param({0})", intParam));
    52.     }
    53.     private void FixedUpdateFunction() {
    54.         Debug.Log("FixedUpdateFunction called");
    55.     }
    56.     private void OnGUIFunction() {
    57.         Debug.Log("OnGUIFunction called");
    58.     }
    59.     private void LateUpdateFunction() {
    60.         Debug.Log("LateUpdateFunction called");
    61.     }
    62.  
    63.     private void Update() {
    64.         Debug.LogError("This shouldn't be called");
    65.     }
    66. }
    67.  
     
    jpmota99 and chelnok like this.
  2. chelnok

    chelnok

    Joined:
    Jul 2, 2012
    Posts:
    680
    Looks little bit more advanced and flexible compared to my current approach; calling myNoMonoBehaviour.Update() in monobehaviour Update :)

    Thanks for sharing!
     
    SquarePieStudios likes this.
  3. SquarePieStudios

    SquarePieStudios

    Joined:
    Apr 22, 2015
    Posts:
    33
    Yea of course! That's the exact use case I'm trying to help with. This way if you have an object used in a bunch of different monobehaviours, you don't have to call Update on each one of them, they can handle their own Update callback :D
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    I have a similar class here:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/GameLoopEntry.cs

    I don't use it too much, as I don't write a lot of code that needs hooks into this. If I need hooks into Update I make sure to try to design it as a component first and foremost (I try to design pretty much anything as that first and foremost).

    I also separate the updates into 3 types: early, standard, tardy. Which all 3 update, fixedupdate, lateupdate have versions of.

    Early happen before all other calls (the GameLoopEntry component is configured first in the execution order).
    Standard just updates at an unassigned position in the execution order.
    Tardy happens after all other calls happen (the TardyExecutionUpdateEventHooks component is configured last in the execution order).

    There's also a few other things in there. Like an Invoke on the next update hook. This is useful for threads that need to call back to the main thread.
     
    SquarePieStudios likes this.
  5. SquarePieStudios

    SquarePieStudios

    Joined:
    Apr 22, 2015
    Posts:
    33
    Awesome! Thanks for sharing. There's definitely some functionality here that I like. I especially like the main thread callback for background threads. At my last job I was doing the majority our project's threading work. I knew I needed something like this, but as things go, I never found the time to set up something like this.

    I will say that the reason I will tend to lean towards this instead of a component is because a component comes with a lot of extra memory baggage.