AnimationPlayer: clips and playback
A complete example, keys, libraries, pause, restart and completion.
Run the example
- Create a level with an
Entity3Droot. AddAnimationClips.csandPlayerAnimationDemo.csto the C# project; attach the latter to the root. Use a separate level for each example in this section. - Add Input Map actions
animation_restarton R,animation_pauseon Space andanimation_fallon F. Build and run the current level with F6. - A blue cube rises 2 units in one second and stops. R restarts the rise; Space pauses or resumes playback. F plays the downward clip from its upper pose.
Code creates the camera, light, visual entity and AnimationPlayer. This demonstrates properties, not a physics door; the cube has no collision body. The shared helper is also required by the tracks and AnimationTree examples.
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();
}
}Resolve the track target
The player's RootEntity = ".." points to the level root. Visual:position therefore addresses the child entity Visual and its position property. Before the colon is an entity path; after it is a property or resource-property chain.
String paths use the engine property position, not the C# property name Position. Play() schedules processing; call Advance(0) after playback starts when the value must be available immediately.
AnimationPlayer inherits Entity and does not relay 3D transforms like Entity3D. Visual and AnimationPlayer are siblings here. Use Entity3D parents for spatial hierarchies.
The equivalent editor workflow
Create AnimationPlayer below the root containing Visual. Open the bottom Animation panel and create a one-second clip in the global library. Insert a Visual.position key at time 0 with (0,0,0), then at 1 second with (0,2,0). Scrub the timeline to check it. Choose discrete updates for booleans and continuous updates for movement.
Do not combine manually authored clips with PlayerAnimationDemo in one hierarchy; the script creates its own AnimationPlayer and targets. For imported animations, use the imported library or an editable copy; do not expect manual changes to survive reimporting over them.
Library names
The empty library name "" is global; play its clip as "rise". A library named "doors" uses the full key "doors/rise". A resource filename and a clip name are different things. Check HasAnimation() before conditional playback.
Time controls
| Call | Result |
|---|---|
Play("rise") | Starts the clip; calling it again for the same clip is not a restart command |
Pause() → Play() | Retains the position and resumes the assigned clip |
Stop() | Stops and resets playback to the current section's start, applying its initial pose |
Stop(keepState: true) | Stops and resets time while retaining the applied pose |
Stop() → Play("rise") | An explicit restart, as with R in the example |
Seek(t, update: true, updateOnly: true) | Immediately shows a pose without method-track calls; it does not play everything up to t |
PlayBackwards("rise") | Plays backward from the end |
SpeedScale multiplies customSpeed. SpeedScale = 0 stops time progression while IsPlaying() can remain true. Do not infer a paused operation from that method alone. Stop() clears the current clip; do not immediately read CurrentAnimationPosition. First assign the clip again through AssignedAnimation = "rise" or start Play(). The retained name from the AssignedAnimation getter alone does not prove that a current clip exists.
The default callback updates by frames. Choose physics processing for physics-related properties; for your own timing, choose CallbackModeProcess = Manual and call Advance(delta) once in the appropriate place. Do not keep automatic processing alongside an additional manual step. In manual mode, decide whether to step while paused; an explicit Advance() can advance a paused clip.
Completion and queues
AnimationFinished represents a nonlooping clip's normal end. Stopping and seeking do not replace that event; looping clips do not have a normal final event. Awaiting the signal without accounting for cancellation or object deletion can leave an operation unfinished.
Queue() places a clip after the current one; a perpetual loop will not reach it on its own. Use AnimationTree for conditional states, and track cancelable gameplay operations separately from waiting for a signal.
Sections, markers and preview
The helper's rise clip contains start at 0.2 seconds and end at 0.8. PlaySectionWithMarkers("rise", "start", "end") plays that section: Y=0.4 to Y=1.6 over 0.6 seconds. Markers define boundaries, not method calls; use a separate track for events. PlaySection() accepts time boundaries directly.
Editor autoplay starts the chosen clip when the player enters the level. Our code already starts it explicitly with Play("rise"). Timeline Step helps place keys; it does not set runtime update frequency. Onion Skinning displays neighboring editor poses to check a trajectory without adding gameplay keys.
RESET and diagnostics
RESET defines initial animated-property values and supplies a reference pose for editor operations and blending. It is not a game save. Reset On Save concerns saving the edited level, not restoring progress on load. To reset gameplay explicitly, play RESET and process a zero step; ordinary Stop() does not mean play RESET.
If nothing changes, check the full animation key, RootEntity, Visual, the property name and keys. Then check Active, SpeedScale, callback mode and competing scripts. If only the editor preview works, confirm that runtime code calls Play() or autoplay is configured.