using UnityEngine; using System; using Cinemachine.Utility; using UnityEngine.Serialization; namespace Cinemachine { /// /// Axis state for defining to react to player input. /// The settings here control the responsiveness of the axis to player input. /// [DocumentationSorting(6.4f, DocumentationSortingAttribute.Level.UserRef)] [Serializable] public struct AxisState { /// The current value of the axis [NoSaveDuringPlay] [Tooltip("The current value of the axis.")] public float Value; /// How fast the axis value can travel. Increasing this number /// makes the behaviour more responsive to joystick input [Tooltip("The maximum speed of this axis in units/second")] public float m_MaxSpeed; /// The amount of time in seconds it takes to accelerate to /// MaxSpeed with the supplied Axis at its maximum value [Tooltip("The amount of time in seconds it takes to accelerate to MaxSpeed with the supplied Axis at its maximum value")] public float m_AccelTime; /// The amount of time in seconds it takes to decelerate /// the axis to zero if the supplied axis is in a neutral position [Tooltip("The amount of time in seconds it takes to decelerate the axis to zero if the supplied axis is in a neutral position")] public float m_DecelTime; /// The name of this axis as specified in Unity Input manager. /// Setting to an empty string will disable the automatic updating of this axis [FormerlySerializedAs("m_AxisName")] [Tooltip("The name of this axis as specified in Unity Input manager. Setting to an empty string will disable the automatic updating of this axis")] public string m_InputAxisName; /// The value of the input axis. A value of 0 means no input /// You can drive this directly from a /// custom input system, or you can set the Axis Name and have the value /// driven by the internal Input Manager [NoSaveDuringPlay] [Tooltip("The value of the input axis. A value of 0 means no input. You can drive this directly from a custom input system, or you can set the Axis Name and have the value driven by the internal Input Manager")] public float m_InputAxisValue; /// If checked, then the raw value of the input axis will be inverted /// before it is used. [NoSaveDuringPlay] [Tooltip("If checked, then the raw value of the input axis will be inverted before it is used")] public bool m_InvertAxis; private float mCurrentSpeed; private float mMinValue; private float mMaxValue; private bool mWrapAround; /// Constructor with specific values public AxisState( float maxSpeed, float accelTime, float decelTime, float val, string name, bool invert) { m_MaxSpeed = maxSpeed; m_AccelTime = accelTime; m_DecelTime = decelTime; Value = val; m_InputAxisName = name; m_InputAxisValue = 0; m_InvertAxis = invert; mCurrentSpeed = 0f; mMinValue = 0f; mMaxValue = 0f; mWrapAround = false; } /// Call from OnValidate: Make sure the fields are sensible public void Validate() { m_MaxSpeed = Mathf.Max(0, m_MaxSpeed); m_AccelTime = Mathf.Max(0, m_AccelTime); m_DecelTime = Mathf.Max(0, m_DecelTime); } /// /// Sets the constraints by which this axis will operate on /// /// The lowest value this axis can achieve /// The highest value this axis can achieve /// If true, values commanded greater /// than mMaxValue or less than mMinValue will wrap around. /// If false, the value will be clamped within the range. public void SetThresholds(float minValue, float maxValue, bool wrapAround) { mMinValue = minValue; mMaxValue = maxValue; mWrapAround = wrapAround; } const float Epsilon = UnityVectorExtensions.Epsilon; /// /// Updates the state of this axis based on the axis defined /// by AxisState.m_AxisName /// /// Delta time in seconds /// Returns true if this axis' input was non-zero this Update, /// flase otherwise public bool Update(float deltaTime) { if (!string.IsNullOrEmpty(m_InputAxisName)) { try { m_InputAxisValue = CinemachineCore.GetInputAxis(m_InputAxisName); } catch (ArgumentException e) { Debug.LogError(e.ToString()); } } float input = m_InputAxisValue; if (m_InvertAxis) input *= -1f; if (m_MaxSpeed > Epsilon) { float targetSpeed = input * m_MaxSpeed; if (Mathf.Abs(targetSpeed) < Epsilon || (Mathf.Sign(mCurrentSpeed) == Mathf.Sign(targetSpeed) && Mathf.Abs(targetSpeed) < Mathf.Abs(mCurrentSpeed))) { // Need to decelerate float a = Mathf.Abs(targetSpeed - mCurrentSpeed) / Mathf.Max(Epsilon, m_DecelTime); float delta = Mathf.Min(a * deltaTime, Mathf.Abs(mCurrentSpeed)); mCurrentSpeed -= Mathf.Sign(mCurrentSpeed) * delta; } else { // Accelerate to the target speed float a = Mathf.Abs(targetSpeed - mCurrentSpeed) / Mathf.Max(Epsilon, m_AccelTime); mCurrentSpeed += Mathf.Sign(targetSpeed) * a * deltaTime; if (Mathf.Sign(mCurrentSpeed) == Mathf.Sign(targetSpeed) && Mathf.Abs(mCurrentSpeed) > Mathf.Abs(targetSpeed)) { mCurrentSpeed = targetSpeed; } } } // Clamp our max speeds so we don't go crazy float maxSpeed = GetMaxSpeed(); mCurrentSpeed = Mathf.Clamp(mCurrentSpeed, -maxSpeed, maxSpeed); Value += mCurrentSpeed * deltaTime; bool isOutOfRange = (Value > mMaxValue) || (Value < mMinValue); if (isOutOfRange) { if (mWrapAround) { if (Value > mMaxValue) Value = mMinValue + (Value - mMaxValue); else Value = mMaxValue + (Value - mMinValue); } else { Value = Mathf.Clamp(Value, mMinValue, mMaxValue); mCurrentSpeed = 0f; } } return Mathf.Abs(input) > Epsilon; } // MaxSpeed may be limited as we approach the range ends, in order // to prevent a hard bump private float GetMaxSpeed() { float range = mMaxValue - mMinValue; if (!mWrapAround && range > 0) { float threshold = range / 10f; if (mCurrentSpeed > 0 && (mMaxValue - Value) < threshold) { float t = (mMaxValue - Value) / threshold; return Mathf.Lerp(0, m_MaxSpeed, t); } else if (mCurrentSpeed < 0 && (Value - mMinValue) < threshold) { float t = (Value - mMinValue) / threshold; return Mathf.Lerp(0, m_MaxSpeed, t); } } return m_MaxSpeed; } } }