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
}
}
}