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(); } 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]; } }