KeyDown Delay In Editor Script OnSceneGUI

I’m creating an editor script that translates objects with hotkeys and mouse instead of having to click and drag a gizmo. The user can select an object, hold down X on the keyboard, and move their mouse to have the object transform along the X axis. I do this with a custom editor for Transform, and by listening for the KeyDown event in OnSceneGUI.

I’m handling my translations inside of this code snippet (snipped for readability. Behavior is still the same).

    private void OnSceneGUI()
    {
        Event current = Event.current;

        if (current.type == EventType.KeyDown)
        {
            Debug.Log(current.type + " " + current.keyCode.ToString());
            //Do stuff
        }

    }

However, I notice there is a delay between when I press the key down, and when it starts to repeat while the key is held down. The problem is demonstrated in the attached video.

Note how after the X key is pressed, there is nearly a second before object actually moves, and the console says the key is being pressed. I would expect that the entire time I held down my key, the event would be fired consistently. This doesn’t seem to be the case here.

Same behavior if I mix it with other event types (KeyUp, Repaint, ect). Is there a correct way to do what I’m trying to do here?

Almost certainly the OS’ key repeat delay. Set a bool in KeyDown and toggle it off in KeyUp, then run your logic in Repaint?

1 Like

Thanks for the advice! I’m still getting the exact same behavior with this implementation though.

    private void OnSceneGUI()
    {
        Event current = Event.current;

        if (current.type == EventType.KeyDown)
        {
            switch (current.keyCode)
            {
                case KeyCode.Z:
                    keyDown = true;
                    break;

                case KeyCode.X:
                    keyDown = true;
                    break;

                case KeyCode.C:
                    keyDown = true;
                    break;
            }
            SceneView.RepaintAll();
        }

        if (current.type == EventType.KeyUp)
        {
            if (current.keyCode == KeyCode.X || current.keyCode == KeyCode.Z || current.keyCode == KeyCode.C)
                keyDown = false;
        }

        if (current.type == EventType.Repaint && keyDown)
        {
            Debug.Log(current.type + " " + current.keyCode.ToString());
        }
        SceneView.RepaintAll();

    }

This can’t be, I used your code pretty much verbatim and I get the typical 200 calls per second when you force a repaint (which btw is a terrible idea to do that all the time).

Here’s a quick example of an “always active editor tool” that does move all top level selected transforms by how much the mouse was moved in the x direction. This approach has several flaws. First of all, Unity already uses the x and z keys globally to switch between global / local and pivot / center mode. So those keys would conflict with that purpose. Of course we can constrict the usage only when an object is selected in a sceneview which does work, however it’s a bit hacky.

Example CustomMoveTools.cs

using UnityEngine;
using UnityEditor;

[InitializeOnLoad]
public class CustomMoveTools
{
    static bool keyDown = false;
    static Vector3 axis;
    static Vector2 lastPos;
    static CustomMoveTools()
    {
        SceneView.beforeSceneGui += OnSceneGUI;
    }

    private static void OnSceneGUI(SceneView aView)
    {
        Event current = Event.current;

        if (current.type == EventType.KeyDown && Selection.activeTransform != null)
        {
            lastPos = current.mousePosition;
            switch (current.keyCode)
            {
                case KeyCode.Z:
                    keyDown = true;
                    axis = Vector3.forward;
                    current.Use();
                    break;

                case KeyCode.X:
                    keyDown = true;
                    axis = Vector3.right;
                    current.Use();
                    break;

                case KeyCode.C:
                    keyDown = true;
                    axis = Vector3.up;
                    current.Use();
                    break;
            }
        }
        else if (current.type == EventType.KeyUp)
        {
            if (keyDown && (current.keyCode == KeyCode.X || current.keyCode == KeyCode.Z || current.keyCode == KeyCode.C))
                keyDown = false;
        }
        else if (current.type == EventType.MouseMove && keyDown)
        {
            var delta = current.mousePosition - lastPos;
            lastPos = current.mousePosition;
            foreach (var obj in Selection.GetTransforms( SelectionMode.TopLevel))
            {
                obj.position += axis * delta.x * 0.1f;
            }
            SceneView.RepaintAll();
        }
    }
}

I registered our callback to the “beforeSceneGui” event because that allows us to properly intercept the key press so Unity does not handle it itself. Note that this general approach has several drawbacks. First of all since the “tool” is unaware of the scale of the currently viewed object, we can’t really choose a proper movement speed. I currently move by 0.1 unit per pixel move of the mouse. However if you have a smaller object and the camera is closer, that is probably too fast. Likewise if the object is larger and the camera further away it would be too slow. The handles solve this by calculating the handle size since they have spatial information of the handle available that they can reference. However we have the problem that when we select multiple objects at once, which do you use for speed reference?

This is generally difficult to solve.

Apart from that, tools like that should probably be implemented as an EditorTool. Replacing the Transform inspector is generally a bad idea.

1 Like