Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Author: [email protected]
- // Unlicensed: this code is explicitly placed into the public domain.
- // I wrote this because the Unity InputSystem is confusing to setup, and this is more intuitive for me.
- using UnityEngine;
- using UnityEngine.InputSystem;
- using System;
- using System.Collections.Generic;
- using UnityEngine.Events;
- using ControlCallback = System.Action<UnityEngine.InputSystem.InputAction.CallbackContext>;
- #if UNITY_EDITOR
- using UnityEditor;
- using System.Linq;
- using UnityEngine.InputSystem.Layouts;
- using StringDict = System.Collections.Generic.Dictionary<string, string>;
- #endif
- public class InputControlBindingBehaviour : MonoBehaviour {
- public InputControlBinding input = new InputControlBinding();
- private void Reset() {
- input.Binding = new List<InputControlBinder>{
- new InputControlBinder("jump", "<Keyboard>/space", this, DefaultInputListener, DefaultInputListener),
- new InputControlBinder("attack", "<Mouse>/leftButton", this, DefaultInputListener, null)
- };
- }
- private void Start() { input.Init(); input.Enable(); }
- private void OnEnable() => input.Enable();
- private void OnDisable() => input.Disable();
- private void OnDestroy() => input.Destroy();
- public void DefaultInputListener(InputAction.CallbackContext context) {
- Debug.Log(context.action.name + " -> input " + (context.canceled ? "released" : "pressed") + ": " +
- context.control.ToString() + "\n" + context.phase + " " + context.ReadValueAsObject());
- }
- [ContextMenu("Preview Runtime Action Map")] private void PreviewRuntimeActionMap() { input.Init(); }
- }
- [Serializable] public class InputControlBinding {
- public List<InputControlBinder> Binding = new List<InputControlBinder>();
- [SerializeField] public InputActionMap InputActionMapRuntime;
- public void Init() {
- Disable(); Destroy(); InputActionMapRuntime = new InputActionMap();
- foreach (InputControlBinder binding in Binding) { AddBindingInternal(binding); }
- }
- private InputAction AddBindingInternal(InputControlBinder binding) {
- InputAction inputAction = InputActionMapRuntime.AddAction(binding.name, InputActionType.Button);
- inputAction.AddBinding(binding.key);
- binding.BindCallbacksInto(inputAction);
- if (InputActionMapRuntime.enabled) { inputAction.Enable(); }
- return inputAction;
- }
- public InputAction Bind(InputControlBinder binding) {
- Binding.Add(binding);
- if (InputActionMapRuntime.actions.Count > 0 || InputActionMapRuntime.enabled) {
- return AddBindingInternal(binding);
- }
- return null;
- }
- public void Enable() => InputActionMapRuntime.Enable();
- public void Disable() => InputActionMapRuntime.Disable();
- public void Destroy() {
- ExplicitlyRemoveCallbacksFromInputActions(); // may be necessary, shouldn't harm data integrity if not necessary
- InputActionMapRuntime.Dispose();
- }
- private void ExplicitlyRemoveCallbacksFromInputActions() {
- foreach (var inputAction in InputActionMapRuntime.actions) {
- List<InputControlBinder> bindings = Binding; // Binding.FindAll(info => info.name == action.name);
- bindings.ForEach(binding => binding.UnbindCallbacksFrom(inputAction));
- }
- }
- }
- [Serializable] public class InputControlBinder {
- [Serializable] public class UnityEvent_CallbackContext : UnityEvent<InputAction.CallbackContext> { }
- public string name;
- [InputControlPath] public string key;
- [Serializable] public class EventCallbacks {
- public UnityEvent_CallbackContext onPress = new UnityEvent_CallbackContext();
- public UnityEvent_CallbackContext onRelease = new UnityEvent_CallbackContext();
- }
- public EventCallbacks callbacks = new EventCallbacks();
- public InputControlBinder(string name, string key, UnityEngine.Object callbackOwner,
- ControlCallback onPressCallback, ControlCallback onReleaseCallback) {
- this.name = name; this.key = key;
- Bind(callbacks.onPress, callbackOwner, onPressCallback);
- Bind(callbacks.onRelease, callbackOwner, onReleaseCallback);
- }
- public static void Bind<T>(UnityEvent<T> dest, UnityEngine.Object funcOwner, Action<T> func) {
- #if UNITY_EDITOR
- if (funcOwner != null) try {
- var unityAction = (UnityAction<T>)Delegate.CreateDelegate(typeof(UnityAction<T>), funcOwner, func.Method);
- UnityEditor.Events.UnityEventTools.AddPersistentListener(dest, unityAction);
- } catch (Exception e) { Debug.LogException(e); }
- #endif
- dest.AddListener(new UnityAction<T>(func));
- }
- public void BindCallbacksInto(InputAction action) {
- action.started += callbacks.onPress.Invoke;
- action.canceled += callbacks.onRelease.Invoke;
- }
- public void UnbindCallbacksFrom(InputAction action) {
- action.started -= callbacks.onPress.Invoke;
- action.canceled -= callbacks.onRelease.Invoke;
- }
- }
- public static class ControlInputs {
- public const string Keyboard = "Keyboard", Mouse = "Mouse", Gamepad = "Gamepad", Touchscreen = "Touchscreen";
- public static readonly (string name, string[] fallback)[] Data = new (string name, string[] fallback)[] {
- (Keyboard, new string[] {
- "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
- "v", "w", "x", "y", "z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
- "space", "enter", "escape", "tab", "shift", "ctrl", "alt", "leftArrow", "rightArrow", "upArrow", "downArrow"
- }), (Mouse, new string[] {
- "leftButton", "rightButton", "middleButton", "forwardButton", "backButton", "scroll/up", "scroll/down"
- }), (Gamepad, new string[] {
- "buttonSouth", "buttonEast", "buttonWest", "buttonNorth", "leftShoulder", "rightShoulder",
- "leftTrigger", "rightTrigger", "leftStickPress", "rightStickPress", "start", "select",
- "dpad/up", "dpad/down", "dpad/left", "dpad/right",
- }), (Touchscreen, new string[]{ "Press" })
- };
- private static string[] _names;
- public static string[] Names => _names != null ? _names : _names = Array.ConvertAll(Data, d => d.name);
- }
- public class InputControlPathAttribute : PropertyAttribute {
- private bool[] explicitlyInclude;
- /// <param name="controlsToExplicitlyInclude">should be const string value from <see cref="ControlInputs"/></param>
- public InputControlPathAttribute(params string[] controlsToExplicitlyInclude) {
- if (controlsToExplicitlyInclude == null || controlsToExplicitlyInclude.Length == 0) { return; }
- string[] names = ControlInputs.Names;
- explicitlyInclude = new bool[ControlInputs.Names.Length];
- for (int i = 0; i < controlsToExplicitlyInclude.Length; i++) {
- explicitlyInclude[ConvertNameToIndex(controlsToExplicitlyInclude[i])] = true;
- }
- }
- public bool Includes(string controlName) {
- if (explicitlyInclude == null || explicitlyInclude.Length == 0) { return true; }
- return explicitlyInclude[ConvertNameToIndex(controlName)];
- }
- public static int ConvertNameToIndex(string controlName) {
- int index = Array.IndexOf(ControlInputs.Names, controlName);
- if (index < 0) { throw new Exception($"{controlName} not in [{string.Join(", ", ControlInputs.Names)}]"); }
- return index;
- }
- public bool Includes(int controlInputIndex) => explicitlyInclude == null || explicitlyInclude[controlInputIndex];
- }
- #if UNITY_EDITOR
- [CustomPropertyDrawer(typeof(InputControlBinder))]
- public class InputBindInfoDrawer : PropertyDrawer {
- public override void OnGUI(Rect guiRect, SerializedProperty property, GUIContent label) {
- EditorGUI.BeginProperty(guiRect, label, property);
- SerializedProperty nameProperty = property.FindPropertyRelative(nameof(InputControlBinder.name));
- SerializedProperty keyProperty = property.FindPropertyRelative(nameof(InputControlBinder.key));
- SerializedProperty callbacksProperty = property.FindPropertyRelative(nameof(InputControlBinder.callbacks));
- Rect firstLineRect = new Rect(guiRect.x, guiRect.y, guiRect.width, EditorGUIUtility.singleLineHeight);
- float nameWidth = firstLineRect.width * 0.4f, keyWidth = firstLineRect.width * 0.6f - 5f;
- Rect nameRect = new Rect(firstLineRect.x, firstLineRect.y, nameWidth, firstLineRect.height);
- Rect keyRect = new Rect(firstLineRect.x + nameWidth + 5f, firstLineRect.y, keyWidth, firstLineRect.height);
- EditorGUI.PropertyField(nameRect, nameProperty, GUIContent.none);
- EditorGUI.PropertyField(keyRect, keyProperty, GUIContent.none);
- float y = guiRect.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
- Rect callbacksRect = new Rect(guiRect.x, y, guiRect.width, EditorGUI.GetPropertyHeight(callbacksProperty, true));
- EditorGUI.PropertyField(callbacksRect, callbacksProperty, true);
- EditorGUI.EndProperty();
- }
- public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
- SerializedProperty callbacksProperty = property.FindPropertyRelative(nameof(InputControlBinder.callbacks));
- return EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing +
- EditorGUI.GetPropertyHeight(callbacksProperty, true);
- }
- }
- [CustomPropertyDrawer(typeof(InputControlPathAttribute))]
- public class InputControlPathDrawer : PropertyDrawer {
- private const string None = "None";
- private static Dictionary<string, StringDict> SpecificControlPaths = new Dictionary<string, StringDict>();
- private static StringDict allControlPaths;
- static InputControlPathDrawer() => RefreshControlPaths();
- private static void RefreshControlPaths() {
- allControlPaths = new StringDict();
- foreach (var inputDefinition in ControlInputs.Data) {
- StringDict controls = SpecificControlPaths[inputDefinition.name] = new StringDict();
- PopulateControlPaths(inputDefinition.name, controls, inputDefinition.fallback);
- AddDictionaryToDictionary(allControlPaths, controls);
- }
- }
- private static void AddDictionaryToDictionary<K, V>(Dictionary<K, V> destination, Dictionary<K, V> valuesToCopy) {
- foreach (var kvp in valuesToCopy) { destination[kvp.Key] = kvp.Value; }
- }
- private static void PopulateControlPaths(string inputName, StringDict controlPaths, string[] fallback) {
- try {
- InputControlLayout controlLayout = InputSystem.LoadLayout(inputName);
- foreach (InputControlLayout.ControlItem control in controlLayout.controls) {
- string path = $"<{inputName}>/{control.name}";
- string controlName = (string.IsNullOrEmpty(control.displayName) ? control.name : control.displayName);
- string safeDisplayName = $"{inputName}: {controlName}".Replace("/", "\u2215");
- controlPaths[path] = safeDisplayName;
- }
- } catch {
- foreach (string key in fallback) {
- string path = $"<{inputName}>/{key}".Replace("/", "\u2215");
- controlPaths[path] = ConvertPathToHumanReadableDisplayName(path);
- }
- }
- }
- private static string ConvertPathToHumanReadableDisplayName(string controlPath) {
- int indexOfInputNameEnd = controlPath.IndexOf('/');
- if (indexOfInputNameEnd >= 0) {
- string device = controlPath.Substring(0, indexOfInputNameEnd).Trim('<', '>');
- string control = controlPath.Substring(indexOfInputNameEnd + 1);
- return $"{device}: {control}".Replace("/", "\u2215");
- }
- return controlPath;
- }
- public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
- EditorGUI.BeginProperty(position, label, property);
- StringDict filteredPaths = GetFilteredPaths();
- StringDict visiblePaths = new StringDict { { "", None } };
- AddDictionaryToDictionary(visiblePaths, filteredPaths);
- string currentPath = property.stringValue;
- string currentDisplayName = GetDisplayName(currentPath);
- if (!visiblePaths.ContainsKey(currentPath) && !string.IsNullOrEmpty(currentPath)) {
- currentDisplayName += " (filtered)";
- }
- Rect rect = EditorGUI.PrefixLabel(position, label);
- if (EditorGUI.DropdownButton(rect, new GUIContent(currentDisplayName), FocusType.Keyboard)) {
- GenericMenu menu = CreateControlPathDropdown(currentPath, property, (InputControlPathAttribute)attribute);
- menu.ShowAsContext();
- }
- EditorGUI.EndProperty();
- }
- private GenericMenu CreateControlPathDropdown(string path, SerializedProperty prop, InputControlPathAttribute attr) {
- GenericMenu menu = new GenericMenu();
- menu.AddItem(new GUIContent(None), string.IsNullOrEmpty(path), () => SetPropertyValue(prop, ""));
- menu.AddSeparator("");
- for(int i = 0; i < ControlInputs.Names.Length; ++i) {
- if (attr.Includes(i)) { AddInputControlPathSubmenu(ControlInputs.Names[i]); }
- }
- void AddInputControlPathSubmenu(string inputName) {
- StringDict controlPathNames = SpecificControlPaths[inputName];
- if (controlPathNames.Count == 0) { return; }
- menu.AddSeparator($"{inputName}/");
- foreach (var kvp in controlPathNames.OrderBy(x => x.Value)) {
- string controlName = ExtractControlName(kvp.Value);
- string menuPath = $"{inputName}/{controlName}";
- menu.AddItem(new GUIContent(menuPath), path == kvp.Key, () => SetPropertyValue(prop, kvp.Key));
- }
- }
- return menu;
- }
- private StringDict GetFilteredPaths() {
- InputControlPathAttribute attr = (InputControlPathAttribute)attribute;
- StringDict filteredPaths = new StringDict();
- for (int i = 0; i < ControlInputs.Names.Length; ++i) {
- if (attr.Includes(i)) { AddDictionaryToDictionary(filteredPaths, SpecificControlPaths[ControlInputs.Names[i]]); }
- }
- return filteredPaths;
- }
- private string GetDisplayName(string controlPath) {
- if (string.IsNullOrEmpty(controlPath)) { return None; }
- if (allControlPaths.TryGetValue(controlPath, out string displayName)) { return displayName; }
- return ConvertPathToHumanReadableDisplayName(controlPath);
- }
- private string ExtractControlName(string displayName) {
- int colonIndex = displayName.IndexOf(": ");
- return colonIndex >= 0 ? displayName.Substring(colonIndex + 2) : displayName;
- }
- private void SetPropertyValue(SerializedProperty property, string value) {
- property.stringValue = value;
- property.serializedObject.ApplyModifiedProperties();
- }
- }
- #endif
Advertisement
Add Comment
Please, Sign In to add comment