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

Triggering Events to specific object

Discussion in 'Scripting' started by sdviosx, Jul 20, 2015.

  1. sdviosx

    sdviosx

    Joined:
    Jan 4, 2015
    Posts:
    22
    Im using the event manager found here:

    http://www.willrmiller.com/?p=87

    This event manager is used globally and events are sent to everyone, this is perfect for updating broad objects.

    What is the best way for updating specific game objects/components? For example, I have 5 traps in a level that all listen to the TriggerTrapEvent, this event is raised if the player pushes a button, since all of these traps listen to this event all of them will be triggered at once and that is something I don't want, each button should trigger a specific trap. I managed to do what I was looking for but I need a direct reference(tight coupling) to the specific trap that the trigger button will trigger. This is one of many cases for triggering an event on a specific object.

    This is a simple sample of what I have done.
    Code (CSharp):
    1. public class Turret : Trap
    2. {
    3.     void Start ()
    4.     {
    5.         EventMessenger.Instance.AddListener<TriggerTrapEvent>(OnTriggered);
    6.     }
    7.  
    8.     void OnDistroyed ()
    9.     {
    10.         EventMessenger.Instance.RemoveListener<TriggerTrapEvent>(OnTriggered);
    11.     }
    12.  
    13.     // When TriggerTrapEvent is raised every single turrets OnTrigger method will be called.
    14.     public void OnTriggered (TriggerTrapEvent e)
    15.     {
    16.         if (e.trap.Equals(this))
    17.         {
    18.             print ("Trap triggered ");
    19.             StartCoroutine("FireProjectile");
    20.         }
    21.     }
    22. }
    Trigger Trap Event
    Code (CSharp):
    1. public class TriggerTrapEvent : GameEvent
    2. {
    3.     public Trap trap {get; private set;}
    4.     public TriggerTrapEvent (Trap _trap)
    5.     {
    6.         trap = _trap;
    7.     }
    8.  
    9.     public Trap GetTrap ()
    10.     {
    11.         return trap;
    12.     }
    13. }
    Button trigger script
    Code (CSharp):
    1. public class Trigger : MonoBehaviour
    2. {
    3.     // Need a direct reference to the trap this button is going to trigger(tight coupling)
    4.     public Trap trapScript;
    5.     private TriggerTrapEvent triggerEvent;
    6.    
    7.     void Start ()
    8.     {
    9.         triggerEvent = new TriggerTrapEvent(trapScript);
    10.     }
    11.  
    12.     void OnTriggerEnter(Collider other)
    13.     {
    14.         if (other.tag == "Player" || other.tag == "Boulder")
    15.         {
    16.             print ("player entered trigger button");
    17.  
    18.             // Fire the trap this button should trigger
    19.             EventMessenger.Instance.Raise<TriggerTrapEvent>(triggerEvent);
    20.         }
    21.     }
    22. }
     
  2. gorbit99

    gorbit99

    Joined:
    Jul 14, 2015
    Posts:
    1,350
    I think you could make that function private or static, and then with trap.GetComponent<ScriptName>.TriggerTrapEvent, but I never tried that out.
     
  3. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    You could give it a name / id and use that instead of a direct reference but there's no "real" alternative to solving this problem - a specific trigger firing a specific trap is going to require one of the objects to know about the other in some capacity.

    Your code looks fantastically well insulated.
     
    sdviosx likes this.
  4. sdviosx

    sdviosx

    Joined:
    Jan 4, 2015
    Posts:
    22
    Thank You,
    I am going to use an enum, I will see how that goes as having many if statements on "OnTriggered" method to check for specific objects might be error prone. In this case having a direct reference of the trap might not be much of a big deal.
     
  5. Chuckalicious

    Chuckalicious

    Joined:
    Jul 28, 2012
    Posts:
    51
    Please help me understand....I looked at this "event manager" example in the link above from sdviosx. I also reviewed this very similar approach in the book "Pro Unity Game Development with C#" found here http://4free-ebooks.com/ebook/pro-unity-game-development-with-c/6bov9wufj chapter 3.

    So you basically have a singleton class managing events for posters and subscribers for unknown types. In the example, from the book he uses SendMessage to communicate the event back to subscribers. In the link above, a more generic delegate (something I'm more accustom to) is used and can be subscribed to.

    I understand using SendMessage can have a performance impact. How does this approach (from the book) compare to the link example AND why use a singleton event manager class? Why not just declare your event delegates in the class raising these events and have other classes subscribe to those delegates?? What are we gaining here by creating a "event manager" class?

    EXAMPLE FROM BOOK
    NotificationManager, create an empty game object and attach this script....

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class NotificationsManager : MonoBehaviour {
    6.  
    7.     //http://www.willrmiller.com/?p=87 - another example of an event manager class
    8.  
    9.     private Dictionary<string, List<Component>> _listeners = new Dictionary<string, List<Component>>();
    10.  
    11.     public void AddListener(Component sender, string notificationName)
    12.     {
    13.         if (!_listeners.ContainsKey(notificationName))
    14.             _listeners.Add(notificationName, new List<Component>());
    15.  
    16.         _listeners[notificationName].Add(sender);
    17.     }
    18.  
    19.     public void PostNotification(Component sender, string notificationName)
    20.     {
    21.         if (!_listeners.ContainsKey(notificationName)) return;
    22.  
    23.         foreach(Component c in _listeners[notificationName])
    24.         {
    25.             c.SendMessage(notificationName, sender, SendMessageOptions.DontRequireReceiver);
    26.         }
    27.     }
    28.  
    29.     public void RemoveListener(Component sender, string notificationName)
    30.     {
    31.         if (!_listeners.ContainsKey(notificationName)) return;
    32.  
    33.         //The loop decrements backward through the list rather than increments forward,
    34.         //because as items are deleted, the list length or size reduces each time,
    35.         //which can invalidate the iterator i, if it increments.
    36.         for(int i = _listeners[notificationName].Count - 1; i >= 0; i--)
    37.         {
    38.             if(_listeners[notificationName][i].GetInstanceID() == sender.GetInstanceID())
    39.             {
    40.                 _listeners[notificationName].RemoveAt(i);
    41.             }
    42.         }
    43.     }
    44.  
    45.     public void RemoveRedundancies()
    46.     {
    47.         var TmpListeners = new Dictionary<string, List<Component>>();
    48.  
    49.         foreach(var Item in _listeners)
    50.         {
    51.             for(int i = Item.Value.Count-1; i>=0; i--)
    52.             {
    53.                 if(Item.Value[i] == null)
    54.                 {
    55.                     Item.Value.RemoveAt(i);
    56.                 }
    57.             }
    58.  
    59.             if (Item.Value.Count > 0)
    60.             {
    61.                 TmpListeners.Add(Item.Key, Item.Value);
    62.             }
    63.         }
    64.  
    65.         _listeners = TmpListeners;
    66.     }
    67.  
    68.     public void ClearListeners()
    69.     {
    70.         _listeners.Clear();
    71.     }
    72.  
    73.     private void OnLevelWasLoaded()
    74.     {
    75.         RemoveRedundancies();
    76.     }
    77. }
    78.  
    This is the script to add to objects posting events...

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Poster : MonoBehaviour {
    5.  
    6.     public NotificationsManager Notifications = null;
    7.    
    8.     // Update is called once per frame
    9.     void Update () {
    10.         //CheckMouseClick();
    11.     }
    12.  
    13.     void OnMouseOver()
    14.     {
    15.         if (Input.GetMouseButtonDown(0))
    16.         {
    17.             var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    18.             RaycastHit hit;
    19.             if (Physics.Raycast(ray, out hit))
    20.             {
    21.                 if (hit.collider.name == "Poster" && Notifications != null)
    22.                 {
    23.                     Notifications.PostNotification(this, "OnMouseClick");
    24.                 }
    25.             }
    26.         }
    27.     }
    28. }
    29.  
    This is the code to add to an object subscribing to an event....

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Listener : MonoBehaviour {
    5.  
    6.     public NotificationsManager Notifications = null;
    7.     private float time = 0;
    8.     private Color original;
    9.  
    10.     void Start()
    11.     {
    12.         original = gameObject.GetComponent<Renderer>().material.color;
    13.  
    14.         if(Notifications != null)
    15.         {
    16.             Notifications.AddListener(this, "OnMouseClick");
    17.         }
    18.     }
    19.  
    20.     void Update()
    21.     {
    22.         if (time > 0)
    23.             time -= Time.deltaTime;
    24.         else
    25.         {
    26.             gameObject.GetComponent<Renderer>().material.color = original;
    27.             time = 0;
    28.         }
    29.     }
    30.    
    31.     public void OnMouseClick(Component sender)
    32.     {
    33.         gameObject.GetComponent<Renderer>().material.color = Color.blue;
    34.         time = 5;
    35.     }
    36. }
    37.  
     
    Last edited: Jul 21, 2015
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Most of this stuff can be replaced with UnityEvents, the EventSystem, and ExecuteEvents.
     
  7. Chuckalicious

    Chuckalicious

    Joined:
    Jul 28, 2012
    Posts:
    51
    Thanks BoredMormon. I understand the Messaging System has replaced SendMessage and how it works. My question was more of "why use event managers"? I mean my classes may declare event delegates that other classes can subscribe to. I can also manage creation/destruction of events, checks for subscribers and callbacks all within the class itself. Why would I move all the event handling into a manager class. Is this a new best practice in programming in general or just with game programming?
     
  8. sdviosx

    sdviosx

    Joined:
    Jan 4, 2015
    Posts:
    22
    In terms of use they both trigger events to object(s). The Type-Safe event manager is (as the name implies) type safe, this ensures the system knows what type of event is being passed at compile time which makes debugging, avoiding typos and refactoring a lot easier as opposed to using string names as the listener type.

    The benefit of having an event manager as a singleton apart from the obvious single instance is that you can call events from anywhere. The event manager provides a layer of abstraction that decouples the communication between objects which provides better encapsulation. The send message function needs a reference(tightly coupled) to an object(s) to send it messages.
     
  9. Chuckalicious

    Chuckalicious

    Joined:
    Jul 28, 2012
    Posts:
    51
    Thanks sdviosx. I agree the SendMessage method (much like its successor Messaging System http://docs.unity3d.com/Manual/MessagingSystem.html) are both tightly coupled. I really like the fact the first example is type-safe where the book example uses generics and string names. Also when I got to the section to create a method for "Removing Redundancies" this books example code started to smell. OK, so my question was answered in two words "decoupling" and "encapsulation". Awesome. Thank you.
     
  10. Chuckalicious

    Chuckalicious

    Joined:
    Jul 28, 2012
    Posts:
    51
    Hey sdviosx, did you ever resolve your challenge above? How did you go about it? (I know it was some time back...just curious. I can see how I might run into this same issue at some point.)
     
  11. sdviosx

    sdviosx

    Joined:
    Jan 4, 2015
    Posts:
    22
    Not really I haven't had the time to get to it, but I plan on using a simple "MessageID" enum so I can check on the "OnTriggered" method.

    This can get ugly though.
    Code (CSharp):
    1. if (e.enumExample == TriggerTrapEvent.MyEnum.EnumOne)
    2.         {
    3.             // Do something
    4.         }
    5.  
    6.         else if (e.enumExample == TriggerTrapEvent.MyEnum.EnumTwo)
    7.         {
    8.             // Do something else
    9.         }
    Another thing to note about this messaging system is that, in my case a level Is going to have more than 5 turrets since all 5 turrets have the "OnTriggered" method, if I raise the trigger event on a specific turret all five listeners are going to trigger. You might also want to implement a way to check if a particular event has finished so it can trigger other event(s) similar to this event system.

    Here are other event managers I have found:
    Message Bus
    Bibdy Blog

    Unity's recent videos on Unite Europe - Talks briefly about their event system
    Making Super Dungeon Bros Super
     
    Last edited: Jul 23, 2015
  12. Chuckalicious

    Chuckalicious

    Joined:
    Jul 28, 2012
    Posts:
    51
    So I think I just ran into this issue. All my clone instances register an event with my event manager. When the event fires all subscribers are notified. What I did was, stored the object that triggered the event in a variable. Then fired off the event. Those listening would evaluate the sender and check the property to see the sender is the same gameobject that fired the event. For example:

    Code (CSharp):
    1.  
    2. //On my bullet prefab is a script that captures the event when hitting an enemy
    3.  
    4. public GameObject enemyHit;
    5.  
    6. void OnTriggerEnter2D(Collider2D other)
    7. {
    8.             if (other.tag == "Enemy")
    9.             {
    10.                 enemyHit = other.gameObject;//Store the object we hit
    11.  
    12.                 if (notificationsManager != null)
    13.                 {
    14.                    //Post the event
    15.                     notificationsManager.PostNotification(this, "ColliderHit");
    16.                 }
    17.             }
    18. }
    19.  
    20. //The enemy being hit (or anyone subscribing to the event "ColliderHit"
    21. //can determine if THEY are the object that caused the event
    22. //by checking the property on the script that fired the event
    23.     void Start () {
    24.         _rigidbody2D = transform.GetComponent<Rigidbody2D>();
    25.  
    26.         if (notificationsManager != null)
    27.         {
    28.             notificationsManager.AddListener(this, "ColliderHit");
    29.         }
    30.     }
    31.     void ColliderHit(Component sender)
    32.     {
    33.         if (sender is FireBolt)
    34.         {
    35.             //here we look at senders property to see if it is us
    36.             if (((FireBolt)sender).enemyHit.Equals(gameObject))
    37.             Health -= ((FireBolt)sender).damageAmount;
    38.         }
    39.  
    40.         if(Health <= 0)
    41.         {
    42.             gameObject.SetActive(false);
    43.         }
    44.     }