OnSceneGUI and controlID's

Can somebody give me an insight how the scene events are controlled in OnSceneGUI() with their control ID’s?I know it is too general to ask, but i can’t find any descent tutorial or information about anything involved with control ID’s so i can put the pieces together. Maybe somebody can give a quick tutorial so people know how to use the OnSceneGUI event system? Let me try a quick few questions:

  • GUIUtility.GetControlID() - So the way i understand, the method returns a unique id integer for a control. What does the doc refer as a control here i don’t quite actually understand. Is the control referred as GUI element(GUI Button, Toggle, etc.) or Mouse Click or Keyboard Click?
  • GUIUtility.hotControl - I am also mystified by that variable. So when you assign a unique ID to the hot control, the control which you gave a unique ID will be the only one active when the mouse/keyboard button is down? Again here i am just guessing due the fact i don’t really know what the doc refers as control.
  • HandleUtility.AddDefaultControl() - “Add the ID for a default control. This will be picked if nothing else is.” I also want to know what this does and how it works.

I would of prefer not to ask so many questions here, but i urgently need that info so i will be very thankful if somebody has time to explain me how does all work in together.

The control ids you get from GUIUtility.GetControlID are “temporary” control ids which are valid and unique for the current call of OnGUI / OnSceneGUI / OnXXXGUI. The next time OnGUI is called GUIUtility.GetControlID will return the same ids in the same order.

Those ids are used by Unity to identify a control beyond the current execution. Since Unity uses an immediate mode there usually isn’t any data preserved after OnGUI has finished.

Yes, control ids refer to controls like Buttons, Toggles, … Those ids are mainly required for managing events between all the interactive controls or for storing generic information for a certani control.

GUIUtility.hotControl like you already said is used to filter events. All the “controls” will read this variable and if it isn’t “-1” or their own id they ignore the event. This filtering is usually done inside Event.current.GetTypeForControl or directly in the controls code. Unfortunately in the last versions of Unity they moved more and more of the control’s code from managed to native code. In the old UnityEngine.dlls you could find most of the logic for those controls.

HandleUtility.AddDefaultControl should be called during the Layout event to register a certain control id as the one that would get the events if no other control has taken it.

If you want to see some examples it’s the best you download ILSpy and take a look into the UnityEditor.dll located at "your Unity folder"/Data/Managed/. There you will find a lot of examples like the TerrainInspector which is one of the more complex “inspectors” out there.

If you’re quite new to the GUI system in general i suggest to take a look at my gui crash course :wink:

If you have more questions feel free to ask, either as new question or if it’s just a small question as comment.

For anyone wishing to create a custom element (for example, to drag a GUI rectangle inside of EditorWindow), bluckle-up and read this amazing post: by Max Anderson

in case if the link ever goes down, I will copy-paste the most important text from there:


At a high level, making a custom Handle involves understanding Events, Graphics, and “control IDs.”

Events will be “sent” to your function automatically via the static Event.current variable. Each event object has an EventType stored in the Event.type variable. The two most important event types you should familiarize yourself with are:

EventType.Layout – This is the first event sent during a repaint. You use it to determine sizes and positioning of GUI elements, or creation of control IDs, in order for future events to make sense. For example, how do you know if a mouse is inside a button if you don’t know where the button is?

EventType.Repaint – This is the last event sent during a repaint. By this point, all input events should have been handled, and you can draw the current state of your GUI based on all of the previous events. Handles.DrawLine only responds to this event, for example. There are many ways to draw to the screen during this event, but the Graphics API gives you the most control.

All other event types happen between these two, most of which are related to mouse or keyboard interactions. You can accomplish a lot with just this information, but in order to deal with multiple interactive objects from a single Editor.OnInspectorGUI call, you need to use “control IDs.”

Control IDs sound scary, but they’re really just numbers. Using them correctly is currently not well documented, so I’ll do my best to explain the process:

Before every event, Unity clears its set of Control IDs.

Each event handling function (Editor.OnInspectorGUI, Editor.OnSceneGUI, etc.) must request a Control ID for each interactible “control” in the GUI that can respond to mouse positions or keyboard focus. This is done using the GUIUtility.GetControlID function. Because this must be done for every event, the order of calls to GUIUtility.GetControlID must be the same during every frame. In other words, if you get a control ID during the Layout event, you MUST get that same ID for every other event until the next Layout event.

During the EventType.Layout event inside Editor.OnInspectorGUI, you can use theHandleUtility.AddControl function to tell Unity where each Handle is relative to the current mouse position. This part is where the “magic” of mapping mouse focus, clicks, and drags happens.

During every event, use the Event.GetTypeForControl function to determine the event type for a particular control, instead of globally. For example, a mouse drag on a single control might still look like a mouse move to all other controls.

I promise it’s easier than it sounds. As proof, here’s code that demonstrates how to properly register a handle control within Editor.OnSceneGUI:

int controlID = GUIUtility.GetControlID(FocustType.Passive);
Vector3 screenPosition = Handles.matrix.MultiplyPoint(handlePosition);

int controlID = GUIUtility.GetControlID(FocustType.Passive);
Vector3 screenPosition = Handles.matrix.MultiplyPoint(handlePosition);

switch (Event.current.GetTypeForControl(controlID))
{
    case EventType.Layout:
        HandleUtility.AddControl(
            controlID,
            HandleUtility.DistanceToCircle(screenPosition, 1.0f)
        );
        break;
}

Not so bad, right? The worst part of that is the HandleUtility.DistanceToCircle call, which takes the screen position of the handle and a radius, and determines the distance from the current mouse position to the (circular) handle. With this, you have a Handle in the Scene View that can recognize mouse gestures, but it doesn’t do anything yet.

To make it do something, we can add code for the appropriate mouse events to the switch statement:

case EventType.MouseDown:
    if (HandleUtility.nearestControl == controlID)
    {
        // Respond to a press on this handle. Drag starts automatically.
        GUIUtility.hotControl = controlID;
        Event.current.Use();
    }
    break;
    
case EventType.MouseUp:
    if (GUIUtility.hotControl == controlID)
    {
        // Respond to a release on this handle. Drag stops automatically.
        GUIUtility.hotControl = 0;
        Event.current.Use();
    }
    break;
    
case EventType.MouseDrag:
    if (GUIUtility.hotControl == controlID)
    {
        // Do whatever with mouse deltas here
        GUI.changed = true;
        Event.current.Use();
    }
    break;

You’ll see a few new things in that snippet:

HandleUtility.nearestControl [undocumented] – This is set automatically by Unity to the control ID closest to the mouse. It knows this from our previous HandleUtility.AddControl call.

GUIUtility.hotControl – This is a shared static variable that we can read and write to determine which control ID is “hot,” or in use. When start and stop a drag operation, we manually set and clear GUIUtility.hotControl for two reasons:

So we can determine if our handle’s control ID is hot during other events.

So every other control can know that it is NOT hot, and should not respond to mouse events.

Event.Use – This function is called when you are “consuming” an event, which sets its type to “Used.” All other GUI code should then ignore this event.

I mention that “drag starts / stops automatically” in the comments, which meansEventType.MouseDrag events will automatically be sent instead of EventType.MouseMovewhen a button is held down. You are still responsible for checking hot control IDs in order to know what handle is being dragged.

I didn’t put any useful code in there because at this point, the world is your oyster. Need to draw something? Add a case EventType.Repaint: block and draw whatever you like. Want to repaint your handle every time the mouse moves? Add a case EventType.MouseMove: block and callSceneView.RepaintAll [undocumented], which will force the Scene View to… repaint all!

Once again, you can find the post here