reckfellENGINE

AnimationPlayer: клипы и управление

Полный пример, ключи, библиотеки, пауза, повторный запуск и завершение.

На этой странице

Запустить пример

  1. Создайте уровень с корнем Entity3D. Добавьте в C#-проект AnimationClips.cs и PlayerAnimationDemo.cs, прикрепите второй к корню. Используйте отдельный уровень для каждого примера этого раздела.
  2. В Input Map создайте animation_restart на R, animation_pause на Space, animation_fall на F. Соберите проект и запустите текущий уровень через F6.
  3. Синий куб поднимается на 2 единицы за секунду и останавливается. R начинает подъём заново; Space ставит воспроизведение на паузу или продолжает его. F запускает опускание из верхней позы.

Скрипт создаёт камеру, свет, визуальную Entity и AnimationPlayer. Это демонстрация свойств, не физическая дверь: у куба нет тела столкновения. Общий помощник нужен также примерам дорожек и AnimationTree.

AnimationClips.cs
C#
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
C#
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.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.