258 lines
6.9 KiB
C#
258 lines
6.9 KiB
C#
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// Procedural platform animation supporting translation, rotation, and scaling.
|
|
/// Supports multiple animation types (Constant, PingPong, Loop, Single) with easing.
|
|
/// </summary>
|
|
[DefaultExecutionOrder(0)]
|
|
public class ProceduralAnimator : MonoBehaviour
|
|
{
|
|
public enum AnimationSpace
|
|
{
|
|
Local,
|
|
World
|
|
}
|
|
|
|
public enum AnimationType
|
|
{
|
|
Constant, // Continuous movement without return
|
|
PingPong, // Back and forth (A → B → A)
|
|
Loop, // Loop with teleport (A → B, teleport to A)
|
|
Single // Play once then stop
|
|
}
|
|
|
|
public enum AnimationBalance
|
|
{
|
|
Centered,
|
|
Forward,
|
|
Backward
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class Settings
|
|
{
|
|
[Tooltip("Animation space")]
|
|
public AnimationSpace Space = AnimationSpace.Local;
|
|
|
|
[Tooltip("Type of animation loop")]
|
|
public AnimationType Type = AnimationType.PingPong;
|
|
|
|
[Tooltip("TCenter of animation")]
|
|
public AnimationBalance Balance = AnimationBalance.Centered;
|
|
|
|
[Tooltip("Custom animation curve")]
|
|
public AnimationCurve Curve = AnimationCurve.EaseInOut(0, 0, 1, 1);
|
|
|
|
[Tooltip("Play on Awake")]
|
|
public bool PlayOnAwake = true;
|
|
|
|
[Tooltip("Animation speed multiplier")]
|
|
public float Duration = 4;
|
|
|
|
[Tooltip("Time offset to desynchronize multiple animations")]
|
|
public float TimeOffset = 0;
|
|
|
|
[Tooltip("Translation amount in local space (meters)")]
|
|
public Vector3 Translation = Vector3.zero;
|
|
|
|
[Tooltip("Rotation amount in degrees")]
|
|
public Vector3 Rotation = Vector3.zero;
|
|
|
|
[Tooltip("Scale amount relative to initial scale")]
|
|
public Vector3 Scale = Vector3.zero;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class State
|
|
{
|
|
[Tooltip("Current animation time")]
|
|
public float CurrentTime;
|
|
|
|
[Tooltip("Is animation finished? (Single only)")]
|
|
public bool IsFinished;
|
|
|
|
[HideInInspector] public Vector3 InitialPosition;
|
|
[HideInInspector] public Quaternion InitialRotation;
|
|
[HideInInspector] public Vector3 InitialScale;
|
|
}
|
|
|
|
[SerializeField] private Settings _settings;
|
|
[SerializeField] private State _state;
|
|
|
|
private bool _started;
|
|
private float _timeOffset;
|
|
|
|
void OnDrawGizmosSelected()
|
|
{
|
|
|
|
}
|
|
|
|
void OnEnable()
|
|
{
|
|
_timeOffset = Time.timeSinceLevelLoad;
|
|
_started = _settings.PlayOnAwake;
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
_state.InitialPosition = transform.localPosition;
|
|
_state.InitialRotation = transform.localRotation;
|
|
_state.InitialScale = transform.localScale;
|
|
_state.CurrentTime = _settings.TimeOffset;
|
|
_state.IsFinished = false;
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (_state.IsFinished || !_started)
|
|
return;
|
|
|
|
_state.CurrentTime = Time.timeSinceLevelLoad + _settings.TimeOffset * _settings.Duration - _timeOffset;
|
|
|
|
float normalizedTime = GetNormalizedTime(_state.CurrentTime);
|
|
float easedTime = ApplyEasing(normalizedTime);
|
|
float balanceTime = ApplyBalance(easedTime);
|
|
|
|
ApplyTranslation(balanceTime);
|
|
ApplyRotation(balanceTime);
|
|
ApplyScale(balanceTime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Play animation from initial state.
|
|
/// </summary>
|
|
[ContextMenu("PlayAnimation")]
|
|
public void PlayAnimation()
|
|
{
|
|
ResetAnimation();
|
|
_timeOffset = Time.timeSinceLevelLoad;
|
|
_started = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets animation to initial state.
|
|
/// </summary>
|
|
public void ResetAnimation()
|
|
{
|
|
_state.CurrentTime = _settings.TimeOffset;
|
|
_state.IsFinished = false;
|
|
transform.localPosition = _state.InitialPosition;
|
|
transform.localRotation = _state.InitialRotation;
|
|
transform.localScale = _state.InitialScale;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pauses or resumes animation.
|
|
/// </summary>
|
|
public void SetPaused(bool paused)
|
|
{
|
|
enabled = !paused;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets normalized time [0,1] based on animation type.
|
|
/// </summary>
|
|
private float GetNormalizedTime(float time)
|
|
{
|
|
if (_settings.Duration == 0)
|
|
return 0;
|
|
|
|
switch (_settings.Type)
|
|
{
|
|
case AnimationType.Constant:
|
|
return time / _settings.Duration;
|
|
|
|
case AnimationType.PingPong:
|
|
return Mathf.PingPong(time / _settings.Duration, 1);
|
|
|
|
case AnimationType.Loop:
|
|
return Mathf.Repeat(time / _settings.Duration, 1f);
|
|
|
|
case AnimationType.Single:
|
|
if (time >= _settings.Duration)
|
|
{
|
|
_state.IsFinished = true;
|
|
return 1;
|
|
}
|
|
return time / _settings.Duration;
|
|
|
|
default:
|
|
return 0f;
|
|
}
|
|
}
|
|
|
|
private float ApplyBalance(float time)
|
|
{
|
|
switch (_settings.Balance)
|
|
{
|
|
case AnimationBalance.Centered:
|
|
return time - .5f;
|
|
|
|
case AnimationBalance.Backward:
|
|
return -time;
|
|
|
|
default:
|
|
return time;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies easing function to normalized time.
|
|
/// </summary>
|
|
private float ApplyEasing(float time)
|
|
{
|
|
switch (_settings.Type)
|
|
{
|
|
default:
|
|
return _settings.Curve.Evaluate(time); ;
|
|
|
|
case AnimationType.Constant:
|
|
return time;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies translation based on eased time.
|
|
/// </summary>
|
|
private void ApplyTranslation(float time)
|
|
{
|
|
if (_settings.Translation == Vector3.zero)
|
|
return;
|
|
|
|
Vector3 offset = _settings.Translation * time;
|
|
if (_settings.Space == AnimationSpace.Local)
|
|
offset = _state.InitialRotation * offset;
|
|
|
|
transform.localPosition = _state.InitialPosition + offset;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies rotation based on eased time.
|
|
/// </summary>
|
|
private void ApplyRotation(float time)
|
|
{
|
|
if (_settings.Rotation == Vector3.zero)
|
|
return;
|
|
|
|
Vector3 rotation = _settings.Rotation * time;
|
|
Quaternion rotationOffset = Quaternion.Euler(rotation);
|
|
if (_settings.Space == AnimationSpace.Local)
|
|
rotationOffset = _state.InitialRotation * rotationOffset;
|
|
else
|
|
rotationOffset = rotationOffset * _state.InitialRotation;
|
|
|
|
transform.localRotation = rotationOffset;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies scale based on eased time.
|
|
/// </summary>
|
|
private void ApplyScale(float t)
|
|
{
|
|
if (_settings.Scale == Vector3.zero)
|
|
return;
|
|
|
|
Vector3 scale = Vector3.one + _settings.Scale * t;
|
|
transform.localScale = Vector3.Scale(_state.InitialScale, scale);
|
|
}
|
|
} |