Last active
November 21, 2018 06:36
-
-
Save stramit/4549fa11780a2440c9c3 to your computer and use it in GitHub Desktop.
SpecialEventClass
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* When developing the UI system we came across a bunch of things we were not happy | |
* with with regards to how certain events and calls could be sent in a loosely coupled | |
* way. We had this requirement because with a UI you tend to implement widgets that receive | |
* certain events, but you don't really want to have lots of glue code to manage them | |
* and keep track of them. The eventing interfaces we developed helped with this. One of | |
* the interesting things, is that they are not justfor the UI system! You can use this as | |
* a type-safe, fast, and simple alternative to SendMessage (never use SendMessage!). | |
* So how does it all work? | |
* There are a few parts that hook together to form the eventing system. | |
* The definition of your Event Interface (needs to extent IEventSystemHandler): | |
public interface IEventSystemHandler | |
{} | |
public interface IPointerClickHandler : IEventSystemHandler | |
{ | |
void OnPointerClick(PointerEventData eventData); | |
} | |
* What this does is define the function that will be called on the target. In | |
* this example we have the OnPointerClick function that is used by the UI system. | |
* This is implemented by a number of widgets we have. By implementing this interface | |
* you have defined a script that can receive the callback from the eventing system. | |
public class Button : Selectable, IPointerClickHandler | |
{ | |
protected Button() | |
{ } | |
... | |
// implementation of the click handler | |
// this will be executed by the event system | |
public virtual void OnPointerClick(PointerEventData eventData) | |
{ | |
if (eventData.button != PointerEventData.InputButton.Left) | |
return; | |
PressLogic(); | |
} | |
} | |
* The next part is learning how to invoke the event on a target GameObject. | |
* This involves using some static functions on the ExecuteEvents class: | |
// starting at object root, walk the hierarchy upwards towards the scene root until | |
// a GameObject is found where one of the components implements T | |
public static GameObject GetEventHandler<T>(GameObject root) | |
where T : IEventSystemHandler | |
// Can any component on GameObject go handle event T | |
public static bool CanHandleEvent<T>(GameObject go) | |
where T : IEventSystemHandler | |
// Execute the given function on the target gameobject | |
// with given eventdata returns true if it was handled | |
public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) | |
where T : IEventSystemHandler | |
// walk the heirarchy (towards parent) | |
// when a GameObject executes the event | |
// return the GameObject that handled the event | |
public static GameObject ExecuteHierarchy<T>(GameObject root, BaseEventData eventData, EventFunction<T> callbackFunction) | |
where T : IEventSystemHandler | |
* For the most part these functions are things for the UI system. If you look into | |
* the implementation of this (available on BitBucket) you can extend and add more | |
* utility. This is something we may or may not do in the future ourselves. | |
* https://bitbucket.org/Unity-Technologies/ui/src/ccb946ecc23815d1a7099aee0ed77b0cde7ff278/UnityEngine.UI/EventSystem/ExecuteEvents.cs?at=5.1 | |
* This gives an overview of the functions you can use to invoke a method on | |
* a loosely coupled GameObject, but we also need to tell the system which function to invoke. | |
* In the EventModules of the UI system we have a number of different calls we issue, | |
* PointerOver, PointerClick ect. A standard call to invoke one of these looks like this: | |
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerClickHandler); | |
* The last argument is a function pointer that we use and is defined like this: | |
private static readonly EventFunction<IPointerClickHandler> s_PointerClickHandler = Execute; | |
private static void Execute(IPointerClickHandler handler, BaseEventData eventData) | |
{ | |
// validate event data converts the EventData from the type Base, to the expected type. | |
handler.OnPointerClick(ValidateEventData<PointerEventData>(eventData)); | |
} | |
public static EventFunction<IPointerClickHandler> pointerClickHandler | |
{ | |
get { return s_PointerClickHandler; } | |
} | |
* The reason we need this mapping layer is that our interface could potentially have | |
* MORE than one function, and we need to know how to invoke against the correct | |
* function. This mapping redirects the call to the correct later. | |
* Lets look at a custom example of how we can write a custom event handler and | |
* invoke it :) | |
*/ | |
using UnityEngine; | |
using UnityEngine.EventSystems; | |
/// <summary> | |
/// This is the interface which we will invoke against | |
/// | |
/// It has two functions, one that takes an event data argument | |
/// and one that does not. We use the delegate binding code to | |
/// pick which function to invoke and with which arguments | |
/// </summary> | |
interface IRefreshHandler : IEventSystemHandler | |
{ | |
void RefreshWithData(CustomEventData data); | |
void RefreshWithoutData(); | |
} | |
/// <summary> | |
/// Custom event data to use when calling the Execute | |
/// function for the event system. In this example it's | |
/// pretty dumb and just has a simple public field. | |
/// </summary> | |
public class CustomEventData : BaseEventData | |
{ | |
public float publicField { get; set; } | |
public CustomEventData() : base(null) | |
{} | |
} | |
/// <summary> | |
/// The MonoBehaviour that has the IRefreshHandler associated with it. | |
/// It has calls that get invoked when clicking a button on the screen. | |
/// | |
/// The first uses a static delegate we have created. | |
/// The second uses a dynamically created delegate | |
/// </summary> | |
public class SpecialEventClass : UIBehaviour, IRefreshHandler | |
{ | |
// Static cached delegate. Will be created once. Nice for the GC | |
// This DOES not need to exist in this scope and can be reused between | |
// any classes that implement the IRefreshHandler. | |
// It's just in this class now for convinience. | |
private static readonly ExecuteEvents.EventFunction<IRefreshHandler> s_RefreshWithData | |
= delegate(IRefreshHandler handler, BaseEventData data) | |
{ | |
var casted = ExecuteEvents.ValidateEventData<CustomEventData>(data); | |
handler.RefreshWithData(casted); | |
}; | |
/// <summary> | |
/// A button that when clicked sends two messages. | |
/// </summary> | |
private void OnGUI() | |
{ | |
if (GUI.Button(new Rect(10, 10, 150, 100), "Execute Calls")) | |
{ | |
// First call uses the cached delegate | |
var eventData = new CustomEventData {publicField = 5.0f}; | |
ExecuteEvents.Execute(gameObject, eventData, s_RefreshWithData); | |
// anon delegate here | |
// nicer and simpler code, but bad for GC as each invoke it will do a 'new' internally to create the delegate | |
ExecuteEvents.Execute(gameObject, null, (IRefreshHandler handler, BaseEventData data) => handler.RefreshWithoutData()); | |
} | |
} | |
// Interface implementation. Do your specific logic here | |
public void RefreshWithData(CustomEventData data) | |
{ | |
Debug.Log(string.Format("Invoked RefreshWithData({0})", data.publicField)); | |
} | |
public void RefreshWithoutData() | |
{ | |
Debug.Log("Invoked RefreshWithoutData()"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment