AnimatedSprite2D: кадры, атлас и управление временем
Собрать SpriteFrames и различать паузу, остановку, цикл и изменение скорости.
Разделить данные и воспроизведение
SpriteFrames — ресурс с именованными анимациями, текстурами кадров и длительностями. AnimatedSprite2D хранит состояние проигрывания: Animation, Frame, FrameProgress, SpeedScale. Две Entity могут использовать один ресурс кадров, сохраняя собственные позиции воспроизведения. Изменение общего SpriteFrames затронет обе; создайте отдельный ресурс, если набор кадров должен отличаться.
Для runnable-примера возьмите четыре файла и порядок запуска из движения 2D. ActorFrames.cs ниже полностью создаёт атлас 128×32, четыре региона по 32×32 и клипы idle/walk. Сторонние текстуры, импорт и ручная нарезка не нужны.
ActorFrames.cs
using Reckfell;
public static class ActorFrames
{
public static SpriteFrames Create()
{
using var image = Image.CreateEmpty(128, 32, false, Image.Format.Rgba8);
image.Fill(Colors.Transparent);
for (int i = 0; i < 4; i++)
{
int x = i * 32;
int stride = i == 1 ? 3 : i == 3 ? -3 : 0;
image.FillRect(new Rect2I(x + 9, 4, 14, 16), Colors.White);
image.FillRect(new Rect2I(x + 19, 8, 3, 3), Colors.Black);
image.FillRect(new Rect2I(x + 9 + stride, 20, 5, 8), new Color(0.25f, 0.45f, 1));
image.FillRect(new Rect2I(x + 18 - stride, 20, 5, 8), new Color(0.25f, 0.45f, 1));
}
var atlas = ImageTexture.CreateFromImage(image);
var frames = new SpriteFrames();
frames.RemoveAnimation("default");
frames.AddAnimation("idle");
frames.SetAnimationLoopMode("idle", SpriteFrames.LoopMode.Linear);
frames.SetAnimationSpeed("idle", 1);
frames.AddAnimation("walk");
frames.SetAnimationLoopMode("walk", SpriteFrames.LoopMode.Linear);
frames.SetAnimationSpeed("walk", 8);
for (int i = 0; i < 4; i++)
{
var region = new AtlasTexture { Atlas = atlas, Region = new Rect2(i * 32, 0, 32, 32) };
frames.AddFrame("walk", region, i == 2 ? 2 : 1);
if (i == 0) frames.AddFrame("idle", region);
}
return frames;
}
}Подставить свой атлас
Замените создание Image/ImageTexture загрузкой своей Texture2D и оставьте нарезку через AtlasTexture. Region задаётся в пикселях исходного изображения, не в диапазоне 0..1. Для сетки с отступами и промежутками учитывайте их при расчёте начала каждого региона. Держите одинаковое положение опорной точки персонажа внутри кадров: иначе картинка будет дёргаться даже при неподвижной Entity.
TextureFilter=Nearest в TopDownActor сохраняет жёсткие пиксельные границы. Это не настраивает импорт, размер Viewport или общий масштаб игры. FlipH зеркалит только изображение; Offset и Centered определяют его размещение относительно позиции Entity. При смене рисунка настройте физическую Shape отдельно.
Посчитать длительность
При положительной скорости длительность кадра в секундах равна relativeDuration / (animationFps × abs(SpeedScale × customSpeed)). customSpeed — аргумент Play. В примере walk имеет FPS=8 и веса 1,1,2,1. Полный линейный цикл длится 5/8=0,625 секунды при обоих множителях 1. Вес 2 означает удвоенную длительность кадра, а не две секунды.
SpeedScale=0 замораживает продвижение, но IsPlaying остаётся true, если проигрывание не остановлено. GetPlayingSpeed возвращает множитель со знаком, без FPS и веса кадра. Не используйте один IsPlaying для вывода «кадры сейчас меняются».
Управлять курсором
| Действие | Результат |
|---|---|
| Play("walk") | Запуск; после Pause продолжает тот же клип с сохранённого места |
| Pause() | Сохраняет Frame и FrameProgress, выключает проигрывание |
| Stop() | Останавливает и сбрасывает курсор к началу |
| SetFrameAndProgress(frame, progress) | Явно задаёт кадр и его долю; обычно progress в [0,1] |
| PlayBackwards("walk") | Запускает обратное проигрывание; положительный SpeedScale нужен для ожидаемого направления |
Смена клипа через Play выбирает его начало, если не задан fromEnd. Чтобы принудительно перезапустить уже выбранный клип, вызовите Stop, затем Play. Не сбрасывайте Frame=0 в каждом _Process: анимация будет застревать на первом кадре. В движении из примера Play вызывается только при смене имени состояния.
Выбрать цикл и дождаться завершения
Используйте актуальный SetAnimationLoopMode: LoopMode.None для однократного проигрывания, Linear для обычного цикла, Pingpong для смены направления на концах. Пример нативно проверен для Linear и None; режим Pingpong сверён с исходником.
AnimationFinished приходит для завершившегося однократного клипа. Для циклического клипа используйте AnimationLooped. Если ждать AnimationFinished у ходьбы с Linear, ожидание не закончится при очередном обороте. FrameChanged означает изменение кадра, а не обязательно завершение действия или игровой удар.
Для атаки или другого игрового события задайте отдельное правило синхронизации: покадровая картинка сама не создаёт хитбокс и не гарантирует один игровой эффект на входной сигнал.