mvaganov

InputControlBindingBehaviour.cs

Sep 10th, 2025
31
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 12.61 KB | Software | 0 0
  1. // Unlicensed: this code is explicitly placed into the public domain.
  2. using UnityEngine;
  3. using UnityEngine.InputSystem;
  4. using System;
  5. using System.Collections.Generic;
  6. using UnityEngine.Events;
  7.  
  8. #if UNITY_EDITOR
  9. using UnityEditor;
  10. using System.Linq;
  11. #endif
  12.  
  13. public class InputControlBindingBehaviour : MonoBehaviour {
  14.     public InputControlBinding input = new InputControlBinding();
  15.     private void Reset() {
  16.         input.Binding = new List<InputControlBindInfo>{
  17.             new InputControlBindInfo("jump", "<Keyboard>/space", this, DefaultInputListener),
  18.             new InputControlBindInfo("attack", "<Mouse>/leftButton", this, DefaultInputListener)
  19.         };
  20.     }
  21.     private void Start() { input.Init(); input.Enable(); }
  22.     private void OnEnable() => input.Enable();
  23.     private void OnDisable() => input.Disable();
  24.     private void OnDestroy() => input.Destroy();
  25.     public void DefaultInputListener(InputAction.CallbackContext context) {
  26.         Debug.Log(context.action.name + " -> input " + (context.canceled ? "released" : "pressed") + ": " + context.control.ToString());
  27.     }
  28. }
  29.  
  30. [Serializable] public class InputControlBinding {
  31.     public List<InputControlBindInfo> Binding = new List<InputControlBindInfo>();
  32.     [SerializeField] protected List<InputAction> ActionsRuntime;
  33.     public void Init() {
  34.         Disable();
  35.         Destroy();
  36.         ActionsRuntime = new List<InputAction>(Binding.Count);
  37.         for (int i = 0; i < Binding.Count; i++) {
  38.             Debug.Log(Binding[i].name);
  39.             InputAction inputAction = new InputAction(Binding[i].name, InputActionType.Button);
  40.             InputControlBindInfo binding = Binding[i];
  41.             inputAction.AddBinding(binding.key);
  42.             inputAction.started += binding.onPress.Invoke;
  43.             inputAction.canceled += binding.onRelease.Invoke;
  44.             ActionsRuntime.Add(inputAction);
  45.         }
  46.     }
  47.     public void Enable() {
  48.         ActionsRuntime.ForEach(inputAction => inputAction.Enable());
  49.     }
  50.     public void Disable() {
  51.         ActionsRuntime.ForEach(inputAction => inputAction.Disable());
  52.     }
  53.     public void Destroy() {
  54.         for (int i = 0; i < ActionsRuntime.Count; i++) {
  55.             InputAction action = ActionsRuntime[i];
  56.             InputControlBindInfo info = Binding.Find(info => info.name == action.name);
  57.             if (info != null) {
  58.                 action.started -= info.onPress.Invoke;
  59.                 action.canceled -= info.onRelease.Invoke;
  60.             }
  61.         }
  62.     }
  63. }
  64.  
  65. [Serializable] public class InputControlBindInfo {
  66.     [Serializable] public class UnityEvent_CallbackContext : UnityEvent<InputAction.CallbackContext> { }
  67.     public string name;
  68.     [InputControlPath] public string key;
  69.     public UnityEvent_CallbackContext onPress = new UnityEvent_CallbackContext();
  70.     public UnityEvent_CallbackContext onRelease = new UnityEvent_CallbackContext();
  71.     public InputControlBindInfo(string name, string key, UnityEngine.Object target, Action<InputAction.CallbackContext> action) {
  72.         this.name = name; this.key = key;
  73. #if UNITY_EDITOR
  74.         var unityAction = (UnityAction<InputAction.CallbackContext>)Delegate.CreateDelegate(
  75.                  typeof(UnityAction<InputAction.CallbackContext>), target, action.Method);
  76.         UnityEditor.Events.UnityEventTools.AddPersistentListener(onPress, unityAction);
  77. #else
  78.         onPress.AddListener(new UnityAction<InputAction.CallbackContext>(action));
  79. #endif
  80.     }
  81. }
  82.  
  83. public class InputControlPathAttribute : PropertyAttribute {
  84.     public bool includeKeyboard = true, includeMouse = true, includeGamepad = true;
  85.     public InputControlPathAttribute(bool keyboard = true, bool mouse = true, bool gamepad = true) {
  86.         includeKeyboard = keyboard; includeMouse = mouse; includeGamepad = gamepad;
  87.     }
  88. }
  89.  
  90. #if UNITY_EDITOR
  91. [CustomPropertyDrawer(typeof(InputControlBindInfo))]
  92. public class InputBindInfoDrawer : PropertyDrawer {
  93.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
  94.         EditorGUI.BeginProperty(position, label, property);
  95.         var nameProperty = property.FindPropertyRelative(nameof(InputControlBindInfo.name));
  96.         var keyProperty = property.FindPropertyRelative(nameof(InputControlBindInfo.key));
  97.         var onPressProperty = property.FindPropertyRelative(nameof(InputControlBindInfo.onPress));
  98.         var onReleaseProperty = property.FindPropertyRelative(nameof(InputControlBindInfo.onRelease));
  99.         var firstLineRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
  100.         var nameRect = new Rect(firstLineRect.x, firstLineRect.y, firstLineRect.width * 0.4f, firstLineRect.height);
  101.         var keyRect = new Rect(firstLineRect.x + firstLineRect.width * 0.4f + 5f, firstLineRect.y, firstLineRect.width * 0.6f - 5f, firstLineRect.height);
  102.         EditorGUI.PropertyField(nameRect, nameProperty, GUIContent.none);
  103.         EditorGUI.PropertyField(keyRect, keyProperty, GUIContent.none);
  104.         var onPressRect = new Rect(
  105.                 position.x,
  106.                 position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing,
  107.                 position.width,
  108.                 EditorGUI.GetPropertyHeight(onPressProperty, true)
  109.         );
  110.         EditorGUI.PropertyField(onPressRect, onPressProperty, true);
  111.         var onReleaseRect = new Rect(
  112.                 position.x,
  113.                 position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing + EditorGUI.GetPropertyHeight(onPressProperty, true),
  114.                 position.width,
  115.                 EditorGUI.GetPropertyHeight(onReleaseProperty, true)
  116.         );
  117.         EditorGUI.PropertyField(onReleaseRect, onReleaseProperty, true);
  118.         EditorGUI.EndProperty();
  119.     }
  120.  
  121.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
  122.         var onPressProperty = property.FindPropertyRelative(nameof(InputControlBindInfo.onPress));
  123.         var onReleaseProperty = property.FindPropertyRelative(nameof(InputControlBindInfo.onRelease));
  124.         return EditorGUIUtility.singleLineHeight +
  125.                      EditorGUIUtility.standardVerticalSpacing +
  126.                      EditorGUI.GetPropertyHeight(onPressProperty, true) +
  127.                      EditorGUI.GetPropertyHeight(onReleaseProperty, true);
  128.     }
  129. }
  130. [CustomPropertyDrawer(typeof(InputControlPathAttribute))]
  131. public class InputControlPathDrawer : PropertyDrawer {
  132.     private static Dictionary<string, string> allControlPaths;
  133.     private static Dictionary<string, string> keyboardPaths;
  134.     private static Dictionary<string, string> mousePaths;
  135.     private static Dictionary<string, string> gamepadPaths;
  136.  
  137.     static InputControlPathDrawer() {
  138.         RefreshControlPaths();
  139.     }
  140.  
  141.     private static void RefreshControlPaths() {
  142.         allControlPaths = new Dictionary<string, string>();
  143.         keyboardPaths = new Dictionary<string, string>();
  144.         mousePaths = new Dictionary<string, string>();
  145.         gamepadPaths = new Dictionary<string, string>();
  146.         try {
  147.             var keyboardLayout = InputSystem.LoadLayout("Keyboard");
  148.             foreach (var control in keyboardLayout.controls) {
  149.                 var path = $"<Keyboard>/{control.name}";
  150.                 var displayName = $"Keyboard: {control.displayName ?? control.name}";
  151.                 keyboardPaths[path] = displayName;
  152.                 allControlPaths[path] = displayName;
  153.             }
  154.         } catch {
  155.             var commonKeysFallbackIfLoadingFails = new[] {
  156.                 "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
  157.                 "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
  158.                 "1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
  159.                 "space", "enter", "escape", "tab", "shift", "ctrl", "alt",
  160.                 "leftArrow", "rightArrow", "upArrow", "downArrow"
  161.             };
  162.             foreach (var key in commonKeysFallbackIfLoadingFails) {
  163.                 var path = $"<Keyboard>/{key}";
  164.                 var displayName = $"Keyboard: {key}";
  165.                 keyboardPaths[path] = displayName;
  166.                 allControlPaths[path] = displayName;
  167.             }
  168.         }
  169.         var mouseControls = new Dictionary<string, string> {
  170.             { "<Mouse>/leftButton", "Mouse: Left Button" },
  171.             { "<Mouse>/rightButton", "Mouse: Right Button" },
  172.             { "<Mouse>/middleButton", "Mouse: Middle Button" },
  173.             { "<Mouse>/forwardButton", "Mouse: Forward Button" },
  174.             { "<Mouse>/backButton", "Mouse: Back Button" },
  175.             { "<Mouse>/scroll/up", "Mouse: Scroll Up" },
  176.             { "<Mouse>/scroll/down", "Mouse: Scroll Down" }
  177.         };
  178.         foreach (var kvp in mouseControls) {
  179.             mousePaths[kvp.Key] = kvp.Value;
  180.             allControlPaths[kvp.Key] = kvp.Value;
  181.         }
  182.         var gamepadControls = new Dictionary<string, string> {
  183.             { "<Gamepad>/buttonSouth", "Gamepad: A/Cross" },
  184.             { "<Gamepad>/buttonEast", "Gamepad: B/Circle" },
  185.             { "<Gamepad>/buttonWest", "Gamepad: X/Square" },
  186.             { "<Gamepad>/buttonNorth", "Gamepad: Y/Triangle" },
  187.             { "<Gamepad>/leftShoulder", "Gamepad: Left Bumper" },
  188.             { "<Gamepad>/rightShoulder", "Gamepad: Right Bumper" },
  189.             { "<Gamepad>/leftTrigger", "Gamepad: Left Trigger" },
  190.             { "<Gamepad>/rightTrigger", "Gamepad: Right Trigger" },
  191.             { "<Gamepad>/leftStickPress", "Gamepad: Left Stick Click" },
  192.             { "<Gamepad>/rightStickPress", "Gamepad: Right Stick Click" },
  193.             { "<Gamepad>/start", "Gamepad: Start/Options" },
  194.             { "<Gamepad>/select", "Gamepad: Select/Share" },
  195.             { "<Gamepad>/dpad/up", "Gamepad: D-Pad Up" },
  196.             { "<Gamepad>/dpad/down", "Gamepad: D-Pad Down" },
  197.             { "<Gamepad>/dpad/left", "Gamepad: D-Pad Left" },
  198.             { "<Gamepad>/dpad/right", "Gamepad: D-Pad Right" }
  199.         };
  200.         foreach (var kvp in gamepadControls) {
  201.             gamepadPaths[kvp.Key] = kvp.Value;
  202.             allControlPaths[kvp.Key] = kvp.Value;
  203.         }
  204.     }
  205.  
  206.     private Dictionary<string, string> GetFilteredPaths() {
  207.         var attr = (InputControlPathAttribute)attribute;
  208.         var filteredPaths = new Dictionary<string, string>();
  209.         if (attr.includeKeyboard) {
  210.             foreach (var kvp in keyboardPaths) { filteredPaths[kvp.Key] = kvp.Value; }
  211.         }
  212.         if (attr.includeMouse) {
  213.             foreach (var kvp in mousePaths) { filteredPaths[kvp.Key] = kvp.Value; }
  214.         }
  215.         if (attr.includeGamepad) {
  216.             foreach (var kvp in gamepadPaths) { filteredPaths[kvp.Key] = kvp.Value; }
  217.         }
  218.         return filteredPaths;
  219.     }
  220.  
  221.     private string GetDisplayName(string controlPath) {
  222.         if (string.IsNullOrEmpty(controlPath)) { return "None"; }
  223.         if (allControlPaths.TryGetValue(controlPath, out string displayName)) { return displayName; }
  224.         // Fallback: try to make a readable name from the path
  225.         var parts = controlPath.Split('/');
  226.         if (parts.Length >= 2) {
  227.             var device = parts[0].Trim('<', '>');
  228.             var control = parts[1];
  229.             return $"{device}: {control}";
  230.         }
  231.         return controlPath;
  232.     }
  233.  
  234.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
  235.         EditorGUI.BeginProperty(position, label, property);
  236.         var filteredPaths = GetFilteredPaths();
  237.         var attr = (InputControlPathAttribute)attribute;
  238.         var pathsWithNone = new Dictionary<string, string> { { "", "None" } };
  239.         foreach (var kvp in filteredPaths) {
  240.             pathsWithNone[kvp.Key] = kvp.Value;
  241.         }
  242.         var currentPath = property.stringValue ?? "";
  243.         var currentDisplayName = GetDisplayName(currentPath);
  244.         // If current path is not in filtered results, show it but mark as filtered
  245.         if (!pathsWithNone.ContainsKey(currentPath) && !string.IsNullOrEmpty(currentPath)) {
  246.             currentDisplayName += " (filtered)";
  247.         }
  248.         var rect = EditorGUI.PrefixLabel(position, label);
  249.         if (EditorGUI.DropdownButton(rect, new GUIContent(currentDisplayName), FocusType.Keyboard)) {
  250.             var menu = new GenericMenu();
  251.             menu.AddItem(new GUIContent("None"),
  252.                                     string.IsNullOrEmpty(currentPath),
  253.                                     () => SetPropertyValue(property, ""));
  254.             menu.AddSeparator("");
  255.             // Group by device type and add appropriate sections
  256.             if (attr.includeKeyboard && keyboardPaths.Count > 0) {
  257.                 foreach (var kvp in keyboardPaths.OrderBy(x => x.Value)) {
  258.                     var menuPath = $"Keyboard/{ExtractControlName(kvp.Value)}";
  259.                     menu.AddItem(new GUIContent(menuPath),
  260.                                             currentPath == kvp.Key,
  261.                                             () => SetPropertyValue(property, kvp.Key));
  262.                 }
  263.             }
  264.             if (attr.includeMouse && mousePaths.Count > 0) {
  265.                 if (attr.includeKeyboard) menu.AddSeparator("Mouse/");
  266.                 foreach (var kvp in mousePaths.OrderBy(x => x.Value)) {
  267.                     var menuPath = $"Mouse/{ExtractControlName(kvp.Value)}";
  268.                     menu.AddItem(new GUIContent(menuPath),
  269.                                             currentPath == kvp.Key,
  270.                                             () => SetPropertyValue(property, kvp.Key));
  271.                 }
  272.             }
  273.             if (attr.includeGamepad && gamepadPaths.Count > 0) {
  274.                 if (attr.includeKeyboard || attr.includeMouse) menu.AddSeparator("Gamepad/");
  275.                 foreach (var kvp in gamepadPaths.OrderBy(x => x.Value)) {
  276.                     var menuPath = $"Gamepad/{ExtractControlName(kvp.Value)}";
  277.                     menu.AddItem(new GUIContent(menuPath),
  278.                                             currentPath == kvp.Key,
  279.                                             () => SetPropertyValue(property, kvp.Key));
  280.                 }
  281.             }
  282.             menu.ShowAsContext();
  283.         }
  284.         EditorGUI.EndProperty();
  285.     }
  286.  
  287.     private string ExtractControlName(string displayName) {
  288.         var colonIndex = displayName.IndexOf(": ");
  289.         return colonIndex >= 0 ? displayName.Substring(colonIndex + 2) : displayName;
  290.     }
  291.  
  292.     private void SetPropertyValue(SerializedProperty property, string value) {
  293.         property.stringValue = value;
  294.         property.serializedObject.ApplyModifiedProperties();
  295.     }
  296. }
  297. #endif
  298.  
Advertisement
Add Comment
Please, Sign In to add comment