using UnityEngine;
using System;
using System.Collections.Generic;
namespace Unity.Cinemachine
{
///
/// Components that hold InputAxisValue structs must implement this interface to be discoverable.
///
public interface IInputAxisOwner
{
///
/// Describes an axis for an axis driver
///
public struct AxisDescriptor
{
/// Delegate to get a reference to the axis being driven
/// A reference to the axis being driven
public delegate ref InputAxis AxisGetter();
/// The axis to drive
public AxisGetter DrivenAxis;
/// The name to display for the axis
public string Name;
///
/// This provides a hint about the intended usage of the axis.
///
public enum Hints
{
/// No hint
Default,
/// Mapping should be the first dimension of a multi-dimensional action
X,
/// Mapping should be the second dimension of a multi-dimensional action
Y
};
/// Indicates what is the intended usage of the axis.
public Hints Hint;
}
///
/// Report the input axis to be driven, and their names
///
/// Axes to drive
public void GetInputAxes(List axes);
}
///
/// Components that can generate an input axis reset must implement this interface.
///
public interface IInputAxisResetSource
{
///
/// Register a handler that will be called when input needs to be reset
///
/// Then handler to register
public void RegisterResetHandler(Action handler);
///
/// Unregister a handler that will be called when input needs to be reset
///
/// Then handler to unregister
public void UnregisterResetHandler(Action handler);
/// Checks whether any reset handlers have been registered
/// True if at least one reset handler is registered
public bool HasResetHandler { get; }
}
/// Abstraction for reading the value of an input axis
public interface IInputAxisReader
{
/// Get the current value of the axis.
/// The owner GameObject, can be used for logging diagnostics
/// A hint for converting a Vector2 value to a float
/// The axis value
public float GetValue(
UnityEngine.Object context,
IInputAxisOwner.AxisDescriptor.Hints hint);
}
///
/// Defines an input axis. This is a field that can take on any value in a range,
/// with optional wrapping to form a loop.
///
[Serializable]
public struct InputAxis
{
/// The current value of the axis. You can drive this directly from a script
[Tooltip("The current value of the axis. You can drive this directly from a script.")]
[NoSaveDuringPlay]
public float Value;
/// The centered, or at-rest value of this axis.
[Delayed, Tooltip("The centered, or at-rest value of this axis.")]
public float Center;
/// The valid range for the axis value. Value will be clamped to this range.
[Tooltip("The valid range for the axis value. Value will be clamped to this range.")]
[Vector2AsRange]
public Vector2 Range;
/// If set, then the axis will wrap around at the min/max values, forming a loop
[Tooltip("If set, then the axis will wrap around at the min/max values, forming a loop")]
public bool Wrap;
/// Defines the settings for automatic re-centering
[Serializable]
public struct RecenteringSettings
{
/// If set, will enable automatic re-centering of the axis
[Tooltip("If set, will enable automatic re-centering of the axis")]
public bool Enabled;
/// If no user input has been detected on the axis for this man
/// seconds, re-centering will begin.
[Tooltip("If no user input has been detected on the axis for this many "
+ "seconds, re-centering will begin.")]
public float Wait;
/// How long it takes to reach center once re-centering has started
[Tooltip("How long it takes to reach center once re-centering has started.")]
public float Time;
/// Default value
public static RecenteringSettings Default => new() { Wait = 1, Time = 2 };
/// Call from OnValidate: Make sure the fields are sensible
public void Validate()
{
Wait = Mathf.Max(0, Wait);
Time = Mathf.Max(0, Time);
}
}
/// Controls automatic re-centering of axis
[FoldoutWithEnabledButton]
public RecenteringSettings Recentering;
/// Some usages require restricted functionality.
/// The possible restrictions are defined here.
[Flags]
public enum RestrictionFlags
{
/// No restrictions
None = 0,
/// Range and center are not editable by the user
RangeIsDriven = 1,
/// Indicates that re-centering this axis is not possible
NoRecentering = 2,
/// Axis represents a momentary spring-back control
Momentary = 4,
};
/// Some usages require restricted functionality. This is set here.
[HideInInspector]
public RestrictionFlags Restrictions;
/// Clamp the value to range, taking wrap into account
/// The value to clamp
/// The value clamped to the axis range
public float ClampValue(float v)
{
float r = Range.y - Range.x;
if (!Wrap || r < UnityVectorExtensions.Epsilon)
return Mathf.Clamp(v, Range.x, Range.y);
var v1 = (v - Range.x) % r;
v1 += v1 < 0 ? r : 0;
return v1 + Range.x;
}
/// Clamp and scale the value to range 0...1, taking wrap into account
/// The axis value, mapped onto [0...1]
public float GetNormalizedValue()
{
float v = ClampValue(Value);
float r = Range.y - Range.x;
return (v - Range.x) / (r > UnityVectorExtensions.Epsilon ? r : 1);
}
/// Get the clamped axis value
/// The axis value, clamped to the axis range
public float GetClampedValue() => ClampValue(Value);
/// Make sure the settings are well-formed
public void Validate()
{
Range.y = Mathf.Max(Range.x, Range.y);
Center = ClampValue(Center);
Value = ClampValue(Value);
Recentering.Validate();
}
/// Reset axis to at-rest state
public void Reset()
{
CancelRecentering();
if (Recentering.Enabled && (Restrictions & RestrictionFlags.NoRecentering) == 0)
Value = ClampValue(Center);
}
/// An InputAxis set up as a normalized momentary control ranging from -1...1 with Center = 0
public static InputAxis DefaultMomentary => new ()
{
Range = new Vector2(-1, 1),
Restrictions = RestrictionFlags.NoRecentering | RestrictionFlags.Momentary
};
/// Internal state for re-centering
struct RecenteringState
{
public const float k_Epsilon = UnityVectorExtensions.Epsilon;
public float m_RecenteringVelocity;
public bool m_ForceRecenter;
public float m_LastValueChangeTime;
public float m_LastValue;
public static float CurrentTime => CinemachineCore.CurrentUnscaledTime;
}
RecenteringState m_RecenteringState;
///
/// Call this before calling UpdateRecentering. Will track any value changes so that the re-centering clock
/// is updated properly.
///
/// True if value changed. This value can be used to cancel re-centering when multiple
/// input axes are coupled.
public bool TrackValueChange()
{
var v = ClampValue(Value);
if (v != m_RecenteringState.m_LastValue)
{
m_RecenteringState.m_LastValueChangeTime = RecenteringState.CurrentTime;
m_RecenteringState.m_LastValue = v;
return true;
}
return false;
}
internal void SetValueAndLastValue(float value)
{
Value = m_RecenteringState.m_LastValue = value;
}
/// Call this to manage re-centering axis value to axis center.
/// This assumes that TrackValueChange() has been called already this frame.
/// Current deltaTime, or -1 for immediate re-centering
/// If true, cancel any re-centering currently in progress and reset the timer.
public void UpdateRecentering(float deltaTime, bool forceCancel)
{
if ((Restrictions & (RestrictionFlags.NoRecentering | RestrictionFlags.Momentary)) != 0)
return;
if (forceCancel)
{
CancelRecentering();
return;
}
if (m_RecenteringState.m_ForceRecenter || (Recentering.Enabled && deltaTime < 0))
{
Value = Center;
CancelRecentering();
}
else if (m_RecenteringState.m_ForceRecenter
|| (Recentering.Enabled && RecenteringState.CurrentTime
- m_RecenteringState.m_LastValueChangeTime >= Recentering.Wait))
{
var v = ClampValue(Value);
var c = Center;
var distance = Mathf.Abs(c - v);
if (distance < RecenteringState.k_Epsilon || Recentering.Time < RecenteringState.k_Epsilon)
{
v = c;
m_RecenteringState.m_RecenteringVelocity = 0;
}
else
{
// Determine the direction
float r = Range.y - Range.x;
if (Wrap && distance > r * 0.5f)
v += Mathf.Sign(c - v) * r;
// Damp our way there
v = Mathf.SmoothDamp(
v, c, ref m_RecenteringState.m_RecenteringVelocity,
Recentering.Time * 0.5f, 9999, deltaTime);
}
Value = m_RecenteringState.m_LastValue = ClampValue(v);
// Are we there yet?
if (Mathf.Abs(Value - c) < RecenteringState.k_Epsilon)
m_RecenteringState.m_ForceRecenter = false;
}
}
/// Trigger re-centering immediately, regardless of whether re-centering
/// is enabled or the wait time has elapsed.
public void TriggerRecentering() => m_RecenteringState.m_ForceRecenter = true;
/// Cancel any current re-centering in progress, and reset the wait time
public void CancelRecentering()
{
m_RecenteringState.m_LastValueChangeTime = RecenteringState.CurrentTime;
m_RecenteringState.m_LastValue = ClampValue(Value);
m_RecenteringState.m_RecenteringVelocity = 0;
m_RecenteringState.m_ForceRecenter = false;
}
}
///
/// This object drives an input axis.
/// It reads raw input, applies it to the axis value, with acceleration and deceleration.
///
[Serializable]
public struct DefaultInputAxisDriver
{
/// Internal state
float m_CurrentSpeed;
/// 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 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 DecelTime;
/// Call from OnValidate: Make sure the fields are sensible
public void Validate()
{
AccelTime = Mathf.Max(0, AccelTime);
DecelTime = Mathf.Max(0, DecelTime);
}
/// Default value
public static DefaultInputAxisDriver Default => new () { AccelTime = 0.2f, DecelTime = 0.2f };
/// Apply the input value to the axis value
/// The InputAxisValue to update
/// The input value to apply to the axis value.
/// current deltaTime
public void ProcessInput(ref InputAxis axis, float inputValue, float deltaTime)
{
const float k_Epsilon = UnityVectorExtensions.Epsilon;
var dampTime = Mathf.Abs(inputValue) < Mathf.Abs(m_CurrentSpeed) ? DecelTime : AccelTime;
if ((axis.Restrictions & InputAxis.RestrictionFlags.Momentary) == 0)
{
if (deltaTime < 0)
m_CurrentSpeed = 0;
else
{
m_CurrentSpeed += Damper.Damp(inputValue - m_CurrentSpeed, dampTime, deltaTime);
// Decelerate to the end points of the range if not wrapping
if (!axis.Wrap && DecelTime > k_Epsilon && Mathf.Abs(m_CurrentSpeed) > k_Epsilon)
{
var v0 = axis.ClampValue(axis.Value);
var d = (m_CurrentSpeed > 0) ? axis.Range.y - v0 : v0 - axis.Range.x;
var maxSpeed = 0.1f + 4 * d / DecelTime;
if (Mathf.Abs(m_CurrentSpeed) > Mathf.Abs(maxSpeed))
m_CurrentSpeed = maxSpeed * Mathf.Sign(m_CurrentSpeed);
}
}
axis.Value = axis.ClampValue(axis.Value + m_CurrentSpeed * deltaTime);
}
else
{
// For momentary controls, input is the desired offset from center
if (deltaTime < 0)
axis.Value = axis.Center;
else
{
var desiredValue = axis.ClampValue(inputValue + axis.Center);
axis.Value += Damper.Damp(desiredValue - axis.Value, dampTime, deltaTime);
}
}
}
/// Reset an axis to at-rest state
/// The axis to reset
public void Reset(ref InputAxis axis)
{
m_CurrentSpeed = 0;
axis.Reset();
}
}
}