Okay guys, even though the code is great, i’ve still had some problems with it: when using overlapping buttons, it would fire button events for EVERY button. Obviously, i only want it to fire an event for the topmost button. Therefore i have come up with the following solution. It is based on Joe’s script and solves the problem of overlapping buttons (both: inside the same GUI.depth or with diferent GUI.depths). It also works with scrollviews (e.g. i have a main menu button, it’s description can be expanded and then is overlapping a scrollview, which also has buttons). It will fire the event only to the topmost button.
My first approach was to collect all button rect’s and their rollover states during the Event.current.Layout process, and then i will know which button is really the topmost. (This is possible because the button routine is always called TWICE per frame as probably everyone of you know …)
With this i had a solution for button clicks, but when there were overlapping buttons inside the same GUI.depth level, still all my buttons were hovering, including the ones “behind” another button. - Therefore i’ve added a “highest control ID” for rollovers, and this finally simplified the whole script and doesn’t need any hashtables anymore. The “highest control ID” is defined by using GUI.depth in combination with the controlID, and only hovered buttons will update it. At the end of the whole Event.current.Layout process, you will have processed all buttons and therefore know the definitive topmost hovered button. When the process status changes (i.e. the Event.current.type changes from EventType.Repaint back to EventType.Layout, a new frame has started and the highest control ID is reset to zero. - This will make sure that the routine starts collecting all button IDs again, so it will also by working for dynamic user interfaces where buttons are added or removed over time …
If you are using GUI.depth, make sure you’re only using values between 0 and 1000. (or, adjust the values in the script).
I have also added code for mobile devices, so the button doesn’t fire when he’s being dragged over. This way it is working nicely for me around and inside scrollViews etc.
This is what i came up with:
/*
*
* **** GUIButton CLASS ****
*
* this versions sends only events to the topmost button ...
*
*
* Fixes the bugs from the original GUI.Button function
* Based on the script from Joe Strout:
* http://forum.unity3d.com/threads/96563-corrected-GUI.Button-code-%28works-properly-with-layered-controls%29?p=629284#post629284
*
*
* The difference in this script is that it will only fire events (click and rollover!)
* for the topmost button when using overlapping buttons inside the same GUI.depth!
* Therefore the script finds the topmost button during the layout process, so it
* can decide which button REALLY has been clicked.
*
* Benefits:
* 1. The script will only hover the topmost button!
* (doesn't matter wheter the topmost button is defined via GUI.depth or via drawing order!)
* 2. The script will only send events to the topmost button (as opposed to Joe's original script)
* 3. The script works for overlapping buttons inside same GUI.depth levels,
* as well as for overlapping buttons using different GUI.depth values
* 4. The script also works when overlapping buttons over buttons inside scrollviews, etc.
*
* Usage: just like GUI.Button() ... for example:
*
* if ( GUIButton.Button(new Rect(0,0,100,100), "button_action", GUI.skin.customStyles[0]) )
* {
* Debug.Log( "Button clicked ..." );
* }
*
*
*
* Original script (c) by Joe Strout!
*
* Code changes:
* Copyright (c) 2012 by Frank Baumgartner, Baumgartner New Media GmbH, fb@b-nm.at
*
*
* */
using UnityEngine;
using System.Collections;
public class GUIButton
{
private static int highestDepthID = 0;
private static Vector2 touchBeganPosition = Vector2.zero;
private static EventType lastEventType = EventType.Layout;
private static bool wasDragging = false;
private static int frame = 0;
private static int lastEventFrame = 0;
public static bool Button(Rect bounds, string caption, GUIStyle btnStyle = null )
{
int controlID = GUIUtility.GetControlID(bounds.GetHashCode(), FocusType.Passive);
bool isMouseOver = bounds.Contains(Event.current.mousePosition);
int depth = (1000 - GUI.depth) * 1000 + controlID;
if ( isMouseOver depth > highestDepthID ) highestDepthID = depth;
bool isTopmostMouseOver = (highestDepthID == depth);
#if (UNITY_IPHONE || UNITY_ANDROID) !UNITY_EDITOR
bool paintMouseOver = isTopmostMouseOver (Input.touchCount > 0);
#else
bool paintMouseOver = isTopmostMouseOver;
#endif
if ( btnStyle == null )
{
btnStyle = GUI.skin.FindStyle("button");
}
if ( Event.current.type == EventType.Layout lastEventType != EventType.Layout )
{
highestDepthID = 0;
frame++;
}
lastEventType = Event.current.type;
if ( Event.current.type == EventType.Repaint )
{
bool isDown = (GUIUtility.hotControl == controlID);
btnStyle.Draw(bounds, new GUIContent(caption), paintMouseOver, isDown, false, false);
}
#if (UNITY_IPHONE || UNITY_ANDROID)
if ( Input.touchCount > 0 )
{
Touch touch = Input.GetTouch(0);
if ( touch.phase == TouchPhase.Began )
{
touchBeganPosition = touch.position;
wasDragging = true;
}
else if ( touch.phase == TouchPhase.Ended
( (Mathf.Abs(touch.position.x - touchBeganPosition.x) > 15) ||
(Mathf.Abs(touch.position.y - touchBeganPosition.y) > 15) )
)
{
wasDragging = true;
}
else
{
wasDragging = false;
}
}
else if ( Event.current.type == EventType.Repaint )
{
wasDragging = false;
}
#endif
// Workaround:
// ignore duplicate mouseUp events. These can occur when running
// unity editor with unity remote on iOS ... (anybody knows WHY?)
if ( frame <= (1+lastEventFrame) ) return false;
switch ( Event.current.GetTypeForControl(controlID) )
{
case EventType.mouseDown:
{
if ( isTopmostMouseOver !wasDragging )
{
GUIUtility.hotControl = controlID;
}
break;
}
case EventType.mouseUp:
{
if ( isTopmostMouseOver !wasDragging )
{
GUIUtility.hotControl = 0;
lastEventFrame = frame;
return true;
}
break;
}
}
return false;
}
}