// Copyright (C) 2023 Nicholas Maltbie // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using nickmaltbie.ScreenManager; using nickmaltbie.ScreenManager.Actions; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.UI; namespace nickmaltbie.OpenKCC.UI.Actions { /// /// Rebind an individual button input action /// public class SelectiveRebindInput : MonoBehaviour, IBindingControl { /// /// Prefix for input mapping for saving to player preferences /// public const string inputMappingPlayerPrefPrefix = "Input Mapping"; /// /// Path used to cancel binding mid operation /// public string cancelPath = "/escape"; /// /// Input action being modified /// public InputActionReference inputAction = null; /// /// Binding display name for showing the control button description /// public UnityEngine.UI.Text bindingDisplayNameText = null; /// /// Button to start rebinding for the given input action /// public Button startRebinding = null; /// /// Text to display when waiting for the player to press a new input action /// public GameObject waitingForInputObject = null; /// /// Menu controller related to this selected object /// public MenuController menuController; /// /// Rebinding operation action waiting for player command to change button bindings /// public InputActionRebindingExtensions.RebindingOperation rebindingOperation { get; private set; } /// /// Get a readable display name for a binding index /// /// Human readable information of button for this binding index public string GetKeyReadableName() => InputControlPath.ToHumanReadableString( inputAction.action.bindings[0].effectivePath, InputControlPath.HumanReadableStringOptions.OmitDevice); /// /// Get the input mapping player preference key from a given index /// public string InputMappingKey => $"{inputMappingPlayerPrefPrefix} {inputAction.action.name}"; public void Awake() { // Load the default mapping saved to the file menuController = gameObject.GetComponentInParent(); string inputMapping = PlayerPrefs.GetString(InputMappingKey, string.Empty); if (!string.IsNullOrEmpty(inputMapping)) { inputAction.action.ApplyBindingOverride(0, inputMapping); } } public void Start() { startRebinding.onClick.AddListener(() => StartRebinding()); bindingDisplayNameText.text = GetKeyReadableName(); } /// /// Start the rebinding process for a given component of this composite axis. /// public void StartRebinding() { startRebinding.gameObject.SetActive(false); waitingForInputObject.SetActive(true); menuController.allowInputChanges = false; inputAction.action.Disable(); inputAction.action.actionMap.Disable(); rebindingOperation = inputAction.action.PerformInteractiveRebinding(0) .WithControlsExcluding("/position") // Don't bind to mouse position .WithControlsExcluding("/delta") // To avoid accidental input from mouse motion .WithCancelingThrough(cancelPath) .OnMatchWaitForAnother(0.1f) .WithTimeout(5.0f) .OnComplete(operation => RebindComplete()) .OnCancel(operation => RebindCancel()) .Start(); } /// /// Cancel the rebinding process for a given component of this composite axis. /// public void RebindCancel() { bindingDisplayNameText.text = GetKeyReadableName(); rebindingOperation.Dispose(); startRebinding.gameObject.SetActive(true); waitingForInputObject.SetActive(false); menuController.allowInputChanges = true; inputAction.action.Enable(); inputAction.action.actionMap.Enable(); } /// /// Finish the rebinding process for a given component of this composite axis. /// public void RebindComplete() { string overridePath = inputAction.action.bindings[0].overridePath; #if UNITY_2020_3_OR_NEWER foreach (PlayerInput input in GameObject.FindObjectsByType(FindObjectsSortMode.None)) #else foreach (PlayerInput input in GameObject.FindObjectsOfType()) #endif { input.actions.FindAction(inputAction.name)?.ApplyBindingOverride(0, overridePath); } bindingDisplayNameText.text = GetKeyReadableName(); rebindingOperation.Dispose(); PlayerPrefs.SetString(InputMappingKey, inputAction.action.bindings[0].overridePath); startRebinding.gameObject.SetActive(true); waitingForInputObject.SetActive(false); menuController.allowInputChanges = true; inputAction.action.Enable(); inputAction.action.actionMap.Enable(); } public void ResetBinding() { PlayerPrefs.DeleteKey(InputMappingKey); inputAction.action.RemoveAllBindingOverrides(); } public void UpdateDisplay() { bindingDisplayNameText.text = GetKeyReadableName(); } } }