using UnityEngine;
namespace Heroes.Battle
{
///
/// Forces an to update at a fixed low frame rate, giving
/// the discrete, "stop-motion" look of Heroes of Might & Magic III sprites.
///
/// Instead of letting Unity interpolate the pose every rendered frame, we disable
/// the Animator's automatic update and step it ourselves in fixed chunks of
/// 1 / targetFps seconds. Between steps the pose is frozen, so movement
/// reads as a sequence of snapping poses rather than smooth motion.
///
/// Works on any Animator/clip without editing the animation assets. Parameters
/// (SetBool/SetTrigger) are still honoured — they are consumed on the next
/// manual call.
///
[RequireComponent(typeof(Animator))]
public sealed class SteppedAnimator : MonoBehaviour
{
[Tooltip("How many animation frames per second to sample. Heroes 3 feel: ~8-12. " +
"Lower = more choppy/retro, higher = smoother.")]
[Range(2f, 30f)]
public float targetFps = 10f;
[Tooltip("Use unscaled time so the stepping keeps running while the game is paused (Time.timeScale = 0).")]
public bool useUnscaledTime = false;
private Animator _animator;
private float _accumulator;
private void Awake()
{
_animator = GetComponent();
// Stop Unity's per-frame automatic update; we drive it manually in Update().
_animator.enabled = false;
}
private void OnEnable()
{
_accumulator = 0f;
}
private void Update()
{
if (_animator == null || targetFps <= 0f) return;
float step = 1f / targetFps;
_accumulator += useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
// Advance the pose in discrete chunks. If the game hitches and several
// steps are due at once we still apply them, so timing stays correct
// while the *visible* pose only ever changes on a step boundary.
int guard = 0;
while (_accumulator >= step && guard++ < 8)
{
_animator.Update(step);
_accumulator -= step;
}
// Clamp leftover so a long stall doesn't queue a burst of catch-up steps.
if (_accumulator > step) _accumulator = step;
}
/// Change the sampling rate at runtime (e.g. per unit type).
public void SetTargetFps(float fps)
{
targetFps = Mathf.Max(1f, fps);
}
}
}