272 lines
6.0 KiB
C#
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];
|
|
}
|
|
}
|