reckfellENGINE

Cutout 2D animation: parts and pivots

A working arm hierarchy, joint pivots and a detached accessory driven by RemoteTransform2D.

On this page

When to use it

Cutout animation moves finished character pieces: upper arm, forearm, hand, head or weapon. Each piece turns as a rigid image. To bend a single drawing by its vertices, use a Polygon2D with skeleton weights.

The upper arm in these frames uses separate rigid parts; the striped lower arm is one deforming polygon. These are frames from the running engine example.

Rest pose: separate parts above and one striped polygon below

Bent pose: separate parts rotate while the striped polygon deforms

Run the multipart arm

Create an Entity2D level root at Position (160,144). Attach CutoutArmDemo.cs, add Input Map action animation_pause on Space, build and run with F6. The arm bends for one second and returns over the next; Space pauses or resumes. This example needs neither a camera nor external images.

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();
    }
}

The hierarchy is Shoulder → Elbow → Hand. Each Entity2D origin is a joint, with its drawing extending along local X. Elbow sits at the upper-arm end (70,0); Hand sits at the forearm end (60,0). Rotating the shoulder carries the whole chain; rotating the forearm leaves its own joint in place.

Replace polygons with sprites

Keep an Entity2D origin at each joint and add a child Sprite2D. Shift the child sprite or its Offset until the drawn joint matches its parent's (0,0). Do not move the joint just to center the texture; that changes the chain and every clip. Check extreme angles, using slight artwork overlap to hide gaps.

Animate joint rotation, keeping local position fixed unless the segment length should change. Add stretching tracks explicitly when intended. The example's SetPose() stops the clip before manually writing angles, preventing AnimationPlayer from replacing them next frame.

An accessory outside the hand hierarchy

Accessory is a sibling of Shoulder but follows Grip through RemoteTransform2D. RemotePath is relative to that RemoteTransform2D. Global position and rotation copying are enabled; UpdateScale = false preserves the accessory scale. This suits an item that must stay in another level branch.

A normal hand-held child is simpler under Hand. RemoteTransform2D does not create a physics joint, copy visibility or solve sorting. Do not target an ancestor or form a cycle of remote dependencies.

Layers and the initial pose

Use ZIndex and a deliberate CanvasItem hierarchy; ShowBehindParent draws behind a parent. Draw order does not change transform inheritance. Negative scale changes orientation and can change an angle's sign; apply a consistent rule at the arm root when mirroring it.

ResetPose() only restores these example angles to 0. It is not a universal reset of every character property. Store the required local rest transforms or a separate RESET clip in your own rig.

Editor IK chains help pose and keyframe a character. They do not automatically become a gameplay-time solver. Use the runtime IK example to follow a target during play.

Diagnostics

SymptomCheck
A part rotates around the image centerMove the drawing relative to its joint, not the joint relative to its parent
Segments separateChild joint local positions and unintended Position keys
A manual angle disappears immediatelyAnimation or IK is writing the same property
Accessory does not followRemotePath, target existence and UpdatePosition/UpdateRotation
Accessory is too largeParent scale and UpdateScale; inspect local and global transforms separately
A part draws in front of the bodyZIndex, ShowBehindParent and CanvasItem order
Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.