Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice
  3. Dismiss Notice

Question Generic function to add listener method to dynamically selected static events.

Discussion in 'Scripting' started by Panagnos, May 17, 2024.

  1. Panagnos

    Panagnos

    Joined:
    Mar 10, 2015
    Posts:
    11
    I have this generic class which defines a static event Click

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System;
    4. using UnityEngine.EventSystems;
    5.  
    6. public class ClickButtonGeneric<T>: MonoBehaviour,IPointerClickHandler where T : class
    7. {
    8.     public static event Action Click;
    9.  
    10.     public void OnPointerClick(PointerEventData eventData)
    11.     {
    12.         ClickButton();
    13.     }
    14.  
    15.     protected virtual void ClickButton()
    16.     {
    17.         Click.Invoke();
    18.     }
    19.    
    20. }
    21.  
    This allows me to create various classes that inherit the generic class and have their own static event Click:
    Code (CSharp):
    1.  
    2. public class ClickButtonOptions : ClickButtonGeneric<ClickButtonOptions>{}
    3.  
    Code (CSharp):
    1.    
    2. public class ClickButtonLogin : ClickButtonGeneric<ClickButtonLogin>{}
    3.  
    I, then can add methods as listeners of the event, like the following example:
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. public class Options :MonoBehaviour
    5. {
    6.    
    7.     private void Start()
    8.     {
    9.         ClickButtonOptions.Click += Toggle;
    10.     }
    11.  
    12.     private void Toggle()
    13.     {
    14.         EventsManager.OnTogglePanel(GetComponent<CanvasGroup>());
    15.     }
    16.  
    17.     private void OnDisable()
    18.     {
    19.         ClickButtonOptions.Click -= Toggle;
    20.     }
    21.    
    22. }
    23.  
    I want to make a generic version of the above code where I will be able to define to which static event the Toggle method will be added. Something like the following:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. public class ToggleablePanel<T> :MonoBehaviour
    5. {
    6.    
    7.     private void Start()
    8.     {
    9.         T.Click += Toggle;
    10.     }
    11.  
    12.     private void Toggle()
    13.     {
    14.         EventsManager.OnTogglePanel(GetComponent<CanvasGroup>());
    15.     }
    16.  
    17.     private void OnDisable()
    18.     {
    19.         T.Click -= Toggle;
    20.     }
    21.    
    22. }
    23.  
    Is something like that possible?
    Would another approach be better?
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    8,402
    I'm not sure what the generic is even adding here, aside from letting you have multiple static events across multiple types. I don't believe you can do what you're trying to do as well. You will still need to use a concrete type to access the static
    Click
    delegate.

    A non-generic, but reusable component that you reference via the inspector would get the same effect without the need for so many different implementations.
     
  3. Panagnos

    Panagnos

    Joined:
    Mar 10, 2015
    Posts:
    11
    I could implement reusable components, but my approach uses csharp events and minimal to none inspector references.

    How would the non-generic approach look like? Could I select from inspector the static event I want to use? Or you mean to totally abandon the generic classes approach?
     
    Last edited: May 17, 2024
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    8,402
    No, nothing static can be serialised.

    You can still use C# events, you'd just reference the component you want to hook into the delegates of.

    It'd just look like this:
    Code (CSharp):
    1. public class ClickButtonHandler : MonoBehaviour, IPointerClickHandler
    2. {
    3.     public event Action Click;
    4.  
    5.     public void OnPointerClick(PointerEventData eventData)
    6.     {
    7.         ClickButton();
    8.     }
    9.  
    10.     protected virtual void ClickButton()
    11.     {
    12.         Click.Invoke();
    13.     }
    14. }
     
  5. Panagnos

    Panagnos

    Joined:
    Mar 10, 2015
    Posts:
    11
    Following spiney199's suggestions I ended up with the following:

    I created a button handler script that I attach to every button I want to invoke click events

    Code (CSharp):
    1.  
    2. using System;
    3. using UnityEngine;
    4. using UnityEngine.EventSystems;
    5.  
    6. public class ClickButtonHandler : MonoBehaviour,IPointerClickHandler
    7. {  
    8.     public event Action Click;
    9.  
    10.     public void OnPointerClick(PointerEventData eventData)
    11.     {
    12.         InvokeEvent();
    13.     }
    14.  
    15.     protected virtual void InvokeEvent()
    16.     {
    17.         Click.Invoke();
    18.     }
    19. }
    I reference all ClickButtonHandler instances in a singleton EventsManager:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System;
    4.  
    5. public class EventsManager : Singleton<EventsManager>
    6. {
    7.     #region Button Handlers
    8.  
    9.     public ClickButtonHandler buttonOptionsHandler;
    10.     public ClickButtonHandler buttonLoginHandler;
    11.     public ClickButtonHandler buttonSignupHandler;
    12.     public ClickButtonHandler buttonShowSignupScreenHandler;
    13.  
    14.     #endregion
    15.     protected override void OnEnable()
    16.     {
    17.         if (Instance == null)
    18.         {
    19.  
    20.         }
    21.         base.OnEnable();
    22.     }  
    23.  
    24. }
    Then I created a base class for the toggleable panels:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. public abstract class ToggleablePanel : MonoBehaviour
    5. {
    6.     protected ClickButtonHandler clickButtonHandler;
    7.     protected virtual void Start()
    8.     {
    9.        
    10.     }
    11.  
    12.     protected virtual void Toggle()
    13.     {
    14.         EventsManager.OnTogglePanel(GetComponent<CanvasGroup>());
    15.     }
    16.  
    17.     protected virtual void OnDisable()
    18.     {
    19.         if (EventsManager.Instance != null)
    20.         {
    21.             clickButtonHandler.Click -= Toggle;
    22.         }
    23.     }
    24.  
    25. }
    26.  
    And every Toggleable panel inherits the base class and defines the ClickButtonHandler instance that is going to be used:

    Code (CSharp):
    1.  
    2. public class Options : ToggleablePanel  
    3. {  
    4.     protected override void Start()
    5.     {
    6.         clickButtonHandler = EventsManager.Instance.buttonOptionsHandler;
    7.         clickButtonHandler.Click += Toggle;
    8.     }  
    9. }
     
  6. Panagnos

    Panagnos

    Joined:
    Mar 10, 2015
    Posts:
    11
    And finally I opted for composition instead of inheritance for the event listeners:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class ToggleablePanel : MonoBehaviour
    4. {
    5.     private CanvasGroup canvasGroup;
    6.     private ClickButtonHandler clickButtonHandler;
    7.     public void Initialize(ClickButtonHandler _clickButtonHandler,CanvasGroup _canvasGroup)
    8.     {
    9.         canvasGroup = _canvasGroup;
    10.         clickButtonHandler = _clickButtonHandler;
    11.         clickButtonHandler.Click += Toggle;
    12.     }  
    13.  
    14.     private void Toggle()
    15.     {
    16.         EventsManager.OnTogglePanel(canvasGroup);
    17.     }
    18.  
    19.     protected virtual void OnDisable()
    20.     {      
    21.         clickButtonHandler.Click -= Toggle;      
    22.     }
    23.  
    24. }
    25.  
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4. public class Options : MonoBehaviour  
    5. {
    6.     private ToggleablePanel toggleablePanel;
    7.  
    8.     private void Start()
    9.     {
    10.         toggleablePanel = gameObject.AddComponent<ToggleablePanel>();
    11.         toggleablePanel.Initialize(EventsManager.Instance.buttonOptionsHandler, GetComponent<CanvasGroup>());
    12.        
    13.     }
    14. }
    15.