This commit is contained in:
iisaev committed 2026-10-18 12:41:38 +03:00
commit acb9dcaaaf
11 files changed
+510

No files matched your search

+71
View File
@@ -0,0 +1,71 @@
using Cthangover.Core.UI.Event;
using Godot;
namespace Cthangover.Mods.Effects.Animation
{
/// <summary>
/// Shader-driven frame animation: drives _FrameIndex and _FrameCount uniforms
/// on the TextureRect's ShaderMaterial rather than swapping textures. This means
/// all animation frames live in a single GPU texture atlas and the shader selects
/// the visible frame. Auto-destroys (QueueFree) after totalDuration elapses,
/// making it suitable for fire-and-forget VFX that don't need external cleanup.
/// </summary>
public partial class EffectAnimator : Control, IOnUpdateEvent
{
[Export] private TextureRect image;
[Export] private float totalDuration = 5f;
[Export] private int frameCount = 5;
private ShaderMaterial materialInstance;
private double startTime;
private float frameInterval;
private int currentFrame;
private static readonly StringName FrameIndexId = "_FrameIndex";
private static readonly StringName FrameCountId = "_FrameCount";
/// <summary>Precomputes the frame interval and pushes the frame count uniform into the shader material.</summary>
public override void _Ready()
{
frameInterval = totalDuration / frameCount;
materialInstance = image.Material as ShaderMaterial;
if (materialInstance != null)
materialInstance.SetShaderParameter(FrameCountId, frameCount);
}
/// <summary>Intentionally empty — the animator drives frames through the event controller instead of engine callbacks.</summary>
public override void _EnterTree()
{
}
/// <summary>Starts the shader animation from frame 0, resetting the elapsed timer.</summary>
public void Play()
{
startTime = Godot.Time.GetTicksUsec() / 1_000_000.0;
currentFrame = 0;
if (materialInstance != null)
materialInstance.SetShaderParameter(FrameIndexId, 0f);
}
/// <summary>Update priority. Default 1.</summary>
public int Priority => 1;
/// <summary>Advances the shader's _FrameIndex uniform each frame based on elapsed time. Self-destructs when <c>totalDuration</c> elapses.</summary>
public void OnUpdate()
{
double now = Godot.Time.GetTicksUsec() / 1_000_000.0;
var elapsed = now - startTime;
var newFrame = Mathf.Min((int)(elapsed / frameInterval), frameCount - 1);
if (newFrame != currentFrame && materialInstance != null)
{
currentFrame = newFrame;
materialInstance.SetShaderParameter(FrameIndexId, currentFrame);
}
if (elapsed >= totalDuration)
QueueFree();
}
}
}