Difference between revisions of "Click through"

From PowerUI
Jump to: navigation, search
(Alternative design)
(Use Input.Unhandled)
 
(7 intermediate revisions by one other user not shown)
Line 38: Line 38:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
PowerUI tries to give your input system as much flexibility as possible, so the above route is considered relatively low level and which allows you to pipe those events wherever you need them, but requires a little more scripting to hook it up.
+
PowerUI tries to give your input system as much flexibility as possible, so the above route is considered relatively low level and which allows you to pipe those events wherever you need them, but requires a little more scripting to hook it up. PowerUI.Input.Unhandled is just an EventTarget instance which you can replace with a custom object too to e.g. divert everything.
  
 
=== Setup an EventTarget MonoBehaviour ===
 
=== Setup an EventTarget MonoBehaviour ===
  
This is the easier route, but involves PowerUI defining more of your input system. This also has the advantage of being able to correctly handle 3D [[Context Menu|context menu's]] too. Firstly, add something like the following to your project:
+
This is the easier route, but involves PowerUI defining more of your input system. This also has the advantage of being able to correctly handle 3D [[Context Menu|context menu's]] too. Firstly, tick the "Handle 3D Input" box in '''Window > PowerUI > Input Settings''', then typically all you'll need to do is just add "MouseEvent" to e.g. OnMouseDown. Here's an example:
 
 
''Design notes: This script will probably be built in. See the 'alternative design' section below.''
 
 
 
<syntaxhighlight lang="csharp">
 
using UnityEngine;
 
using System.Collections;
 
using PowerUI;
 
using Dom;
 
 
 
 
 
// First make it a Dom.IEventTarget:
 
public class GameObjectEventTarget : MonoBehaviour,IEventTarget {
 
   
 
    // An underlying event target which allows us to addEventListener etc:
 
    public EventTarget Target = new EventTarget();
 
   
 
    // IEventTarget requires the dispatchEvent method.
 
    // It's the same as the standard W3C dispatchEvent.
 
    // Define that too:
 
    public bool dispatchEvent(Dom.Event e){
 
   
 
        // We received some event!
 
        // This typically happens when the UI did not handle it,
 
        // or because an event was specifically dispatched to this gameObject.
 
        return Target.dispatchEvent(e);
 
   
 
    }
 
   
 
}
 
 
 
// Extend GameObject with a 'GetEventTarget' method.
 
public static class GameObjectExtensions{
 
 
    /// <summary>Gets or creates an EventTarget on a GameObject.</summary>
 
    public static EventTarget getEventTarget(this GameObject go){
 
        // Get the monobehaviour:
 
        GameObjectEventTarget monoBehaviour = go.GetComponent<GameObjectEventTarget>();
 
     
 
        if(monoBehaviour == null){
 
            // Add it:
 
            monoBehaviour = go.AddComponent<GameObjectEventTarget>();
 
        }
 
       
 
        // Return the target property:
 
        return monoBehaviour.Target;
 
    }
 
 
 
    /// <summary>Dispatches an event to a gameObject.
 
    /// Depending on the events settings, it may bubble up the scene hierarchy.</summary>
 
    public static bool dispatchEvent(this GameObject go, Dom.Event e){
 
        // Get the monobehaviour:
 
        GameObjectEventTarget monoBehaviour = go.GetComponent<GameObjectEventTarget>();
 
       
 
        if(monoBehaviour != null){
 
            monoBehaviour.dispatchEvent(e);
 
        }
 
       
 
        if(e.bubbles && !e.cancelBubble){
 
            // Bubble to the parent GO:
 
            Transform parent = go.transform.parent;
 
           
 
            if(parent!=null){
 
                return dispatchEvent(parent.gameObject, e);
 
            }
 
        }
 
       
 
        return !e.defaultPrevented;
 
    }
 
}
 
 
 
</syntaxhighlight>
 
 
 
Next, to use the above, simply use getEventTarget from within your MonoBehaviour:
 
  
 
<syntaxhighlight lang="csharp">
 
<syntaxhighlight lang="csharp">
  
 +
// A MonoBehaviour attached to a GameObject with a collider.
 
public class MyGameworldEvents : MonoBehaviour{
 
public class MyGameworldEvents : MonoBehaviour{
 
      
 
      
     public void Awake(){
+
     public void OnMouseDown(MouseEvent e){
         // Get the event target:
+
         // To 'convert' a method, all we've really done is add the MouseEvent parameter.
         var target = gameObject.getEventTarget();
+
         // That makes it much better at dealing with multitouch too.
 
          
 
          
        // Now we're getting somewhere interesting!
 
       
 
        // Add a mouse down event (referencing the method name):
 
        target.addEventListener("mousedown",OnMouseDown);
 
 
        // It works with whatever event type you support
 
        // (see also 'Custom events' in the Event Flow wiki).
 
        // Inline delegates are fine too:
 
        target.addEventListener("mouseup",delegate(MouseEvent e){
 
 
            // Mouse up.
 
        });
 
       
 
    }
 
 
    public void OnMouseDown(MouseEvent e){
 
   
 
 
         // This runs when this gameobject got clicked on
 
         // This runs when this gameobject got clicked on
 
         // and the UI was not.
 
         // and the UI was not.
Line 163: Line 74:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Alternative design ==
+
<syntaxhighlight lang="csharp">
  
PowerUI could use reflection like Unity does to look for OnMouseDown(MouseEvent e) and similar methods so your scripts could be like this instead:
+
// A MonoBehaviour attached to a GameObject with a collider.
 
 
<syntaxhighlight lang="csharp">
 
 
public class MyGameworldEvents : MonoBehaviour{
 
public class MyGameworldEvents : MonoBehaviour{
 
      
 
      
    [Handle("mousedown")]
+
     public void OnMissionComplete(MissionEvent e){
     public void OnMouseDown(MouseEvent e){
+
         // Your method must simply start with 'On' and accept exactly 1 event argument.
   
 
        // This runs when this gameobject got clicked on
 
         // and the UI was not.
 
 
          
 
          
 +
        // This will receive the above missioncomplete event.
 
     }
 
     }
 
    
 
    
 
}
 
}
</syntaxhighlight>
 
 
The bonus being it would easily work with all of your custom events too:
 
  
<syntaxhighlight lang="csharp">
 
public class MyGameworldEvents : MonoBehaviour{
 
   
 
    [Handle("missioncomplete")]
 
    public void OnMissionComplete(MissionEvent e){
 
        // A mission was completed
 
        // and the event was dispatched to this gameObject.
 
    }
 
 
 
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 
Any opinions?
 

Latest revision as of 18:18, 2 October 2017

It's very common to have game world objects which can be clicked/ tapped on. However, if your UI is over one of those objects, you might get something called 'click through' - where both the UI and your game world object handle the click.

Why click through happens

Unity doesn't provide a way to block the built in raycast which occurs when the screen is pressed or the mouse is clicked. Click through happens for Unity's built in GUI too. So, because PowerUI can't block it from happening, both Unity's raycast and PowerUI's element resolve occur at the same time - when both happen to get handled, then the click through scenario arises.

Avoiding it

Unity GUI and older versions of PowerUI have a flag which you check from your MonoBehaviour click methods (like OnMouseDown). Due to a wider variety of events (e.g. the touch events) and as PowerUI fully supports multi-touch (which the flag route fails for), there's now two potential routes - both involve catching the event after they've passed through the UI:


Use Input.Unhandled

See also the event flow. This special EventTarget receives all events which were not handled by your UI. If you click 'through' your UI, the mouse/touch events are sent here. Grab them using addEventListener:

PowerUI.Input.Unhandled.addEventListener("mousedown",delegate(MouseEvent e){
    
    // They clicked on *nothing!* (straight 'through' the UI).
    // Send this event wherever you'd like.
    // Note that the original raycast (and any GameObject it hit) are available:
    if(e.raySuccess){
        // It hit something! Use rayHit next.

        // Get the clicked gameObject:
        GameObject go = e.rayHit.gameObject;
        
        // Try getting a MonoBehaviour called 'MyInputScript':
        MyInputScript myInput = go.GetComponent<MyInputScript>();
        
        if(myInput != null){
            // Great - forward the event to it:
            myInput.MouseDown(e);
        }
    }
    
});

PowerUI tries to give your input system as much flexibility as possible, so the above route is considered relatively low level and which allows you to pipe those events wherever you need them, but requires a little more scripting to hook it up. PowerUI.Input.Unhandled is just an EventTarget instance which you can replace with a custom object too to e.g. divert everything.

Setup an EventTarget MonoBehaviour

This is the easier route, but involves PowerUI defining more of your input system. This also has the advantage of being able to correctly handle 3D context menu's too. Firstly, tick the "Handle 3D Input" box in Window > PowerUI > Input Settings, then typically all you'll need to do is just add "MouseEvent" to e.g. OnMouseDown. Here's an example:

// A MonoBehaviour attached to a GameObject with a collider.
public class MyGameworldEvents : MonoBehaviour{
    
    public void OnMouseDown(MouseEvent e){
        // To 'convert' a method, all we've really done is add the MouseEvent parameter.
        // That makes it much better at dealing with multitouch too.
        
        // This runs when this gameobject got clicked on
        // and the UI was not.
        
    }
  
}

This way you can dispatch any event to any gameObject too:

// Create whatever event type you'd like:
MissionEvent e= new MissionEvent("missioncomplete");

// Dispatch it to a gameobject:
aGameObject.dispatchEvent(e);
// A MonoBehaviour attached to a GameObject with a collider.
public class MyGameworldEvents : MonoBehaviour{
    
    public void OnMissionComplete(MissionEvent e){
        // Your method must simply start with 'On' and accept exactly 1 event argument.
        
        // This will receive the above missioncomplete event.
    }
  
}