Custom Input Manager

Hello! I have written my own InputManager. Nothing to fancy, just to give me the ability to remap keys.
We have currently no need for custom key maps or whatever they are called in the New Input System which I’ve read a bit about.
This is what it looks like right now:

using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InputManager : MonoBehaviour
{
    public static InputPreset CurrentInputPreset = new InputPreset();

    public class Axis
    {
        public string Name;
        public KeyCode NegativeButton;
        public KeyCode PositiveButton;
        public float MaxValue = 1;
        public float MinValue = -1;

        public float currentValue;
        public float lerpSpeed = 8f;
    }

    public class InputPreset
    {
        public List<Axis> axisList = new List<Axis>();
        public Dictionary<string, KeyCode> buttonKeys = new Dictionary<string, KeyCode>();
    }


    private void Awake()
    {
        DontDestroyOnLoad(gameObject);

        if (PlayerPrefs.HasKey("keybinds"))
            CurrentInputPreset = JsonConvert.DeserializeObject<InputPreset>(PlayerPrefs.GetString("keybinds"));
        else
            SetDefaults();

        SetDefaults();
    }


    public static void SetDefaults()
    {
        CurrentInputPreset.buttonKeys["Jump"] = KeyCode.Space;
        CurrentInputPreset.buttonKeys["Sprint"] = KeyCode.LeftShift;
        CurrentInputPreset.buttonKeys["Crouch"] = KeyCode.LeftControl;
        CurrentInputPreset.buttonKeys["Shoot"] = KeyCode.Mouse0;
        CurrentInputPreset.buttonKeys["Aim"] = KeyCode.Mouse1;
        CurrentInputPreset.buttonKeys["Reload"] = KeyCode.R;
        CurrentInputPreset.buttonKeys["Interact"] = KeyCode.E;
        CurrentInputPreset.buttonKeys["Push To Talk"] = KeyCode.V;

        CurrentInputPreset.axisList.Add(new Axis()
        {
            Name = "Horizontal",
            NegativeButton = KeyCode.A,
            PositiveButton = KeyCode.D
        });
        CurrentInputPreset.axisList.Add(new Axis()
        {
            Name = "Vertical",
            NegativeButton = KeyCode.S,
            PositiveButton = KeyCode.W
        });
        CurrentInputPreset.axisList.Add(new Axis()
        {
            Name = "Drone Altitude",
            NegativeButton = KeyCode.LeftControl,
            PositiveButton = KeyCode.LeftShift
        });
           
        SaveInputPreset();
    }

    public static void SaveInputPreset()
    {
        PlayerPrefs.SetString("keybinds", JsonConvert.SerializeObject(CurrentInputPreset));
        PlayerPrefs.Save();
    }


    void Update()
    {
        for (int i = 0; i < CurrentInputPreset.axisList.Count; i++)
        {
            //if Negative button is pressed & Positive is not
            if (Input.GetKey(CurrentInputPreset.axisList[i].NegativeButton) &&
                !Input.GetKey(CurrentInputPreset.axisList[i].PositiveButton))
                CurrentInputPreset.axisList[i].currentValue = Mathf.Clamp(
                    Mathf.Lerp(
                        CurrentInputPreset.axisList[i].currentValue, -1,
                        Time.deltaTime * CurrentInputPreset.axisList[i].lerpSpeed),
                    CurrentInputPreset.axisList[i].MinValue,
                    CurrentInputPreset.axisList[i].MaxValue);
            //if Positive is pressed and negative is not
            else if (Input.GetKey(CurrentInputPreset.axisList[i].PositiveButton) &&
                !Input.GetKey(CurrentInputPreset.axisList[i].NegativeButton))
                CurrentInputPreset.axisList[i].currentValue = Mathf.Clamp(
                    Mathf.Lerp(CurrentInputPreset.axisList[i].currentValue, 1,
                    Time.deltaTime * CurrentInputPreset.axisList[i].lerpSpeed),
                    CurrentInputPreset.axisList[i].MinValue,
                    CurrentInputPreset.axisList[i].MaxValue);
            //If neither is pressed, or both are pressed
            else
                CurrentInputPreset.axisList[i].currentValue = Mathf.Clamp(
                    Mathf.Lerp(CurrentInputPreset.axisList[i].currentValue, 0,
                    Time.deltaTime * CurrentInputPreset.axisList[i].lerpSpeed),
                    CurrentInputPreset.axisList[i].MinValue,
                    CurrentInputPreset.axisList[i].MaxValue);
        }
    }


    /// <summary>
    /// Returns true the single frame the button is pressed
    /// </summary>
    /// <param name="buttonName"></param>
    /// <returns></returns>
    public static bool GetButtonDown(string buttonName)
    {
        if (!CurrentInputPreset.buttonKeys.ContainsKey(buttonName))
        {
            Debug.LogWarning("The button " + buttonName + " does not exist");
            return false;
        }

        return Input.GetKeyDown(CurrentInputPreset.buttonKeys[buttonName]);
    }

    /// <summary>
    /// Returns true while the button is held down
    /// </summary>
    /// <param name="buttonName"></param>
    /// <returns></returns>
    public static bool GetButton(string buttonName)
    {
        if (!CurrentInputPreset.buttonKeys.ContainsKey(buttonName))
        {
            Debug.LogWarning("The button " + buttonName + " does not exist");
            return false;
        }

        return Input.GetKey(CurrentInputPreset.buttonKeys[buttonName]);
    }

    /// <summary>
    /// Returns true the single frame the button is released
    /// </summary>
    /// <param name="buttonName"></param>
    /// <returns></returns>
    public static bool GetButtonUp(string buttonName)
    {
        if (!CurrentInputPreset.buttonKeys.ContainsKey(buttonName))
        {
            Debug.LogWarning("The button " + buttonName + " does not exist");
            return false;
        }

        return Input.GetKeyUp(CurrentInputPreset.buttonKeys[buttonName]);
    }

    public static void SetButtonForKey(string buttonName, KeyCode keyCode)
    {
        CurrentInputPreset.buttonKeys[buttonName] = keyCode;
    }

    public static float GetAxis(string axisName)
    {
        Axis axis = CurrentInputPreset.axisList.Find(x => x.Name == axisName);
        if (axis == null)
        {
            Debug.LogWarning("No axis with name " + axisName);
            return 0;
        }
        return axis.currentValue;
    }

    public static float GetAxisRaw(string axisName)
    {
        Axis axis = CurrentInputPreset.axisList.Find(x => x.Name == axisName);
        if(axis == null)
        {
            Debug.LogWarning("No axis with name " + axisName);
            return 0;
        }
        if (Input.GetKey(axis.NegativeButton) && !Input.GetKey(axis.PositiveButton))
            return -1;
        else if (Input.GetKey(axis.PositiveButton) && !Input.GetKey(axis.NegativeButton))
            return 1;
        else
            return 0;
    }

}

So now to my question(s).
This script up here (More specificly it’s update method) is using half the processing time of the game. ALONE. That being about 1.5 - 2ms.
Anyone have any ideas of optimizing this? All the operations done shouldn’t have a crazy time complexity right? Or am I overlooking something?

Any for my second question. Is it worth using the new Input System. If so, where can the documentation be found? Or is there even any? Since It’s not fully done yet as far as I understand it.

Very happy for help. Thanks!

Do a deep profile and narrow down where it’s tanking. Is it just due to axis quantity?

Axis quantity is 3. Deep profile looks like this
https://gyazo.com/ffeebf0e739dd1d903c287721b91c16a

Yeck >_<

With Deep Profile you can expand that Input.GetKey line to see whats wrong. It’s most likely all of the string lookups. They are notoriously expensive and should be avoided if possible. You are doing already doing two lookups in that method to first confirm that the string exists, then again after you know it does.

Those are not string lookups. I am passing the KeyCode enum. But expanding it simply says Input.KeyGetInt

Well, one easy optimization to make would be to cache CurrentInputPreset.axisList[x]. You’re indexing every single time, and that has overhead.

Open up Input.GetKey() in your profiler to get more specific info on why that’s slow.

Next up is the amount of lerping. I think I have to ask a more “big picture” question here: Why do the lerping in the input system? Why not let whatever needs that lerp handle it themselves (player movement, AI, etc.) as they each may have their own requirements on how to interpret the input?

Let the input system only deal with input. Don’t let it handle any logic for subsystems.

How are there 660 calls with only 3 axis?

Good catch.

Looking closer, I see SetDefaults() is continuously adding to the static list without clearing it out first.

So if InputManager got instantiated again over and over again…

The lerping is there to make the input smooth. Unitys build in Input.GetAxis also has that. To see what I mean. Replace your movement code from GetAxis with GetAxisRaw

That is a good point I did not notice at first. After looking into it further. That seems to be the issue. I think I have forgotted to clear my playerprefs when saving new ones or something along those lines. Meaning that there are clogged axis. Now it’s all executing in 0.01 ms.

Exactly!

Thanks to you lads for helping me out. Was getting quite frustrated there haha.

1 Like

Thats a weird one! Glad you got it sorted.