Files
soccar2d/Assets/Scripts/Utils/UI/FrameAnimator.cs
T
2026-08-12 13:45:19 +05:30

272 lines
6.0 KiB
C#

using UnityEngine.UI;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteAlways]
public class FrameAnimator : MonoBehaviour
{
public Sprite[] sprites;
public float frameRate = 10f;
public bool preview;
public bool isPlaying = false;
public bool loop = true;
public int currentFrame = 0;
float m_frameTimer = 0f;
bool m_previewPlaying = false;
#if UNITY_EDITOR
double m_lastEditorTime = 0d;
bool m_editorHooked;
#endif
Image m_img;
Image img {
get{
if(m_img==null){
m_img= GetComponent<Image>();
}
return m_img;
}
}
void OnValidate()
{
if(preview)
{
preview=false;
StartPreviewOnce();
}
}
void OnEnable()
{
#if UNITY_EDITOR
// Never hook editor updates during play-mode enter/exit — QueuePlayerLoopUpdate
// + isPlaying serialized true can infinite-reload Temp/__Backupscenes.
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
TryHookEditorUpdate();
#endif
}
void OnDisable()
{
#if UNITY_EDITOR
UnhookEditorUpdate();
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
#endif
}
void Start()
{
if (sprites == null || sprites.Length == 0)
{
return;
}
currentFrame = Mathf.Clamp(currentFrame, 0, sprites.Length - 1);
ApplyCurrentFrame();
}
// Update is called once per frame
void Update()
{
if (!Application.isPlaying)
{
return;
}
TickAnimation(Time.deltaTime);
}
#if UNITY_EDITOR
void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.ExitingEditMode ||
state == PlayModeStateChange.EnteredPlayMode ||
state == PlayModeStateChange.ExitingPlayMode)
{
m_previewPlaying = false;
UnhookEditorUpdate();
}
else if (state == PlayModeStateChange.EnteredEditMode)
{
TryHookEditorUpdate();
}
}
void TryHookEditorUpdate()
{
if (Application.isPlaying || EditorApplication.isPlayingOrWillChangePlaymode)
{
UnhookEditorUpdate();
return;
}
if (m_editorHooked)
return;
m_lastEditorTime = EditorApplication.timeSinceStartup;
EditorApplication.update += OnEditorUpdate;
m_editorHooked = true;
}
void UnhookEditorUpdate()
{
if (!m_editorHooked)
return;
EditorApplication.update -= OnEditorUpdate;
m_editorHooked = false;
}
void OnEditorUpdate()
{
if (Application.isPlaying ||
EditorApplication.isPlayingOrWillChangePlaymode ||
this == null ||
!isActiveAndEnabled)
{
UnhookEditorUpdate();
return;
}
// Edit-mode animation is preview-only. Ignoring serialized isPlaying prevents
// permanent editor loops when isPlaying was left true in the scene.
if (!m_previewPlaying)
return;
double now = EditorApplication.timeSinceStartup;
float dt = (float)(now - m_lastEditorTime);
m_lastEditorTime = now;
if (dt <= 0f)
return;
int frameBefore = currentFrame;
TickAnimation(dt);
if (currentFrame != frameBefore || m_previewPlaying)
EditorApplication.QueuePlayerLoopUpdate();
}
#endif
void TickAnimation(float deltaTime)
{
if (frameRate <= 0f || sprites == null || sprites.Length == 0)
{
return;
}
bool shouldAnimate = Application.isPlaying
? (m_previewPlaying || isPlaying)
: m_previewPlaying;
if (!shouldAnimate)
{
return;
}
m_frameTimer += deltaTime;
float frameDuration = 1f / frameRate;
while (m_frameTimer >= frameDuration)
{
m_frameTimer -= frameDuration;
if (m_previewPlaying)
{
AdvancePreviewFrame();
}
else
{
AdvanceFrame();
}
}
}
public void Play(bool restart = false)
{
if (sprites == null || sprites.Length == 0)
{
return;
}
if (restart)
{
currentFrame = 0;
m_frameTimer = 0f;
ApplyCurrentFrame();
}
isPlaying = true;
}
public void Stop()
{
isPlaying = false;
m_frameTimer = 0f;
}
public void ResetToFirstFrame()
{
currentFrame = 0;
m_frameTimer = 0f;
ApplyCurrentFrame();
}
void StartPreviewOnce()
{
if (sprites == null || sprites.Length == 0)
{
return;
}
isPlaying = false;
m_previewPlaying = true;
m_frameTimer = 0f;
currentFrame = 0;
ApplyCurrentFrame();
}
void AdvancePreviewFrame()
{
currentFrame++;
if (currentFrame >= sprites.Length)
{
currentFrame = sprites.Length - 1;
m_previewPlaying = false;
}
ApplyCurrentFrame();
}
void AdvanceFrame()
{
currentFrame++;
if (currentFrame >= sprites.Length)
{
if (loop)
{
currentFrame = 0;
}
else
{
currentFrame = sprites.Length - 1;
isPlaying = false;
}
}
ApplyCurrentFrame();
}
void ApplyCurrentFrame()
{
if (img == null || sprites == null || sprites.Length == 0)
{
return;
}
currentFrame = Mathf.Clamp(currentFrame, 0, sprites.Length - 1);
img.sprite = sprites[currentFrame];
}
}