Sharing Animations Between Multiple Characters With the Same Skeleton

Is it possible to load animations from one file and use them in another file?
This might be a silly question.

The number of animations is the same for different characters, and they all use the same skeleton.
Is it possible to store the animations in a single file and have all characters receive and use the animations from that file?

Not a silly question at all. :slightly_smiling_face:

Yes, it is possible — but mostly manually.

Since all characters use the same skeleton, you can export the animation keyframes from Blender (or another DCC tool) and then load/apply them to another character that has the same bone hierarchy.
Internally, each bone simply reads its position / rotation / scale keys and applies them to itself.

Demo:
Castle Game Engine\examples\animations\animate_bones_by_code

General workflow

1- Write a script to export animation keys from Blender. (Parent bones and child bones must NOT be exported the same way)

2-Load the keys from a file into arrays (or another data structure)

Each character’s bones must have either:

1-individual behaviors that apply the keys, or

2-one shared behavior that drives all bones

A procedure that:

pushes the keys over time to play the animation

handles transitions between old and new keys (blending) for smooth results :slightly_smiling_face::slightly_smiling_face:

At the moment, CGE already has low memory usage even when loading many animations, so in practice you won’t gain much in terms of memory or performance by doing this.

Where this approach can be useful

Playing partial animations (upper body only / lower body only, etc…)

Reducing the total number of animations

Reducing animation work inside Blender

In the future, I plan to complete a practical example of implementing this method, which may be useful for some special cases.

2 Likes

Thank you for the explanation.

I think managing animations for a large number of characters using this approach—especially inside the Update loop—would be quite difficult. In the provided example, only the neck is animated, but in a real-world animation many bones may need to be animated simultaneously.

Additionally, the number of animations is not small (around 30 animations). This approach may solve the problem for small-scale projects, but for a large number of characters I don’t think it would scale well or remain efficient.

In my opinion, having a property that can receive and manage animations in a serialized form (such as JSON or plain text) would be a much better solution.

Even better would be having a base animation transform that can execute and manage the selected animation directly on the scene.

1 Like

Isn’t there a solution to display the animation this way?

i put demo here in my last post.

1 Like

Hello,

I’ve been busy with some work for a while.
Thank you for taking the time to create and share this demo.

I had some free time today and replaced one of your animations with one of my own.
However, the timing in the animation is not respected, and everything plays quickly and back-to-back.

About the animation timing: in the demo I’m playing the animations at a fixed 30 FPS,
so if your action was authored at a different framerate (60 / 24 / 10 etc.),
it can look like everything is playing too fast.

Also, before exporting, it’s a good idea to bake the action in Blender so it generates keyframes for all frames before export.
This helps preserve the correct timing and avoids issues when the animation has sparse keyframes.

One more thing: if you’re using CastleTransformReference to point to the scene,
try disabling it . In some cases it can make the animation appear to play faster than expected.

If it still doesn’t work, upload a small test case and I’ll take a look.

1 Like

This script helps you bake an Action in Blender.

Just select any Action in the Dope Sheet, then run the script, and you will get a new baked Action
with a name like: **000_**ActionName

After that, select this new baked Action, select any bone, run the key exporter script (CopySelectedBone.py), and it will export all animation keys to a file on disk.

BakeAction.zip (901 Bytes)

1 Like

In my opinion, nothing has changed. Even the speed of the animations included in the example feels very unnatural.

1 Like

For me the animation looks the same in Blender and in the app.

try uploading your model with any animation, and ill take a look.

Emm yes, I think I exported keys with 24 fps but I play it at 30.

1 Like

Sorry for a late reply! I just want to add another possibility:

  • The animation in CGE is expressed using a set of X3D nodes (inside Scene.RootNode) that are connected using X3D routes. How these nodes are connected to perform an animation is described in Interpolation component | Castle Game Engine .

  • This means that you can write a Pascal code that copies the nodes (TimeSensor, interpolators) from one scene, and add them to another scene. You will need to connect them to the bones in new scene (using e.g. NewSeene.RootNode.AddRoute(...)) and this way the animation from old file can be transferred into a new one.

TODO: I don’t have a ready example doing this, for now (and I agree it would be cool to make :slight_smile: ).

3 Likes

I’m very glad that this caught your attention.

I started reviewing the component’s source code, but then I got busy with some unrelated tasks and couldn’t continue the investigation in depth.

I wanted to add a property to TCastleScene that references another TCastleScene, and if this property is set, the AutoAnimation values would be populated from it and the animation of that TCastleScene would be executed.

Thank you for sharing the link. I hope it helps achieve this goal.

I wrote these two functions.

With the help of these two functions, at runtime I can copy the animation from the first TCastleScene to the second TCastleScene and execute it.

procedure CopyAnimationOnly(Source, Target: TCastleScene);
var
I: Integer;
N: TX3DNode;
NewNode: TAbstractChildNode;
begin
for I := 0 to Source.RootNode.FdChildren.Count - 1 do
begin
N := Source.RootNode.FdChildren[I];

if (N is TTimeSensorNode) or
   (N is TAbstractInterpolatorNode) then
begin
  NewNode := N.DeepCopy as TAbstractChildNode;
  //NewNode.RoutesCount:=0;
  Target.RootNode.AddChildren([NewNode], False);
end;

end;
end;

and

procedure CopyRoutes(Source, Target: TCastleScene);
var
I: Integer;
R: TX3DRoute;
NewSourceNode, NewDestNode: TX3DNode;
NewSourceEvent, NewDestEvent: TX3DEvent;
begin
for I := 0 to Source.RootNode.RoutesCount - 1 do
begin
R := Source.RootNode.Routes[I];

NewSourceNode :=
  Target.RootNode.TryFindNodeByName(
    TX3DNode,
    R.SourceNode.X3DName,
    false
  );

NewDestNode :=
  Target.RootNode.TryFindNodeByName(
    TX3DNode,
    R.DestinationNode.X3DName,
    false
  );

if (NewSourceNode = nil) or (NewDestNode = nil) then
  Continue;

NewSourceEvent :=
  NewSourceNode.AnyEvent(R.SourceEvent.X3DName, false);

NewDestEvent :=
  NewDestNode.AnyEvent(R.DestinationEvent.X3DName, false);

if (NewSourceEvent = nil) or (NewDestEvent = nil) then
  Continue;

Target.RootNode.AddRoute(NewSourceEvent, NewDestEvent);

end;
end;

How to use:
procedure TViewMain.onbtnCreateClick(ASender: TObject);
begin
CopyAnimationOnly(Source,Target);
CopyRoutes(Source,Target);
end;

procedure TViewMain.onbtnWalkClick(ASender: TObject);
begin
if TCastleButton(ASender).Tag=1 then
Target.PlayAnimation(‘Walking_A’, true);
if TCastleButton(ASender).Tag=2 then
Target.PlayAnimation(‘Walking_B’, true);
if TCastleButton(ASender).Tag=3 then
Target.PlayAnimation(‘Walking_C’, true);
end;

1 Like

Result:

1 Like

This method takes a lot of time when the number of source animations is large, because it spends time copying.

The command CopyRoutes(Source, Target); takes about 10 seconds for 30 animations, while the source file size is about 1 MB.

I think I need to find another solution.

I think your computer might be a bit weak :slight_smile: maybe the CPU is not as strong compared to the GPU.
That’s why the animation looks very fast.
As for copying all animations from one scene to another i dont think it’s very useful. You can do that more easily in Blender and then just export/load model.
However, transferring a specific animation from one scene to another could be useful and also deleting a specific animation might be helpful. But copying all animations at once doesn’t seem useful at all.
Still, as a first step, it’s fine it’s a way to understand and start working with X3D :slight_smile:

1 Like

Yes, my laptop is quite old. Although on my Samsung phone the animations are copied even more slowly.

At first, I tried to do this in Blender, but I couldn’t manage to make it work, so I ended up doing it directly in CGE.

The solution you suggested using an array of animations is also good, but adjusting the playback speed or frame rate was a bit difficult for me. It would be much nicer if it were available as a component or a Behavior that could simply be added to an object.

1 Like

That’s quite long. I think your solution looks reasonable – but it can be optimized, either on your side or engine side. Can you submit a testcase to reproduce? That is, a minimal application (code + data) showing the slowness. I would be happy to take a look and measure + suggest.

If really the loop you showed in post ( Sharing Animations Between Multiple Characters With the Same Skeleton - #14 by Hamid ) takes 10 seconds, then we can figure out why:

  • how many routes you have?
  • Is the searching (2 calls to Target.RootNode.TryFindNodeByName) so slow? Maybe we could optimize it by a dictionary with hashtable (just TDisctionary<String,TX3DNode>), built once and reused?
  • Is the addition Target.RootNode.AddRoute so slow? We could optimize it by adding a AddRoutes(RoutesList) API to engine.
1 Like

Hello @michalis ,

I prepared an example.
The more animations there are, the slower the copying becomes.

I did this with two sources, but if the number increases, it becomes even slower.

CopyAnimation.zip (8.0 MB)