How to save and call player defined keybinds that use modifiers

I’m working on my game’s keybinding and remapping features right now. I’ve got a singleton KeyManager class that works well for remapping single keys. It collects player defined remaps and saves them to PlayerPrefs. The bindings are called using a Dictionary kv pair. Here is the working Singleton.

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class KeyManager : MonoBehaviour
{
    public static KeyManager Instance { get; private set; }
    public static Dictionary<string, KeyCode> Keybinds = new Dictionary<string, KeyCode>();

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    void Start()
    {// add kv's to the Keybinds dictionary, key is a string defined here,
     // followed by a cast-to-Keycode value saved in PlayerPrefs
        Keybinds.Add("PitchUp", (KeyCode)System.Enum.Parse(typeof(KeyCode), PlayerPrefs.GetString("PitchUp", "W")));
        Keybinds.Add("PitchDown", (KeyCode)System.Enum.Parse(typeof(KeyCode), PlayerPrefs.GetString("PitchDown", "S")));
        Keybinds.Add("RotateLeft", (KeyCode)System.Enum.Parse(typeof(KeyCode), PlayerPrefs.GetString("RotateLeft", "A")));
        Keybinds.Add("RotateRight", (KeyCode)System.Enum.Parse(typeof(KeyCode), PlayerPrefs.GetString("RotateRight", "D")));

        //what follows below is where I need assistance
        Keybinds.Add("FacePrograde", (KeyCode)System.Enum.Parse(typeof(KeyCode), PlayerPrefs.GetString("FacePrograde", "W")));      //need shift-modifier + w
        Keybinds.Add("FaceRetrograde", (KeyCode)System.Enum.Parse(typeof(KeyCode), PlayerPrefs.GetString("FaceRetrograde", "S")));    //need shift-modifier + s
        Keybinds.Add("FacePort", (KeyCode)System.Enum.Parse(typeof(KeyCode), PlayerPrefs.GetString("FacePort", "A")));          //need shift-modifier + a
        Keybinds.Add("FaceStarboard", (KeyCode)System.Enum.Parse(typeof(KeyCode), PlayerPrefs.GetString("FaceStarboard", "D")));     //need shift-modifier + d

    }

The goal is to be able to define keybinds that use modifiers(like Shift) by default, as well as allow players to redefine keys using modifiers wherever they wish. I need a way to register KeyCode combinations like “Shift + W”, “Shift + S”, etc. and a way to save those into the dictionary. Is there a separate class that looks at KeyCodes as a combination of modifiers and non-modifiers? Or do I need 2 separate dictionaries? I’m not sure how I would implement 2 dictionaries though, below is an example of how these saved kv pairs are called in other classes.

void DetectManualInput()
    {
        if (Input.GetKey(KeyManager.Keybinds["PitchUp"]) || Input.GetKey(KeyManager.Keybinds["PitchDown"]) ||
            Input.GetKey(KeyManager.Keybinds["RotateLeft"]) || Input.GetKey(KeyManager.Keybinds["RotateRight"]))
        {
            //movement logic
        }
        if (Input.GetKeyDown(KeyManager.Keybinds["FacePrograde"]))
        {
            PressedFaceProgradeButton();
        }
        if (Input.GetKeyDown(KeyManager.Keybinds["FaceRetrograde"]))
        {
            PressedFaceRetrogradeButton();
        }
    }

I can think of really messy hard-code approaches to check for modifier keys, but I came here in the hopes that there is some built in functionality in C# to cover this that I’m not aware of. Thank you.

Hi!

The Keycode class is meant to represent the keys on a keyboard and not what they do when you press them.
If you open it up, you will notice that it differentiates between the numbers on the keypad and those above the letters, but it doesn’t even know the difference between a capital and a lowercase S.

This mean that you could just check if the player is pressing e.g. control and s at the same time:

if(Input.GetKey(KeyCode.LeftControl) && Input.GetKey(KeyCode.S)) //Save the game

Another option would be Input.inputString, which will show the input formatted as a string. This option does in fact differentiate between capital and lowercase S! It uses the standardized string, which is really just a char array.

You may need to mess around a bit, but I believe character combos are registered as one char (provided you’re using the right encoding). These will not be human readable, as some key combos are represented as weird foreign Unicode characters, or sometimes non-printing characters, but if you are bent on only having one item in your dictionary, this could be an option. You may need to do some testing to see if it works on all devices though.


However I frankly don’t see the need to do something like that. The KeyCode is more than sufficient to handle your need, and much more understandable. There is no need for 2 dictionaries, if you want to keep the same dictionary you have, you can simply store both keycodes like this:

if(Input.GetKeyDown(KeyManager.Keybinds["SaveGameModifier"]) && Input.GetKeyDown(KeyManager.Keybinds["SaveGameKey"]))

You could also store a keycode array for your dictionary and use extension methods for both power and simplicity:

public static class KeyCodeExtensions
{
    public static bool IsKeyComboPressed(this IEnumerable<KeyCode> keycodes)
    {            
        bool result = true;
        foreach (KeyCode keycode in keycodes)
        {
            if (!Input.GetKey(keycode)) //If any keys aren't being pressed, the combo isn't pressed.
            {
                result = false;
            }
        }
        return result;
    }
}

Usage would look like this:

if(KeyManager.Keybinds["SaveGame"].IsKeyComboPressed()) //Save the game

Either of these 2 ways would be nicer, easier, and more likely to work than wrangling together something with chars if you ask me.

I hope this helps!