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. Dismiss Notice

Question Scriptable object events - how to pass more than one parameter?

Discussion in 'Scripting' started by stephenq80, Jun 2, 2021.

  1. stephenq80

    stephenq80

    Joined:
    Jun 20, 2018
    Posts:
    34
    Hi,

    Thanks for reading my post. I came across a Unity blog post and a YouTube tutorial that laid out how to create and use scriptable object events. The systems works fine for passing one parameter.

    However, there are times where I might want to pass more than one parameter of the same type. I was wonder if someone could show me how to modify the code to allow passing two parameters of the same type? Here is the code.

    BaseGameEvent

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. public abstract class BaseGameEvent<T> : ScriptableObject
    5. {
    6.     private readonly List<IGameEventListener<T>> eventListeners = new List<IGameEventListener<T>>();
    7.  
    8.     public void RaiseEvent(T item)
    9.     {
    10.         for (int i = eventListeners.Count - 1; i >= 0; i--)
    11.         {
    12.             eventListeners[i].OnEventRaised(item);
    13.         }
    14.     }
    15.  
    16.     public void RegisterListener(IGameEventListener<T> listener)
    17.     {
    18.         if (!eventListeners.Contains(listener))
    19.         {
    20.             eventListeners.Add(listener);
    21.         }
    22.     }
    23.  
    24.     public void UnregisterListener(IGameEventListener<T> listener)
    25.     {
    26.         if (eventListeners.Contains(listener))
    27.         {
    28.             eventListeners.Remove(listener);
    29.         }
    30.     }
    31. }
    BaseGameEventListener

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.Events;
    4.  
    5. public abstract class BaseGameEventListener<T, E, UER> : MonoBehaviour, IGameEventListener<T> where E: BaseGameEvent<T> where UER : UnityEvent<T>
    6. {
    7.     [SerializeField] private E gameEvent;
    8.     public E GameEvent { get { return gameEvent; } set { gameEvent = value; } }
    9.  
    10.     [SerializeField] private UER unityEventResponse;
    11.  
    12.     private void OnEnable()
    13.     {
    14.         if (gameEvent == null)
    15.         {
    16.             return;
    17.         }
    18.         gameEvent.RegisterListener(this);
    19.     }
    20.  
    21.     private void OnDisable()
    22.     {
    23.         if (gameEvent == null)
    24.         {
    25.             return;
    26.         }
    27.         gameEvent.UnregisterListener(this);
    28.     }
    29.  
    30.     public void OnEventRaised(T item)
    31.     {
    32.         if (unityEventResponse != null)
    33.         {
    34.             unityEventResponse.Invoke(item);
    35.         }
    36.     }
    37. }
    38.  
    IGameEventListener

    Code (CSharp):
    1.  
    2. public interface IGameEventListener<T>
    3. {
    4.     void OnEventRaised(T item);
    5. }
    6.  
    Thanks for any help!
     
  2. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    You would do this by just adding more generic types to raise the event with:
    Code (CSharp):
    1. public interface IGameEventListener<T>
    2. {
    3.   void OnEventRaised(T item);
    4. }
    5.  
    6. public interface IGameEventListener<T1, T2>
    7. {
    8.   void OnEventRaised(T1 item1, T2 item2);
    9. }
    10.  
    11. public interface IGameEventListener<T1, T2, T3>
    12. {
    13.   void OnEventRaised(T1 item1, T2 item2, T3 item3);
    14. }
    But you can probably already see how cumbersome this is.

    A better approach would be to keep the one-parameter logic as is, and if you need to pass more data into an event, use a struct to do so.
    For example:
    Code (CSharp):
    1. public struct DamageEventArgs
    2. {
    3.   public GameObject damageSource;
    4.   public GameObject damageTarget;
    5.   public float damage;
    6. }
    Code (CSharp):
    1. public class DamageEvent : BaseGameEvent<DamageEventArgs>
    2. {
    3.   //etc...
    4. }
    Code (CSharp):
    1. public class DamageEventListener : IGameEventListener<DamageEventArgs>
    2. {
    3.   public void OnEventRaised(DamageEventArgs args)
    4.   {
    5.     string sourceName = args.damageSource.name;
    6.     string targetName = args.damageTarget.name;
    7.     float damage = args.damage;
    8.  
    9.     Debug.Log($"{sourceName} attacked {targetName} for {damage} damage!");
    10.   }
    11. }
     
    Last edited: Jun 2, 2021
    Bunny83, StarManta and Lekret like this.
  3. stephenq80

    stephenq80

    Joined:
    Jun 20, 2018
    Posts:
    34
    Thank you very much Vryken. The information is very useful. The struct approach seems the way to go. I'll give that a shot.
     
    Vryken likes this.
  4. ELWATONA

    ELWATONA

    Joined:
    Jul 6, 2021
    Posts:
    1
    Hi! Im currently using ur code and im trying to make a CustomEditor for it but I'm facing the problem that
    <TParameter>
    cant be edited in Inspector from a CustomEditor.
    Code (CSharp):
    1. public abstract class GameEventEditor<TParameter> : Editor
    2.     {
    3.         public TParameter Arguments;
    4.         public SerializedObject SerializedObject;
    5.         public SerializedProperty ArgumentProperty;
    6.         public void OnEnable()
    7.         {
    8.             SerializedObject = new SerializedObject(this);
    9.         }
    10.         public override void OnInspectorGUI()
    11.         {
    12.             base.OnInspectorGUI();
    13.             GUI.enabled = true;
    14.  
    15.             EditorGUI.BeginChangeCheck();
    16.             ArgumentProperty = SerializedObject.FindProperty("Arguments");
    17.             Debug.Log(ArgumentProperty.editable);
    18.  
    19.             EditorGUILayout.PropertyField(ArgumentProperty);
    20.  
    21.             BaseGameEvent<TParameter> e = target as BaseGameEvent<TParameter>;
    22.  
    23.             if(GUILayout.Button("Raise"))
    24.             {
    25.                 e.Raise(Arguments);
    26.             }
    27.  
    28.             if(EditorGUI.EndChangeCheck()) SerializedObject.ApplyModifiedProperties();
    29.         }
    30.     }
    Screenshot_2.png

    After many attempts I'm thinking that maybe adding a variable inside
    BaseGameEvent 
    that holds the values from
    TParameter 
    could be the solution, but my problem now is that I don't know how to reference it inside the
    GUILayout.Button
    logic


    Code (CSharp):
    1.     public abstract class BaseGameEvent<TParameter> : ScriptableObject
    2.     {
    3.         public TParameter Parameter;
    4.         [SerializeField] private List<IEventListener<TParameter>> _listeners = new List<IEventListener<TParameter>>();
    5.  
    6.         public void Raise(TParameter parameter)
    7.         {
    8.             for (int i = _listeners.Count - 1; i >= 0; i--)
    9.             {
    10.                 _listeners[i].RaiseEvent(parameter);
    11.             }
    12.         }
    13.         public void RegisterListener(IEventListener<TParameter> listener)
    14.         {
    15.             if (!_listeners.Contains(listener)) { _listeners.Add(listener); }
    16.         }
    17.         public void UnRegisterListener(IEventListener<TParameter> listener)
    18.         {
    19.             if (_listeners.Contains(listener)) { _listeners.Remove(listener); }
    20.         }
    21.     }
    Screenshot_3.png
    Any suggestions?
     
    Last edited: Jan 12, 2023