Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Events vs Normal Functions

Discussion in 'Scripting' started by Rphysx, Apr 11, 2014.

  1. Rphysx

    Rphysx

    Joined:
    Mar 26, 2014
    Posts:
    54
    Doesn't matter how much I strive to understand the usefullness of events, I simply can't see how they differs from normal Functions.

    Let's suppose that I need to check if a ball Object has or not touched the floor. Without using events, I'd check at every update() if the ball position in y-axys is the same as the floor. If it is, a function will be called in which something happens.

    So what are events useful for ? To do the same with events I need to create-an-istance-of-a-delegate-and-pass-it-in-the-declaration-of-an-event-that-will-then-define-a-method-which-will-be-triggered-if-something-in-different-assembly-as-been-subscribed-to-the-said-event-declaration-to-trigger-the-function-in-the-other-assembly-whaaat ?!

    I'd need to do all of this to actually change the function in Update() with an event ?
    Or pheraps i'm completely wrong and events may be triggered in this scenario even without the use of the update() function ?
    I mean i'd see the great use of events if in this case for example it would ignite without having to check everytime the y-axys of both objects but it is not the case (as far as I understood)

    I find my self always relying to update() if I need to do particular behaviour in case somethings happens, is it wrong ? Because generally I end up checking for loads of information alltogheter in the same frame to understand if I should raise some methods or not, is this wrong ?

    What part of events I'm not understanding right ?
     
  2. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    It might be helpful to abstract your thinking a bit. The question isn't whether or not the ball is touching the floor but what happens as a result of the ball touching the floor. Does the player lose? Does the floor catch fire? What does it mean to do those things?

    We don't use them very often (and we more so just use delegates that we tack functionality onto at various points) but one example is some unit that a player can move by clicking in the level somewhere. You could make an OnMoveIssued event/delegate and then instead of making an Update method that just loops did we move yet-did we move yet-did we move yet you handle the user's mouse input and when they click you simply say unit.OnMoveIssued(postion). You can tack anything onto that. Fire up his state machine to get him moving. Start an animation. Play a sound. Light the floor on fire. Whatever you want.

    This also makes them notoriously difficult to debug :)
     
  3. Rphysx

    Rphysx

    Joined:
    Mar 26, 2014
    Posts:
    54
    Expecially talking about this, how events avoid the "did-we-move-yet" pattern ?
     
  4. metagrue

    metagrue

    Joined:
    Jan 28, 2013
    Posts:
    46
    Updates, by design, fire once per frame.

    Essentially, by using events causes there to be a new event, when the mouse is clicked, that fires the functionality on that frame, instead of once every single frame. This reduces code overhead, that is the amount of processing power you're using per frame. Mind you you'll still be using that same processing power, but only on the select frames instead of every single frame.

    Hope that makes sense.
     
  5. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Code (csharp):
    1.  
    2. public delegate void MoveDelegate(Vector3 position);
    3.  
    4. public MoveDelegate OnMoveIssued;
    5.  
    6. void Start()
    7. {
    8.     OnMoveIssued += delegate(position) { Debug.Log("I'm going to move! Watch me go!"); }
    9.     OnMoveIssued += delegate(position) { stateMachine.ChangeState(new MoveState(this, position)); }
    10.     if (playerOwnsUnit)
    11.     {
    12.         OnMoveIssued += delegate(position) { networkView.RPC("Move", RPCMode.Others, position); }
    13.     }
    14. }
    15.  
    16. // somewhere else that monitors input
    17. if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.MousePosition))
    18. {
    19.     mySelectedUnit.OnMoveIssued(hit.point);
    20. }
    21.  
    OnMoveIssued is loaded up with anonymous delegates that tell a unit to move and, if the player owns the unit, to send a network message to other players telling it to move as well.
     
  6. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,670
    [EDIT: The tl;dr is: Just read KelsoMRK's post, which beat mine in time, and provides actual code relevant to your question. :)]

    You can read more about what rancid1 described in Microsoft's Polling and Event Notification knowledgebase article.

    Checking every frame in Update() is an example of polling.

    Relying on the subject to tell you when something happens is called event handling.

    One isn't necessarily better than the other. It's situational.

    Let's say you're on a car trip with kids. If every second the kids ask:

    Are we there yet?
    Are we there yet?
    Are we there yet?
    Are we there yet?
    Are we there yet?
    Are we there yet?
    Etc.....

    This is polling.

    If, instead, they take a nap, and when you get to Disneyland you wake them up and tell them, "We're here," this is an event.

    In many circumstances, an event system is more efficient than continually polling, and it can lead to cleaner code, since you don't have to write polling code. For example, can you imagine how messy it would be if you needed to do this:
    Code (csharp):
    1.  
    2. void OnLevelWasLoaded(int level) {...}
    3.  
    4. void OnTriggerEnter(Collider other) {...}
    5.  
    6. void OnTriggerStay(Collider other) {...}
    7.  
    8. void OnTriggerExit(Collider other) {...}
    9.  
    10. void Update() {
    11.     if (CheckLevelWasLoaded() == true) {
    12.         OnLevelWasLoaded(x);
    13.     }
    14.     if (CheckTriggerWasEntered() == true) {
    15.         OnTriggerEnter(x);
    16.     }
    17.     if (CheckIsInTrigger() == true) {
    18.         OnTriggerStay(x);
    19.     }
    20.     if (CheckTriggerWasExited() == true) {
    21.         OnTriggerExit(x);
    22.     }
    23. }
    24.  
    That Update() method could quickly become a mess. Because OnLevelWasLoaded(), OnTriggerEnter(), etc., function as events (not C# language events, but events nonetheless), you don't need that Update() method. There are other advantages, too, but in the context of this thread I think this is the big one.


    Event systems require a little overhead, though. If the event is going to fire frequently, like every frame, then it's usually more efficient to poll instead.
     
    Last edited: Apr 11, 2014
    Rickmc3280 likes this.
  7. Rphysx

    Rphysx

    Joined:
    Mar 26, 2014
    Posts:
    54
    So it's actually resonable to say that sometimes an event cannot provide what polling can do, and it's obligatory to use poll instead of events ? This is not actually an excuse to overwhelm update() function xD I'll do in any case my best to implement events in my next scripts to get familiar with the argument
     
  8. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,670
    They can both always get the job done. Depending on the situation, one might be better than the other. If a situation requires processing every frame, you might as well poll.
     
  9. bigdaddy

    bigdaddy

    Joined:
    May 24, 2011
    Posts:
    153
    There are other uses for events. When used with one of the Event Messenger classes from the wiki, they do a good job of decoupling your code.

    For example, when a unit dies, it broadcasts an event with itself as a parameter. My SceneManager listens for unit death events and updates the score, my AnalyticsManager listens for unit deaths and tracks who and where, my UnitSpawner listens for unit deaths and despawns the unit, etc. The unit doesn't know anything about any of these, it just knows it died.
     
  10. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,670
    I agree with you. But for completeness in this thread you can also decouple using polling. The observed subject can just set an attribute (such as dead/alive). The subject doesn't need to know who is polling it. In the scenario you described, the unit could mark itself as dead. The SceneManager could check the dead/alive attribute for every unit every frame. This would be an absolutely terrible way to implement it, though.

    On the other hand, say you have an object that follows the mouse position, and the nature of the game is that the mouse is constantly moving. It may make more sense to poll the current mouse position every frame, rather than making the mouse fire a "mouse moved" event every frame that needs to be handled by a separate handler.
     
  11. Rphysx

    Rphysx

    Joined:
    Mar 26, 2014
    Posts:
    54
    Okay at this point before starting the entire playlist of jamieking about delegates and events I'd just ask someone to help my invulnerable ignorance to understand how an event works to avoid the continuos polling for example to check if a ball as already or not touched the floor. As far as I'm starting to understand, polling is the damn easiest way to get a job of this kind done. But if I'm more likely not to waste resources and get overheads I should dive myself into asynchronous events programming. Is it right ? To countinuosly check for a ball.y position, either if you do it with an event already set or with a simple function to be polled in update() isn't it the same ?
     
  12. bigdaddy

    bigdaddy

    Joined:
    May 24, 2011
    Posts:
    153
    I wouldn't use an event to check if a ball has touched the floor unless its not a frequent occurrence.

    But lets assume the ball doesn't touch the floor often, in fact the ball touching the floor is a Bad Thing(tm) in the game. Let's also assume there's some information that different parts of the system want to know about when the ball touches the floor : 1) where it touched, 2) how long it was off the floor.

    So the ball will know a) when it touches the floor, b) where it touched, and c) how long it was up.

    In a polling strategy, the ball class would have to make public whether it had touched the floor, the location it touched and the time it stayed up. The other classes would need a reference to the ball class and in its Update loop, each would check the ball class to see if it touched the floor. If so it would then grab the information it needed from the ball class.

    (All this is not tested; I just scripted it in this message so there probably are errors)
    Code (csharp):
    1.  
    2. public class Ball : MonoBehaviour {
    3.  
    4.   public bool hasHitFloor;
    5.   public Vector3 hitLocation;
    6.   public float timeInAir;
    7.  
    8.   float startTime;
    9.  
    10.   void Start() {
    11.       startTime = Time.time;
    12.   }
    13.  
    14.   void OnCollisionEnter(Collider other) {
    15.       if (other.CompareTag("Floor") {
    16.           timeInAir = Time.time - startTime;
    17.           hitLocation = transform.position;
    18.           hasHitFloor = true;
    19.           Invoke("CleanUp", 0.1f); // we'll give the pollers some time to access then we'll clean up
    20.       }
    21.   }
    22.  
    23.   void CleanUp() {
    24.     Destroy(gameObject);
    25.   }
    26. }
    27.  
    28. public class Score : MonoBehaviour {
    29.  
    30.    int currentScore;
    31.    Ball ball;  
    32.  
    33.     void Start() {
    34.        GameObject ballGO = <Some way of finding the ball game object here>
    35.        if (ballGO != null) {
    36.           ball = ballGO.GetComponent<Ball>();
    37.        }
    38.     }
    39.  
    40.     void Update() {
    41.       if (ball != null) {
    42.          if (ball.hasHitFloor) {
    43.             currentScore += Mathf.RoundToInt(100 * ball.timeInAir);
    44.             ball = null;
    45.          }
    46.       }
    47.     }
    48. }
    49.  
    50.  
    51. public class HotSpot : MonoBehaviour {
    52.  
    53.    List<Vector3> hotspots;
    54.    Ball ball;  
    55.  
    56.    void Awake() {
    57.        hotSpots = new List<Vector3>();
    58.     }
    59.  
    60.     void Start() {
    61.        GameObject ballGO = <Some way of finding the ball game object here>
    62.        if (ballGO != null) {
    63.           ball = ballGO.GetComponent<Ball>();
    64.        }
    65.     }
    66.  
    67.     void Update() {
    68.       if (ball != null) {
    69.          if (ball.hasHitFloor) {
    70.             hotspots.add(ball.hitLocation);
    71.             ball = null;
    72.       }
    73.     }
    74. }
    75.  
    This should work fine and yeah you'll get a two polls every frame but not a big deal. But notice that each class that needs information from the ball class has to go about finding the ball class, and checking if it can find a ball class; also each frame check if the ball class is still around (not null)

    now for event type system
    Code (csharp):
    1.  
    2. // Struct to hold info that we pass around
    3. // We could pass around the ball class itself, but lets change it up :)
    4. public struct BallHitInfo {
    5.   public bool hasHitFloor;
    6.   public Vector3 hitLocation;
    7.   public float timeInAir;
    8. }
    9.  
    10. public class Ball : MonoBehaviour {
    11.  
    12.   float startTime;
    13.  
    14.   void Start() {
    15.       startTime = Time.time;
    16.   }
    17.  
    18.   void OnCollisionEnter(Collider other) {
    19.       if (other.CompareTag("Floor") {
    20.           BallHitInfo info = new BallHitInfo();
    21.           info.timeInAir = Time.time - startTime;
    22.           info.hitLocation = transform.position;
    23.           info.hasHitFloor = true;
    24.           Messenger<BallHitInfo>.Broadcast("BallHitFloor", info);
    25.           CleanUp(); // don't need to wait around this time
    26.       }
    27.   }
    28.  
    29.   void CleanUp() {
    30.     Destroy(gameObject);
    31.   }
    32. }
    33.  
    34. public class Score : MonoBehaviour {
    35.  
    36.    int currentScore;
    37.  
    38.     void Awake() {
    39.         Messenger<BallHitInfo>.AddListener("BallHitFloor", OnBallHitFloor);
    40.     }
    41.  
    42.     void OnDestroy() {
    43.         Messenger<BallHitInfo>.RemoveListener("BallHitFloor", OnBallHitFloor);
    44.     }
    45.  
    46.     void OnBallHitFloor(BallHitInfo info) {
    47.       currentScore += Mathf.RoundToInt(100 * info.timeInAir);
    48.     }
    49. }
    50.  
    51.  
    52. public class HotSpot : MonoBehaviour {
    53.  
    54.    List<Vector3> hotspots;
    55.  
    56.     void Awake() {
    57.        hotSpots = new List<Vector3>();
    58.         Messenger<BallHitInfo>.AddListener("BallHitFloor", OnBallHitFloor);
    59.     }
    60.  
    61.     void OnDestroy() {
    62.         Messenger<BallHitInfo>.RemoveListener("BallHitFloor", OnBallHitFloor);
    63.     }
    64.  
    65.     void OnBallHitFloor(BallHitInfo info) {
    66.         hotspots.add(info.hitLocation);
    67.     }
    68. }
    69.  
    Now Score's and HotSpot's OnBallHitFloor method won't be called until the Ball class broadcasts the message. Just to be explicit, the Messenger class(es) in the wiki uses C# delegates so the broadcast is not like Unity's BroadcastMessage.

    Now lets say you add another class, say Spawner, that waits some number of seconds after a ball has hit the floor to spawn another ball.

    Polling:
    Code (csharp):
    1.  
    2. public class Spawner : MonoBehaviour {
    3.  
    4.   public float waitTime = 2f;
    5.   public Transform ballObject;
    6.  
    7.   int ballCount = 0;
    8.   Ball ball;
    9.  
    10.   void Start() {
    11.      CreateBall();
    12.   }
    13.  
    14.   void Update() {
    15.       if (ball != null) {
    16.          if (ball.hasHitFloor) {
    17.             Invoke("CreateBall", waitTime);
    18.             ball = null;
    19.       }
    20.   }
    21.  
    22.   void CreateBall() {
    23.      Transform btrans = Instantiate(ballObject,  new Vector3((10-ballCount) * 2.0F, 0, 0), Quaternion.identity) as Transform;
    24.      ball = btrans.GetComponent<Ball>();
    25.      ballCount++;
    26.   }
    27. }
    28.  
    Events:
    Code (csharp):
    1.  
    2. public class Spawner : MonoBehaviour {
    3.  
    4.   public float waitTime = 2f;
    5.   public Transform ballObject;
    6.  
    7.     void Awake() {
    8.         Messenger<BallHitInfo>.AddListener("BallHitFloor", OnBallHitFloor);
    9.     }
    10.  
    11.     void OnDestroy() {
    12.         Messenger<BallHitInfo>.RemoveListener("BallHitFloor", OnBallHitFloor);
    13.     }
    14.  
    15.     void Start() {
    16.         CreateBall();
    17.     }
    18.  
    19.     void OnBallHitFloor(BallHitInfo info) {
    20.         Invoke("CreateBall", waitTime);
    21.     }
    22.  
    23.   void CreateBall() {
    24.      Instantiate(ballObject,  new Vector3((10-ballCount) * 2.0F, 0, 0), Quaternion.identity) as Transform;
    25.      ballCount++;
    26.   }
    27. }
    28.  
    Polling and Events are good strategies to use, but they have different scenarios. Do I want my camera to follow my player? Then I'd use polling in LateUpdate on the camera. Do I have something that happens not as frequent? I would look at Events.

    And just something additional to blow your mind... what if in the Polling example, the different polling classes don't look for the Ball class but instead listen for a message from the ball class? And that ball class broadcasts the message when it starts...

    Hope this helps.
     
  13. Rphysx

    Rphysx

    Joined:
    Mar 26, 2014
    Posts:
    54
    It really helped, thanks. Sure I'd try to make functions in the other classes (to increase score/store vector pos etc) that I'd have later invoked in the ball main class whenever it hit the floor, so that there won't be any need to check in updates if it has touched it or not, but I see this is made to help me out understanding a bit the difference between always relying on polling and trying to approach the event style scripting, which is more elegant/readable.
    As i'll grow as a programmer I'll definitey take a look on asynchronous functions/patterns to solve some polling situations. Thanks in any case, I got the difference now : )