Prevent Double Assign in blind Keys (Script level Medium/Hard)

Hi there,

normally I prefer to code everything myself, but to save time I used the super asset of One Wheel Studio , in order to bind the keys.

My problem is that I’m having a little trouble doing what I want to modify.

Here we have 2 scripts :

  • KeyBinding (attached to a prefab button)
  • and KeyBindingManager

Each KeyBinding prefab button handles a different key with : public enum KeyAction in the KeyBindingManager.

The entry is written like this :
if (Input.GetButtonDown(KeyCode.myKey))

if (KeyBindingManager.GetKeyDown(KeyAction.myEnumKeyAction))
{...}

My problem :
The user can bind the same KeyCode to two different KeyActions.
Which is a problem for me.

I don’t know how to go about finding a solution.

For now, what comes to mind would be,
be able to compare prefabs with their KeyAction,
and do a ChangeKeyCode(false);
If their two KeyCode are identical.

Does this seem correct to you ?

Here are the codes :

KeyBindingManager

using UnityEngine;
using System.Collections.Generic;


//static class that stores the key dictionary. The dictionary is loaded at runtime from Keybinding scripts.
//The keybinding scripts will load from the inspector unless there is a corresponding key in player prefs.

    public static Dictionary<KeyAction, KeyCode> keyDict = new Dictionary<KeyAction, KeyCode>();

    //Returns key code
    public static KeyCode GetKeyCode(KeyAction key)
    {
        KeyCode _keyCode = KeyCode.None;
        keyDict.TryGetValue(key, out _keyCode);
        return _keyCode;
    }

    //Use in place of Input.GetKey
    public static bool GetKey(KeyAction key)
    {
        KeyCode _keyCode = KeyCode.None;
        keyDict.TryGetValue(key, out _keyCode);
        return Input.GetKey(_keyCode);
    }

    //Use in place of Input.GetKeyDown
    public static bool GetKeyDown(KeyAction key)
    {
        KeyCode _keyCode = KeyCode.None;
        keyDict.TryGetValue(key, out _keyCode);
        return Input.GetKeyDown(_keyCode);
    }

    //Use in place of Input.GetKeyUP
    public static bool GetKeyUp(KeyAction key)
    {
        KeyCode _keyCode = KeyCode.None;
        keyDict.TryGetValue(key, out _keyCode);
        return Input.GetKeyUp(_keyCode);
    }

    public static void UpdateDictionary(KeyBinding key)
    {
        if (!keyDict.ContainsKey(key.keyAction))
            keyDict.Add(key.keyAction, key.keyCode);
        else
            keyDict[key.keyAction] = key.keyCode;
    }
}

//used to safe code inputs
//Add new keys to "bind" here
public enum KeyAction
{
    none,
    up,
    down,
    left,
    right,
    forward,
    backward,
    pause,
    jump,
    mute
}

KeyBinding

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System;

[System.Serializable]
//[RequireComponent(typeof(SaveKeyBinding))] //used to save data to database
public class KeyBinding : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{

    public delegate void remap(KeyBinding key);
    public static event remap keyRemap;

    //Name comes from enum in KeyBindingsManager scrip
    public KeyAction keyAction;
    //keycode set in inspector and by player
    public KeyCode keyCode = KeyCode.W;
    //Text to display keycode for user feedback
    public Text keyDisplay;

    //Used for color changing during key binding
    public GameObject button;
    public Color toggleColor = new Color(0.75f,0.75f,0.75f,1f);
    Image buttonImage;
    Color originalColor;

    //mouse rebinding
    public bool allowMouseButtons = true;

    //Internal variables
    bool reassignKey = false;
    Event curEvent;
    bool isHovering = false; //used for mouse controls

    //Changes in button behavior should be made here
    void OnGUI()
    {
        curEvent = Event.current;

        //Checks if key is pressed and if button has been pressed indicating wanting to re - assign
        if (curEvent.isKey && curEvent.keyCode != KeyCode.None && reassignKey)
        {
            keyCode = curEvent.keyCode;
            ChangeKeyCode(false);
            UpdateKeyCode();
            SaveKeyCode();
        }
        else if (curEvent.shift && reassignKey) //deals with some oddity in how Unity deals with shifts keys
        {
            if (Input.GetKey(KeyCode.LeftShift))
                keyCode = KeyCode.LeftShift;
            else
                keyCode = KeyCode.RightShift;

            ChangeKeyCode(false);
            UpdateKeyCode();
            SaveKeyCode();
        }
        //checks if mouse is pressed and assigns appropriate keycode
        else if (curEvent.isMouse && reassignKey && isHovering && allowMouseButtons)
        {
            StartCoroutine(Wait());//prevents "over clicking"
            //converts mouse button to keycode - see Keycode defintion for why 323 is added.
            int _int = curEvent.button + 323;
            KeyCode mouseKeyCode = (KeyCode)_int;

            keyCode = mouseKeyCode;
            ChangeKeyCode(false);
            UpdateKeyCode();
            SaveKeyCode();
 
        }
        //cancels binding if not hovering and mouse clicked
        else if(curEvent.isMouse && !isHovering)
        {
            ChangeKeyCode(false);
        }
    }

    public void OnPointerEnter(PointerEventData data)
    {
        isHovering = true;
    }

    public void OnPointerExit(PointerEventData data)
    {
        isHovering = false;
    }

    //Initializes
    void Awake()
    {
        buttonImage = button.GetComponent<Image>();
        originalColor = buttonImage.color;
        button.GetComponent<Button>().onClick.AddListener(() => ChangeKeyCode(true));
    }

    //Loads keycodes from player preferences
    void OnEnable()
    {
         Debug.Log("PrintOnDisable: script was enable");
        //Comment out this line it you want to allow multiple simultaneous assignments
        KeyBinding.keyRemap += PreventDoubleAssign;

        KeyCode tempKey;
        tempKey = (KeyCode) PlayerPrefs.GetInt(keyAction.ToString()); //saved by the keyType, but returns int that corresponds to keyCode --- sauvegardé par le keyType, mais retourne int qui correspond à keyCode

        if(tempKey.ToString() == "None")
        {
            Debug.Log(keyCode.ToString());
            keyDisplay.text = keyCode.ToString();
            UpdateKeyCode();
            SaveKeyCode();
        }
        else
        {
            keyCode = tempKey;
            keyDisplay.text = keyCode.ToString();
            UpdateKeyCode();
        }
    }

    void OnDisable()
    {
         Debug.Log("PrintOnDisable: script was disabled");
        KeyBinding.keyRemap -= PreventDoubleAssign;
    }

    //Called by button on GUI
    public void ChangeKeyCode(bool toggle)
    {
        reassignKey = toggle;

        if(toggle)
        {
            buttonImage.color = toggleColor;

            if(keyRemap != null )
                keyRemap(this);
        }
        else
            buttonImage.color = originalColor;
    }
 
    //saves keycode to player prefs
    public void SaveKeyCode()
    {
        keyDisplay.text = keyCode.ToString();
        PlayerPrefs.SetInt(keyAction.ToString(),(int)keyCode);
        PlayerPrefs.Save();
    }

    //Prevents user from remapping two keys at the same time
    void PreventDoubleAssign(KeyBinding kb)
    {
        if(kb != this)
        {
            reassignKey = false;
            buttonImage.color = originalColor;
        }
    }

    //updates dictionary on key bindings manager
    public void UpdateKeyCode()
    {
        KeyBindingManager.UpdateDictionary(this);
    }

    //prevent button from getting clicked.
    IEnumerator Wait()
    {
        button.GetComponent<Button>().onClick.RemoveAllListeners();
        yield return new WaitUntil(() => !Input.GetMouseButton(0));
        button.GetComponent<Button>().onClick.AddListener(() => ChangeKeyCode(true));
    }

}

What I want to achieve :
If I bind my key A to button 1, I would like it to be impossible for me to bind my key A to button B, regardless of the method used.

This is sort of a UI / UX question.

It can be annoying to be prohibited from using duplicate bindings, just because if you only want to swap A and B bindings, you need to first put in a fake C binding, then make A into C, then B into A (to avoid the dupe), then C back into B… confusing to the user.

Instead a better way is to have a final validation that takes place, usually when you leave the screen, where it checks for dupe bindings and does not let the user leave the screen.

You can even hook that validation to something that makes the “OKAY” button non-interactable as long as there is a duplicate binding. Good UI/UX would replace the button with “Fix dupe bindings first!” message.

As a bonus you can have a “reset to defaults” button to restore the defaults, which is always a nice thing to have anyway.

1 Like

I note, interesting ^^ very good idea “reset to defaults”

I had rather the idea of remaining asser conventional, in the idea or if there is a double choice the first returns to the null state.

I find it interesting the idea of the message, but rather integrated into an alert popub.

But before that I would already have to understand how the script works.

I had little hope that someone would answer me ^^

I hid the code with HIDES, hoping that someone a little less sane than the others would have the courage to read it :cry:
but i tried xDDDD Well.

I’ll have to get serious about this code from hell.