AnimationPlayer: клипы и управление
Полный пример, ключи, библиотеки, пауза, повторный запуск и завершение.
Запустить пример
- Создайте уровень с корнем
Entity3D. Добавьте в C#-проектAnimationClips.csиPlayerAnimationDemo.cs, прикрепите второй к корню. Используйте отдельный уровень для каждого примера этого раздела. - В Input Map создайте
animation_restartна R,animation_pauseна Space,animation_fallна F. Соберите проект и запустите текущий уровень через F6. - Синий куб поднимается на 2 единицы за секунду и останавливается. R начинает подъём заново; Space ставит воспроизведение на паузу или продолжает его. F запускает опускание из верхней позы.
Скрипт создаёт камеру, свет, визуальную Entity и AnimationPlayer. Это демонстрация свойств, не физическая дверь: у куба нет тела столкновения. Общий помощник нужен также примерам дорожек и AnimationTree.
AnimationClips.cs
using Reckfell;
public static class AnimationClips
{
public static Animation Value(string path, Variant from, Variant to, double seconds, bool loop = false)
{
var clip = new Animation
{
Length = seconds,
LoopMode = loop ? Animation.LoopModeEnum.Linear : Animation.LoopModeEnum.None
};
int track = clip.AddTrack(Animation.TrackType.Value);
clip.TrackSetPath(track, new EntityPath(path));
clip.ValueTrackSetUpdateMode(track, Animation.UpdateMode.Continuous);
clip.TrackInsertKey(track, 0, from);
clip.TrackInsertKey(track, seconds, to);
return clip;
}
public static AnimationLibrary Library()
{
var library = new AnimationLibrary();
library.AddAnimation("RESET", Value("Visual:position", Vector3.Zero, Vector3.Zero, .001));
var rise = Value("Visual:position", Vector3.Zero, new Vector3(0,2,0), 1);
rise.AddMarker("start", .2);
rise.AddMarker("end", .8);
library.AddAnimation("rise", rise);
library.AddAnimation("fall", Value("Visual:position", new Vector3(0,2,0), Vector3.Zero, 1));
library.AddAnimation("idle", Value("Visual:position", Vector3.Zero, Vector3.Zero, 1, true));
library.AddAnimation("walk", Value("Visual:position", new Vector3(0,1,0), new Vector3(0,1,0), 1, true));
library.AddAnimation("run", Value("Visual:position", new Vector3(0,3,0), new Vector3(0,3,0), 1, true));
var jump = Value("Visual:position", Vector3.Zero, Vector3.Zero, 1);
jump.TrackInsertKey(0, .5, new Vector3(0,2,0));
library.AddAnimation("jump", jump);
return library;
}
public static AnimationPlayer Stage(Entity3D owner)
{
var visual = new Entity3D { Name = "Visual" };
var mesh = new MeshInstance3D
{
Mesh = new BoxMesh(),
MaterialOverride = new StandardMaterial3D { AlbedoColor = new Color(.15f,.35f,1) }
};
visual.AddChild(mesh);
owner.AddChild(visual);
var camera = new Camera3D { Name = "Camera", Position = new Vector3(5,4,7), Current = true };
owner.AddChild(camera);
camera.LookAt(owner.GlobalPosition + new Vector3(0,1,0));
owner.AddChild(new DirectionalLight3D { RotationDegrees = new Vector3(-45,-30,0) });
var player = new AnimationPlayer
{
Name = "AnimationPlayer",
RootEntity = new EntityPath(".."),
PlaybackAutoCapture = false
};
owner.AddChild(player);
player.AddAnimationLibrary("", Library());
return player;
}
}PlayerAnimationDemo.cs
using Reckfell;
public partial class PlayerAnimationDemo : Entity3D
{
public AnimationPlayer Player { get; private set; } = null!;
public override void _Ready()
{
Player = AnimationClips.Stage(this);
Player.Play("rise");
}
public void RestartRise()
{
Player.Stop();
Player.Play("rise");
}
public void PauseOrResume()
{
if (Player.IsPlaying())
Player.Pause();
else
Player.Play();
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event.IsActionPressed("animation_restart"))
RestartRise();
else if (@event.IsActionPressed("animation_pause"))
PauseOrResume();
else if (@event.IsActionPressed("animation_fall"))
Player.Play("fall");
else
return;
GetViewport().SetInputAsHandled();
}
}Как найти цель дорожки
RootEntity = ".." у созданного игрока указывает на корень уровня. Поэтому Visual:position означает свойство position дочерней Entity Visual. До двоеточия — путь Entity, после — свойство или цепочка свойств ресурса.
В строковом пути используется имя свойства движка position, а не C#-имя Position. Вызов Play() планирует обработку; если значение нужно получить немедленно, используйте Advance(0) после запуска.
AnimationPlayer наследует Entity и не передаёт 3D-трансформацию как Entity3D. В примере Visual и AnimationPlayer — соседи. Для пространственной иерархии используйте Entity3D-родителей.
То же действие в редакторе
Создайте AnimationPlayer под корнем с Visual, откройте нижнюю панель Animation / Анимация, создайте клип длиной 1 с в глобальной библиотеке. Вставьте ключ Visual.position при времени 0 и значении (0,0,0), затем при 1 с и (0,2,0). Перемещайте курсор времени для проверки. Для булевых свойств выберите дискретное обновление дорожки, для движения — непрерывное.
Не добавляйте одновременно ручные клипы и PlayerAnimationDemo в одну иерархию: скрипт создаёт собственный AnimationPlayer и цели. Для импортированных анимаций выберите библиотеку импортированного ресурса или её редактируемую копию; не рассчитывайте сохранить ручные правки поверх повторного импорта.
Имена библиотек
Пустое имя библиотеки "" — глобальная библиотека, её клип запускается как "rise". У библиотеки "doors" полный ключ будет "doors/rise". Имя файла ресурса и имя клипа — разные вещи. Перед условным запуском проверяйте HasAnimation().
Управление временем
| Вызов | Результат |
|---|---|
Play("rise") | Запускает клип; повторный вызов того же клипа не является командой сброса в начало |
Pause() → Play() | Сохраняет позицию и продолжает назначенный клип |
Stop() | Останавливает и сбрасывает воспроизведение к началу текущей секции; обновляет её начальную позу |
Stop(keepState: true) | Останавливает со сбросом времени, сохраняя применённую позу |
Stop() → Play("rise") | Явный повторный запуск с начала, как у R в примере |
Seek(t, update: true, updateOnly: true) | Немедленно показывает позу без вызовов дорожек методов; это не «проиграть всё до t» |
PlayBackwards("rise") | Проигрывает от конца в обратном направлении |
SpeedScale и customSpeed перемножаются. SpeedScale = 0 останавливает течение времени, но IsPlaying() может оставаться true. Не делайте паузу только по результату этого метода. После Stop() текущий клип снят: не читайте CurrentAnimationPosition сразу после остановки. Сначала снова назначьте клип через AssignedAnimation = "rise" или запустите Play(). Сохранённое имя в getter AssignedAnimation само по себе не доказывает наличие текущего клипа.
Обычный режим обновляет анимацию по кадрам. Для свойств физики выберите физический callback; для собственного шага — CallbackModeProcess = Manual и один Advance(delta) в нужном месте. Не оставляйте автоматическое обновление вместе со своим дополнительным шагом. В ручном режиме сами решайте, выполнять ли шаг при паузе: явный Advance() может продвинуть приостановленный клип.
Завершение и очередь
AnimationFinished подходит для нормального конца незацикленного клипа. Остановка и перемотка не заменяют это событие; у зацикленного клипа обычного конечного события нет. Ожидание такого сигнала без учёта отмены или удаления объекта может остаться незавершённым.
Queue() ставит клип после текущего; за бесконечным циклом очередь сама не продолжится. Для состояний с условиями используйте AnimationTree, для отменяемой игровой операции храните её состояние отдельно от ожидания сигнала.
Секции, маркеры и предпросмотр
У rise в помощнике есть маркеры start при 0,2 с и end при 0,8 с. PlaySectionWithMarkers("rise", "start", "end") воспроизводит этот участок: куб движется от Y=0,4 до Y=1,6 за 0,6 с. Маркеры задают границы, а не вызывают методы; для событий нужна отдельная дорожка. Границы по времени задаёт PlaySection().
В редакторе autoplay запускает выбранный клип при входе проигрывателя в уровень. В нашем коде запуск уже явный — Play("rise"). Шаг шкалы Step помогает расставлять ключи и не задаёт частоту runtime-обновления. Onion Skinning показывает соседние позы для проверки траектории в редакторе, не добавляя игровые ключи.
RESET и диагностика
RESET задаёт исходные значения анимируемых свойств и используется редактором/смешиванием как базовая поза. Это не сохранение игры. Reset On Save относится к сохранению редактируемого уровня, а не к автоматическому восстановлению прогресса при загрузке. Для сброса в игре запустите RESET явно и обработайте нулевой шаг; обычный Stop() не означает «проиграть RESET».
Если клип не виден: проверьте полный ключ, RootEntity, существование Visual, правильное имя свойства и наличие ключей. Затем проверьте Active, SpeedScale, режим callback и конкурирующий скрипт. Если анимация работает только в предпросмотре редактора, убедитесь, что игровой код действительно вызывает Play() или настроен autoplay.