Search code examples
c#unity-game-engineunity3d-editor

Get currently pressed keys in inspector


I am currently building a simple shortcuts system for my Unity project.

At the moment I am inserting the shortcuts in the inspector by string, for example: "Control+Alt+A". but writing it every time is too annoying.

So I thought about creating a custom editor for it, where the textbox would be filled automatically with the keys the user presses. Unfortunately the only way I found about getting this info is with the OnSceneGUI function, but it requires the scene window to have focus, and if the shortcut is defined in Unity it would fire.

To sum up, How can I read the currently pressed keys and display them with an editor script¿

Edit: this link almost does what I need, but it detects only one key (no combinations), and the Shift key isn't detected at all


Solution

  • Depends a lot on how you store and check the hotkeys later but you could create a custom EditorWindow pop-up.

    First I would store my shortcut e.g. like

    [Serializable]
    public struct ShortCut
    {
        public bool Ctrl;
        public bool Alt;
        public bool Shift;
    
        public KeyCode Key;
    
        public ShortCut(KeyCode key, bool ctrl, bool alt, bool shift)
        {
            Key = key;
            Ctrl = ctrl;
            Alt = alt;
            Shift = shift;
        }
    
        public override string ToString()
        {
            var builder = new StringBuilder();
            if (Ctrl) builder.Append("CTRL + ");
            if (Alt) builder.Append("ALT + ");
            if (Shift) builder.Append("SHIFT + ");
    
            builder.Append(Key.ToString());
    
            return builder.ToString();
        }
    
        public bool IsDown => IsCtrlDown && IsAltDown && IsShiftDown && Input.GetKeyDown(Key);
    
        private bool IsCtrlDown => !Ctrl || Input.GetKey(KeyCode.RightControl) || Input.GetKey(KeyCode.LeftControl);
        private bool IsAltDown => !Alt || Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt);
        private bool IsShiftDown => !Shift || Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
    }
    

    You can wait for it in a script like e.g.

    public class ShortCutTester : MonoBehaviour
    {
        // This youc an now call via the context menu of the someShortCut field in the Inspector
        [ContextMenuItem("Set Shortcut", nameof(SetSomeShortCut))]
        public ShortCut someShortCut;
    
        private void SetSomeShortCut()
        {
            ShortKeyDialog.ShowDialog(someShortCut, result =>
            {
                // mark as dirty and provide undo/redo funcztionality
                Undo.RecordObject(this, "Change someShortCut");
                someShortCut = result;
            });
        }
    
        private void Update()
        {
            if (someShortCut.IsDown)
            {
                Debug.Log("Hello!");
            }
        }
    
        #if UNITY_EDITOR
        // in order to catch all editor own key events on runtime
        // you will still get the warning "You have to exit playmode in order to save"
        // but at least this makes the Hello print anyway. Couldn't figure out how to catch the key completely before Unity handles it
        private void OnGUI()
        {
            var e = Event.current;
            e.Use();
        }
        #endif
    }
    

    and set it via a popup dialog

    public class ShortKeyDialog : EditorWindow
    {
        private ShortCut keys;
        private ShortCut newKeys;
        private Action<ShortCut> callback;
        private bool changed;
    
        public static void ShowDialog( ShortCut currentShortcut, Action<ShortCut> onDone)
        {
            var dialog = GetWindow<ShortKeyDialog>();
            dialog.keys = currentShortcut;
            dialog.newKeys = currentShortcut;
            dialog.callback = onDone;
            dialog.minSize = new Vector2(300, 200);
            dialog.position = new Rect(Screen.width / 2f + 150, Screen.height / 2f + 100, 300, 200);
            dialog.ShowModalUtility();
        }
    
        private void OnGUI()
        {
            //Get pressed keys
            var e = Event.current;
            if (e?.isKey == true)
            {
                switch (e.type)
                {
                    case EventType.KeyDown:
                        switch (e.keyCode)
                        {
                            // Here you will need all allowed keycodes
                            case KeyCode.A:
                            case KeyCode.B:
                            case KeyCode.C:
                            case KeyCode.D:
                            case KeyCode.E:
                            case KeyCode.F:
                            case KeyCode.G:
                            case KeyCode.H:
                            case KeyCode.I:
                            case KeyCode.J:
                            case KeyCode.K:
                            case KeyCode.L:
                            case KeyCode.M:
                            case KeyCode.N:
                            case KeyCode.O:
                            case KeyCode.P:
                            case KeyCode.Q:
                            case KeyCode.R:
                            case KeyCode.S:
                            case KeyCode.T:
                            case KeyCode.U:
                            case KeyCode.V:
                            case KeyCode.W:
                            case KeyCode.X:
                            case KeyCode.Y:
                            case KeyCode.Z:
                            case KeyCode.F1:
                            case KeyCode.F2:
                            case KeyCode.F3:
                            case KeyCode.F4:
                            case KeyCode.F5:
                            case KeyCode.F6:
                            case KeyCode.F7:
                            case KeyCode.F8:
                            case KeyCode.F9:
                            case KeyCode.F10:
                            case KeyCode.F11:
                            case KeyCode.F12:
                            case KeyCode.F13:
                            case KeyCode.F14:
                            case KeyCode.F15:
                            case KeyCode.Comma:
                            case KeyCode.Plus:
                            case KeyCode.Minus:
                            case KeyCode.Period:
                                // etc depends on your needs I guess
                                changed = true;
                                newKeys = new ShortCut (e.keyCode, e.control, e.alt, e.shift);
    
                                // Refresh the EditorWindow
                                Repaint();
                                break;
                        }
                        break;
                }
    
                // Use all key presses so nothing else handles them 
                // e.g. also not the Unity editor itself like e.g. for CTRL + S
                e.Use();
            }
    
            EditorGUILayout.LabelField("Current Shortcut");
            EditorGUILayout.HelpBox(keys.ToString(), MessageType.None);
    
            EditorGUILayout.Space();
    
            EditorGUILayout.LabelField("Press buttons to assign a new shortcut", EditorStyles.textArea);
    
            EditorGUILayout.HelpBox(newKeys.ToString(), MessageType.None);
    
            EditorGUILayout.Space();
    
            EditorGUILayout.BeginHorizontal();
            {
                if (GUILayout.Button("Cancel"))
                {
                    Close();
                }
                EditorGUI.BeginDisabledGroup(!changed);
                {
                    if (GUILayout.Button("Save"))
                    {
                        callback?.Invoke(newKeys);
                        Close();
                    }
                }
                EditorGUI.EndDisabledGroup();
            }
            EditorGUILayout.EndHorizontal();
        }
    }
    

    Please note: Typed on smartphone so no chance to test and correct things currently but I hope the idea gets clear

    Will test and update asap when back on a PC