Animation I - Runtime Playback
- Daniel Bellido Chueco
- Apr 24
- 4 min read

One of the first animation milestones I tackled in my engine was building a basic runtime playback system for glTF models.
At this stage, the goal was not skinning yet. The idea was to get the engine to the point where it could import animation clips, play them back in real time, and apply them correctly to a model hierarchy. In other words: make the joints move properly before worrying about deforming the mesh itself.
What I wanted to achieve
The task was focused on setting up the foundations of animation playback:
import animation data from glTF files
store that data as native engine resources
play clips in runtime
apply local transforms to the correct nodes
recalculate global transforms properly
and add visual debugging to confirm everything was working as expected
It sounds straightforward when written like that, but in practice it meant touching several parts of the engine at once: the asset pipeline, runtime playback, scene components, and debug tooling.
Importing animations as engine resources
The first step was extending the glTF import pipeline so that each animation inside a file would be exported as its own sub-asset.
To support that, I introduced a new resource type:
AnimationAsset
This asset stores:
the animation name
total duration
animation channels per node
keyframes for position, rotation, and scale
Each clip is saved into the engine’s Library just like any other asset, with a deterministic UID. That means animations are no longer just something embedded inside a glTF file — they become proper engine resources that can be loaded and reused independently.
Building the runtime controller
Once the data could be imported, the next step was playback.
For that I implemented AnimationController, a runtime class responsible for managing:
current playback time
loop behaviour
play / pause / stop state
transform sampling for a given node at a given time
For interpolation, I used a binary search approach with upper_bound, then interpolated between the surrounding keyframes. Position and scale use linear interpolation, while rotation uses quaternion interpolation.
This gave me the basic functionality I needed to evaluate an animation clip continuously over time.
Applying animation to the hierarchy
After that came the actual scene integration.
I created an AnimationComponent, intended to live on the root object of the imported model. This component is responsible for:
loading an AnimationAsset
updating the AnimationController
recursively traversing the model hierarchy
matching animation channels by GameObject name
applying local position / rotation / scale to each animated node
forcing world transform updates afterwards
This was a key part of the task, because it connected imported animation data to the actual runtime scene graph.
At that point, the engine was able to animate the hierarchy of nodes correctly, even though the rendered mesh itself was still rigid.
Editor integration
I also integrated the component into the editor workflow.
That meant being able to:
add an AnimationComponent from the inspector
assign an animation resource to it
play the clip from within the editor/runtime
save and load the component as part of the scene
This made the system much easier to test properly, because it could be used in the normal scene workflow rather than only through hardcoded runtime experiments.
Debugging the hierarchy visually
One of the most useful parts of this task was the debug draw.
To make sure the hierarchy was animating correctly, I added visualisation for:
a line between each node and its parent
a local axis triad on each node
That turned out to be incredibly helpful. Even before skinning was implemented, I could already verify that the animation data was being applied correctly just by looking at the moving hierarchy in the scene.
It also made it much easier to spot problems such as incorrect node matching, strange pivots, or unexpected hierarchy layouts coming from imported models.
Final result
By the end of this phase, the engine could:
import glTF animations
store each animation as an engine resource
play clips in runtime
apply local transforms to a node hierarchy
update world transforms correctly
and visually debug the animated hierarchy
In short, I now had a working node-based animation playback system.
What was still missing
At this point, the mesh itself still remained in its bind pose. That was expected.
This first phase was only about getting the animation playback layer working properly. The next step was the more advanced part: skinning.
That meant moving on to:
joints and weights
inverse bind matrices
matrix palettes
and eventually deforming the mesh itself based on the animated skeleton
Closing thoughts
This was one of those tasks that felt very foundational. On the surface it was “just playback”, but in practice it laid down a lot of the engine-side infrastructure needed for a proper animation pipeline:
asset import
native animation resources
runtime sampling
scene integration
editor support
debug visualisation
Getting this phase working properly made the next stage - skinning -
much more manageable, because by then I already knew the hierarchy and transforms were behaving correctly.


Comments