Wednesday, February 4, 2015

Using Unity's Event Message System (Unity 4.6)

It's been a while since my last post, but typing away in my Info Tech class today really got me in the mood for a blog post. I'm happy to be back, so let's get started!


Unity recently updated it's messaging system...







It used to be as easy as:

target.SendMessage(string methodName);

Now it looks like this:

ExecuteEvents.Execute(GameObject target, BaseEventData eventData, EventFunction functor);

Lets look at how to utilize the new approach...





In this short demo we'll look at a simple example of how you could use the new message system and take advantage of Unity's EventSystem class. We'll be creating a custom event using the ExecuteEvents helper class and send events using IEventSystemHandler.

The SendMessage approach is still supported, so you can continue to use it. There's nothing stopping you, really. The way it works is you call SendMessage(FunctionName) on a GameObject, Unity will look for "FunctionName" on every script attached to that GameObject. If it finds one, it will call that function.

The new system is very similar, but now we will need to setup an Interface and implement it in the class we would like to receive messages. Our event that we will monitor is the mouse going over a UI object.


  
//This interface will notify Classes that use it MouseOver and MouseLeaving
public interface IMyMouseOver : IEventSystemHandler
{
 //Functions that can be called with the messaging system
 void MouseOver(string str);
 void MouseLeaving();
}

An Interface defines the communication link between two separate classes. In the class that we want to receive the event, we will need to implement the Interface. The requirements for implementing an Interface are fairly simple, first we'll add the Interface to the Class definition. To do this, put a coma after MonoBehavior and add in IMyMouseOver. Second, we will need to create the functions as defined by the Interface. In this case we have two: MouseOver(string str) and MouseLeaving() so they will be required in the class that we want to receive the event.



using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class MyDisplayText : MonoBehaviour, IMyMouseOver {

 public Text text;
 public void MouseOver(string str)
 {
  if(!text.enabled)
  {
   text.text = str;
   text.enabled = true;
  }

 }
 public void MouseLeaving()
 {
  if(text.enabled)
   text.enabled = false;
 }
} 

This class is very simple, I've set it up to enable some text when it receives the MouseOver function or to disable said text if the MouseLeaving function is called. Notice that we are implementing the Interface we created, it's called IMyMouseOver.

Lastly, we will need to define conditions for which our 'event' will occur. Basically, we will decide when the mouse pointer is over a UI Object and use ExecuteEvent to call the MouseOver() function. On Update we will use the GetMouseOver() function to raycast at the mouse position. The first object hit will be used as our target to call the GetMouseOver() function.



using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.EventSystems;

public class MyMouseOver : MonoBehaviour {

 GameObject prevObject;
 // Use this for initialization
 void Start () {
 
 }
 
 // Update is called once per frame
 void Update () {
 
  GameObject target = GetMouseOver ();
  if(target)
  {
     prevObject = target;
     ExecuteEvents.Execute<IMyMouseOver>(target, null, (x,y)=>x.MouseOver("Mouse Over Active"));
  }
  else
  {
   if(prevObject)
   {
      ExecuteEvents.Execute<IMyMouseOver>(prevObject, null, (x,y)=>x.MouseLeaving());
      prevObject = null;
   }

  }
 }
 //Raycasts at our mouse position and returns the first object hit
 private GameObject GetMouseOver()
 {
    PointerEventData pe = new PointerEventData(EventSystem.current);
    pe.position = Input.mousePosition;
    List<RaycastResult> hits = new List<RaycastResult>();
    EventSystem.current.RaycastAll( pe, hits );
    foreach(RaycastResult rr in hits)
    {
       GameObject go = rr.gameObject;
       if(go)
          return go;
    }

    return null;
 }
}
//This interface will notify Classes that use it MouseOver and MouseLeaving
public interface IMyMouseOver : IEventSystemHandler
{
   //Functions that can be called with the messaging system
   void MouseOver(string str);
   void MouseLeaving();
}





 In order for this code to work it requires a Canvas and a text object for us to enable/disable


 I've added the MyDisplayText script to the UI Objects that I want to display text




When we move the mouse over an object with the MyDisplayText script, the text is displayed!

As you can see our MyMouseOver handler script determines when the mouse is over an object, it then uses the IMouseOver Interface to make function calls. While the same thing could be accomplished with SendMessage, we're now more precisely defining the information we can pass between classes. By implementing an Interface, we can ensure our classes can communicate and know which functions are available. The performance of the new system is supposed to be more efficient as well.

What do you think are the advantages of using the new system? Or would you prefer to continue using SendMessage?






1 comment: