reckfellENGINE

Составная 2D-анимация: части и шарниры

Рабочая иерархия руки, поворот вокруг сустава и вынесенный аксессуар через RemoteTransform2D.

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

Когда использовать

Составная анимация двигает готовые части персонажа: плечо, предплечье, кисть, голову или оружие. Часть поворачивается как жёсткое изображение. Чтобы изгибать один рисунок по вершинам, нужен Polygon2D со скелетом и весами.

На кадрах ниже сверху показаны жёсткие части, снизу — один деформируемый полигон. Это кадры работающего примера движка.

Исходная поза: отдельные части сверху, цельный полосатый полигон снизу

Согнутая поза: части поворачиваются, полосатый полигон деформируется

Запустить руку из отдельных частей

Создайте уровень с корнем Entity2D, задайте корню Position (160,144). Прикрепите CutoutArmDemo.cs, добавьте Input Map действие animation_pause на Space, соберите C#-проект и запустите F6. Рука сгибается за секунду и возвращается ещё за секунду; Space приостанавливает или продолжает клип. Камера и внешние изображения этому примеру не нужны.

CutoutArmDemo.cs
C#
using Reckfell;

public partial class CutoutArmDemo : Entity2D
{
    public Entity2D Shoulder { get; private set; } = null!;
    public Entity2D Elbow { get; private set; } = null!;
    public Entity2D Hand { get; private set; } = null!;
    public Entity2D Accessory { get; private set; } = null!;
    public AnimationPlayer Player { get; private set; } = null!;

    private static Polygon2D Part(float width, float height, Color color) => new()
    {
        Polygon = [new(0,-height/2),new(width,-height/2),new(width,height/2),new(0,height/2)],
        Color = color
    };

    private static void Keys(Animation clip, string path, float middle)
    {
        int track = clip.AddTrack(Animation.TrackType.Value);
        clip.TrackSetPath(track,new EntityPath(path));
        clip.TrackInsertKey(track,0,0f);
        clip.TrackInsertKey(track,1,middle);
        clip.TrackInsertKey(track,2,0f);
    }

    public override void _Ready()
    {
        Shoulder = new Entity2D { Name="Shoulder" };
        Elbow = new Entity2D { Name="Elbow", Position=new Vector2(70,0) };
        Hand = new Entity2D { Name="Hand", Position=new Vector2(60,0) };
        Shoulder.AddChild(Part(70,24,new Color(.15f,.35f,1)));
        Elbow.AddChild(Part(60,18,new Color(.45f,.6f,1)));
        Hand.AddChild(Part(18,24,Colors.White));
        Elbow.AddChild(Hand); Shoulder.AddChild(Elbow); AddChild(Shoulder);
        Accessory = new Entity2D { Name="Accessory", ZIndex=2 };
        Accessory.AddChild(Part(30,6,Colors.White)); AddChild(Accessory);
        var remote = new RemoteTransform2D
        {
            Name="Grip", UpdatePosition=true, UpdateRotation=true,
            UpdateScale=false, UseGlobalCoordinates=true
        };
        Hand.AddChild(remote);
        remote.RemotePath = remote.GetPathTo(Accessory);
        Player = new AnimationPlayer { Name="AnimationPlayer", RootEntity=new EntityPath("..") };
        AddChild(Player);
        var bend = new Animation { Length=2, LoopMode=Animation.LoopModeEnum.Linear };
        Keys(bend,"Shoulder:rotation",-.4f);
        Keys(bend,"Shoulder/Elbow:rotation",1f);
        var library = new AnimationLibrary(); library.AddAnimation("bend",bend);
        Player.AddAnimationLibrary("",library); Player.Play("bend");
    }

    public void SetPose(float shoulder, float elbow)
    {
        Player.Stop(keepState:true);
        Shoulder.Rotation=shoulder; Elbow.Rotation=elbow;
    }

    public void ResetPose() => SetPose(0,0);

    public override void _UnhandledInput(InputEvent @event)
    {
        if (!@event.IsActionPressed("animation_pause")) return;
        if (Player.IsPlaying()) Player.Pause(); else Player.Play("bend");
        GetViewport().SetInputAsHandled();
    }
}

Иерархия — Shoulder → Elbow → Hand. Начало координат каждой Entity2D находится в шарнире, а рисунок вытянут от него вдоль локальной X. Elbow расположен на конце плеча (70,0), Hand — на конце предплечья (60,0). Поэтому поворот плеча переносит всю цепочку, а поворот предплечья не смещает его собственный шарнир.

Заменить полигоны спрайтами

Для каждой части оставьте Entity2D с началом в суставе, а под неё добавьте Sprite2D. Сместите дочерний спрайт или его Offset, чтобы нарисованный сустав совпал с (0,0) родителя. Не двигайте шарнир ради центрирования текстуры: это изменит цепочку и все клипы. Проверяйте стыки при крайних углах; небольшое перекрытие рисунков прячет разрыв.

Анимируйте rotation шарниров, оставляя их локальные position постоянными, если длина сегмента не должна меняться. Для осмысленного растяжения добавляйте отдельную дорожку явно. SetPose() в примере останавливает клип перед ручной записью углов; иначе AnimationPlayer перезапишет их следующим кадром.

Аксессуар вне иерархии кисти

Accessory находится рядом с Shoulder, но следует за Grip через RemoteTransform2D. RemotePath задаётся относительно самого RemoteTransform2D. Включены глобальные позиция и вращение; UpdateScale = false сохраняет масштаб аксессуара. Такой вариант полезен, когда предмет должен оставаться в другой ветви уровня.

У обычного дочернего предмета проще и дешевле оставить родителем Hand. RemoteTransform2D не создаёт физическое соединение, не копирует видимость и не решает сортировку. Не направляйте его на предка или по циклу взаимных зависимостей.

Слои и исходная поза

Для частей используйте ZIndex и осознанную иерархию CanvasItem; для размещения за родителем есть ShowBehindParent. Порядок рисования не меняет направление наследования трансформации. Отрицательный масштаб меняет ориентацию и может менять знак угла; для зеркалирования всей руки применяйте единое правило на её корне.

ResetPose() возвращает только заданные углы в 0. Это исходная поза данного примера, не универсальное восстановление всех свойств персонажа. В собственном риге запишите и сохраняйте нужные локальные исходные трансформации или отдельный RESET-клип.

Редакторские цепочки IK помогают выставлять позы и записывать ключи. Они не являются автоматически работающим во время игры решателем. Для слежения за целью в игре используйте runtime-пример IK.

Диагностика

СимптомПроверка
Часть вращается вокруг центра изображенияПеренесите геометрию относительно шарнира, а не шарнир относительно родителя
Сегменты расходятсяЛокальные позиции дочерних суставов и случайные ключи Position
Ручной угол сразу пропадаетАктивная анимация или IK пишет то же свойство
Аксессуар не следует за рукойRemotePath, существование цели и флаги UpdatePosition/UpdateRotation
Аксессуар слишком большойМасштаб родителя и UpdateScale; проверьте локальную и глобальную трансформации отдельно
Часть оказывается перед теломZIndex, ShowBehindParent и порядок CanvasItem
Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

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

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

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