using System; using UnityEngine; #if CINEMACHINE_UNITY_INPUTSYSTEM using UnityEngine.InputSystem; using UnityEngine.InputSystem.Users; #endif namespace Unity.Cinemachine { /// /// This is a behaviour that is used to drive other behaviours that implement IInputAxisOwner, /// which it discovers dynamically. It is the bridge between the input system and /// Cinemachine cameras that require user input. Add it to a Cinemachine camera that needs it. /// /// This implementation can read input from the Input package, or from the legacy input system, /// or both, depending on what is installed in the project. /// [ExecuteAlways] [SaveDuringPlay] [AddComponentMenu("Cinemachine/Helpers/Cinemachine Input Axis Controller")] [HelpURL(Documentation.BaseURL + "manual/InputAxisController.html")] public class CinemachineInputAxisController : InputAxisControllerBase { #if CINEMACHINE_UNITY_INPUTSYSTEM /// /// Leave this at -1 for single-player games. /// For multi-player games, set this to be the player index, and the actions will /// be read from that player's controls /// [Tooltip("Leave this at -1 for single-player games. " + "For multi-player games, set this to be the player index, and the actions will " + "be read from that player's controls")] public int PlayerIndex = -1; /// If set, Input Actions will be auto-enabled at start [Tooltip("If set, Input Actions will be auto-enabled at start")] public bool AutoEnableInputs = true; #endif /// /// This is a mechanism to allow the inspector to set up default values /// when the component is reset. /// /// The information of the input axis. /// Reference to the controller to change. internal delegate void SetControlDefaultsForAxis( in IInputAxisOwner.AxisDescriptor axis, ref Controller controller); internal static SetControlDefaultsForAxis SetControlDefaults; #if CINEMACHINE_UNITY_INPUTSYSTEM /// /// CinemachineInputAxisController.Reader can only handle float or Vector2 InputAction types. /// To handle other types you can install a handler to read InputActions of a different type. /// /// The action to read /// The axis hint of the action. /// The owner object, can be used for logging diagnostics /// The default reader to call if you don't handle the type /// The value of the control public Reader.ControlValueReader ReadControlValueOverride; /// protected override void Reset() { base.Reset(); PlayerIndex = -1; AutoEnableInputs = true; } #endif /// /// Creates default controllers for an axis. /// Override this if the default axis controllers do not fit your axes. /// /// Description of the axis whose default controller needs to be set. /// Controller to drive the axis. protected override void InitializeControllerDefaultsForAxis( in IInputAxisOwner.AxisDescriptor axis, Controller controller) { SetControlDefaults?.Invoke(axis, ref controller); } //TODO Support fixed update as well. Input system has a setting to update inputs only during fixed update. //TODO This won't work accuratly if this setting is enabled. void Update() { if (Application.isPlaying) UpdateControllers(); } /// Read an input value from legacy input or from and Input Action [Serializable] public sealed class Reader : IInputAxisReader { #if CINEMACHINE_UNITY_INPUTSYSTEM /// Action for the Input package (if used). [Tooltip("Action for the Input package (if used).")] public InputActionReference InputAction; /// The input value is multiplied by this amount prior to processing. /// Controls the input power. Set it to a negative value to invert the input [Tooltip("The input value is multiplied by this amount prior to processing. " + "Controls the input power. Set it to a negative value to invert the input")] public float Gain = 1; /// The actual action, resolved for player internal InputAction m_CachedAction; /// /// CinemachineInputAxisController.Reader can only handle float or Vector2 InputAction types. /// To handle other types you can install a handler to read InputActions of a different type. /// /// The action to read /// The axis hint of the action. /// The owner object, can be used for logging diagnostics /// The default reader to call if you don't handle the type /// The value of the control public delegate float ControlValueReader( InputAction action, IInputAxisOwner.AxisDescriptor.Hints hint, UnityEngine.Object context, ControlValueReader defaultReader); #endif #if ENABLE_LEGACY_INPUT_MANAGER /// Axis name for the Legacy Input system (if used). /// CinemachineCore.GetInputAxis() will be called with this name. [InputAxisNameProperty] [Tooltip("Axis name for the Legacy Input system (if used). " + "This value will be used to control the axis.")] public string LegacyInput; /// The LegacyInput value is multiplied by this amount prior to processing. /// Controls the input power. Set it to a negative value to invert the input [Tooltip("The LegacyInput value is multiplied by this amount prior to processing. " + "Controls the input power. Set it to a negative value to invert the input")] public float LegacyGain = 1; #endif /// Enable this if the input value is inherently dependent on frame time. /// For example, mouse deltas will naturally be bigger for longer frames, so /// should not normally be scaled by deltaTime. [Tooltip("Enable this if the input value is inherently dependent on frame time. " + "For example, mouse deltas will naturally be bigger for longer frames, so " + "in this case the default deltaTime scaling should be canceled.")] public bool CancelDeltaTime = false; /// public float GetValue( UnityEngine.Object context, IInputAxisOwner.AxisDescriptor.Hints hint) { float inputValue = 0; #if CINEMACHINE_UNITY_INPUTSYSTEM if (InputAction != null) { if (context is CinemachineInputAxisController c) inputValue = ResolveAndReadInputAction(c, hint) * Gain; } #endif #if ENABLE_LEGACY_INPUT_MANAGER if (inputValue == 0 && !string.IsNullOrEmpty(LegacyInput)) { try { inputValue = CinemachineCore.GetInputAxis(LegacyInput) * LegacyGain; } catch (ArgumentException) {} //catch (ArgumentException e) { Debug.LogError(e.ToString()); } } #endif return (Time.deltaTime > 0 && CancelDeltaTime) ? inputValue / Time.deltaTime : inputValue; } #if CINEMACHINE_UNITY_INPUTSYSTEM float ResolveAndReadInputAction( CinemachineInputAxisController context, IInputAxisOwner.AxisDescriptor.Hints hint) { // Resolve Action for player if (m_CachedAction != null && InputAction.action.id != m_CachedAction.id) m_CachedAction = null; if (m_CachedAction == null) { m_CachedAction = InputAction.action; if (context.PlayerIndex != -1) m_CachedAction = GetFirstMatch(InputUser.all[context.PlayerIndex], InputAction); if (context.AutoEnableInputs && m_CachedAction != null) m_CachedAction.Enable(); // local function to wrap the lambda which otherwise causes a tiny gc static InputAction GetFirstMatch(in InputUser user, InputActionReference aRef) { var iter = user.actions.GetEnumerator(); while (iter.MoveNext()) if (iter.Current.id == aRef.action.id) return iter.Current; return null; } } // Update enabled status if (m_CachedAction != null && m_CachedAction.enabled != InputAction.action.enabled) { if (InputAction.action.enabled) m_CachedAction.Enable(); else m_CachedAction.Disable(); } // Read the value if (m_CachedAction != null) { // If client installed an override, use it if (context.ReadControlValueOverride != null) return context.ReadControlValueOverride.Invoke(m_CachedAction, hint, context, ReadInput); return ReadInput(m_CachedAction, hint, context, null); } return 0; } /// /// Definition of how we read input. Override this in your child classes to specify /// the InputAction's type to read if it is different from float or Vector2. /// /// The action being read. /// The axis hint of the action. /// The owner object, can be used for logging diagnostics /// Not used /// Returns the value of the input device. float ReadInput( InputAction action, IInputAxisOwner.AxisDescriptor.Hints hint, UnityEngine.Object context, ControlValueReader defaultReader) { var control = action.activeControl; if (control != null) { try { // If we can read as a Vector2, do so if (control.valueType == typeof(Vector2) || action.expectedControlType == "Vector2") { var value = action.ReadValue(); return hint == IInputAxisOwner.AxisDescriptor.Hints.Y ? value.y : value.x; } // Default: assume type is float return action.ReadValue(); } catch (InvalidOperationException) { Debug.LogError($"An action in {context.name} is mapped to a {control.valueType.Name} " + "control. CinemachineInputAxisController.Reader can only handle float or Vector2 types. " + "To handle other types you can install a custom handler for " + "CinemachineInputAxisController.ReadControlValueOverride."); } } return 0f; } #endif } } }