1
votes

In my game I need to check for a specific keyboard combination, lets say Left Shift + B. If I do it normally with if(Input.GetKey(KeyCode.LeftShift) && Input.GetKey(KeyCode.B)) { Debug.Log("correct"); }

It will accept everything as long as it has those two controls, so Left Shift + B, Left Shift + C + B, Left Shift + any keyboard button to be honest + B will return true as well.

My question is if I can detect somehow if ONLY those two were pressed, I already tried going foreach char c in KeyCode and detecting whether that Input was my B and then setting bool to correct or false but that doesn't really work. Any ideas?

2
Sad to say there really isn't a good way of doing this. Iterating through each key each update just to check if it was pressed or not is going to perform very poorly. You might be able to cut this down by only considering keys which are mapped to something in your game. I feel like such a control scheme would probably be pretty frustrating, though, so I'd carefully consider whether you actually want to do this or not. - DetectivePikachu

2 Answers

1
votes

Event.current and OnGUI

Though usually it is not used so often anymore you can check the input in OnGUI using Event.current.

Every time a key goes down, store it in a list of currently pressed keys. When this keys goes up remove it from the list. Then you can simply check if the list contains the according keys and if the length matches the expected key amount.

public HashSet<KeyCode> currentlyPressedKeys = new HashSet<KeyCode>();

private void OnGUI()
{
    if (!Event.current.isKey) return;

    if (Event.current.keyCode != KeyCode.None)
    {
        if (Event.current.type == EventType.KeyDown)
        {
            currentlyPressedKeys.Add(Event.current.keyCode);
        }
        else if (Event.current.type == EventType.KeyUp)
        {
            currentlyPressedKeys.Remove(Event.current.keyCode);
        }
    }

    // Shift is actually the only Key which is not treated as a
    // EventType.KeyDown or EventType.KeyUp so it has to be checked separately
    // You will not be able to check which of the shift keys is pressed!
    if (!Event.current.shift)
    {
        return;
    }

    // As said shift is check on another way so we want only 
    // exactly 1 key which is KeyCode.B
    if (currentlyPressedKeys.Count == 1 && currentlyPressedKeys.Contains(KeyCode.B))
        Debug.Log("Only Shift + B");
}

It has to be done in OnGUI since there might be multiple events in one single frame. This is exclusive and will ony fire while Shift + B is pressed.

If you rather put this somewhere in your scene and make the values static

public class KeysManager : MonoBehaviour
{
    public static bool ShiftPressed;
    public static HashSet<KeyCode> currentlyPressedKeys = new HashSet<KeyCode>();

    private void OnGUI()
    {
        if (!Event.current.isKey) return;

        if (Event.current.keyCode != KeyCode.None)
        {
            if (Event.current.type == EventType.KeyDown)
            {
                currentlyPressedKeys.Add(Event.current.keyCode);
            }
            else if (Event.current.type == EventType.KeyUp)
            {
                currentlyPressedKeys.Remove(Event.current.keyCode);
            }
        }

        ShiftPressed = Event.current.shift;
    }
}

Then you can as before use something like

private void Update()
{
    if (KeysManager.ShiftPressed && KeysManager.currentlyPressedKeys.Count == 1 && KeysManager.currentlyPressedKeys.Contains(KeyCode.B))
    {
        Debug.Log("Only Shift + B exclusively should trigger this");
    }
}

Iterating with Input.GetKey through all KeyCode

Alternatively you could as commented check all possible keys.

However, this might or might not be an issue regarding performance. You'll have to test that and decide whether it is acceptable in your specific case.

using System.Linq;

...

// will store all buttons except B and LeftShift
KeyCode[] otherKeys;

private void Awake ()
{
    // This simply returns an array with all values of KeyCode
    var allKeys = (KeyCode[])Enum.GetValues(typeof(KeyCode));

    // This uses Linq Where in order to only keep entries that are different from
    // KeyCode.B and KeyCode.LeftShift
    // ToArray finally converts the IEnumerable<KeyCode> into a KeyCode[]
    otherKeys = allKeys.Where(k => k != KeyCode.B && k != KeyCode.LeftShift).ToArray();
}

private void Update()
{
    if(Input.GetKey(KeyCode.LeftShift) && Input.GetKey(KeyCode.B) && !AnyOtherKeyPressed())
    {
        // Happens while ONLY LeftShift + B is pressed
    }
}

// Return true if any other key
// is pressed except B and LeftShift
private bool AnyOtherKeyPressed()
{
    foreach (var keyCode in otherKeys)
    {
        if(Input.GetKey(keyCode)) return true;
    }

    return false;
}

Maybe we worry too much and it doesn't matter (I woudln't believe that but just theoretically ^^) than you could even take it one level up and make it more flexible.

[Serializable]
public class KeyCombo
{
    // Note I'll be lazy here .. you could create a custom editor 
    // for making sure each keyCode is unique .. but another time
    public List<KeyCode> keyCodes = new List<KeyCode>();

    // This will show an event in the Inspector so you can add callbacks to your keyCombos
    // this is the same thing used in e.g. Button onClick
    public UnityEvent whilePressed;

    // Here all other keyCodes will be stored
    [HideInInspector] public KeyCode[] otherKeys;

    // Return true if any other key
    // is pressed except B and LeftShift
    public bool AnyOtherKeyPressed()
    {
        foreach (var keyCode in otherKeys)
        {
            if (Input.GetKey(keyCode)) return true;
        }

        return false;
    }
}

public List<KeyCombo> keyCombos = new List<KeyCombo>();

private void Awake()
{
    // This simply returns an array with all values of KeyCode
    var allKeys = (KeyCode[])Enum.GetValues(typeof(KeyCode));

    foreach (var keyCombo in keyCombos)
    {
        // This uses Linq Where in order to only keep entries that are different from
        // the ones listed in keyCodes
        // ToArray finally converts the IEnumerable<KeyCode> into a KeyCode[]
        keyCombo.otherKeys = allKeys.Where(k => !keyCombo.keyCodes.Contains(k)).ToArray();
    }
}

private void Update()
{
    foreach (var keyCombo in keyCombos)
    {
        if (keyCombo.keyCodes.All(Input.GetKey) && !keyCombo.AnyOtherKeyPressed())
        {
            keyCombo.whilePressed.Invoke();
        }
    }
}

With this you can now add multiple KeyCombos and check them individually - However be aware that every additional keyCombo also means one additional iteration through all other keys so ... it's far away from perfect.

0
votes

You can use Event.current.modifiers if you're doing combined keystrokes like Alt + Q etc. Something like this

        if (!Event.current.isKey || Event.current.keyCode == KeyCode.None) return;
        switch (Event.current.type) {
            case EventType.KeyDown:
                if (Event.current.modifiers == EventModifiers.Alt) {

                    switch (Event.current.keyCode) {
                        case KeyCode.Q:
                            break;
                        default:
                            break;
                    }
                }
                break;
            default:
                break;
        }