# Sharing Animations Between Multiple Characters With the Same Skeleton

**URL:** <https://forum.castle-engine.io/t/sharing-animations-between-multiple-characters-with-the-same-skeleton/2094>\
**Category:** 3D\
**Created:** [February 1, 2026, 1:32pm UTC](https://forum.castle-engine.io/t/sharing-animations-between-multiple-characters-with-the-same-skeleton/2094 "2026-02-01T13:32:21Z")\
**Posts on this page:** 3\
**Page:** 2

<div class="post-metadata">

**Author:** ![michalis](https://forum.castle-engine.io/user_avatar/forum.castle-engine.io/michalis/32/3_2.png) [@michalis](https://forum.castle-engine.io/u/michalis)\
**Post date:** [July 18, 2026, 11:15pm UTC](https://forum.castle-engine.io/t/sharing-animations-between-multiple-characters-with-the-same-skeleton/2094/21 "2026-07-18T23:15:32Z")

</div>

![image](https://forum.castle-engine.io/uploads/default/original/2X/e/eccf4ff16b5784e0058898b31230f8001ca6e819.png)

Hello,

I know it’s been 4 months since the last post in this thread – well, my TODO list is long, but I never forget:)

@Hamid I hope this thread, and in particular question above “why is route copy so slow”, is still relevant. I managed to reduce the time of your animation copying 100x, to nearly zero (from 4.92 sec → 0.05 sec)!

For context, I used latest Castle Game Engine with FPC 3.2.2 and tested on Linux/x86\_64. (I only needed to add `{$mode delphi}` at the top of `GameViewMain` to compile your testcase.) The times you will experience (I think you use Delphi on Windows) may differ somewhat, but the “order of magnitude improvement” should be the same.

### 0. Initial measurement

I used our [Timer](https://castle-engine.io/apidoc/html/CastleTimeUtils.html#Timer) to easily measure time,

```delphi
procedure TViewMain.onbtnCreateClick(ASender: TObject);
var
  T: TTimerResult;
begin
  T := Timer;
  CopyAnimationOnly(Source1,Target);
  WritelnLog('CopyAnimationOnly(Source1,Target): %f s', [T.ElapsedTime]);

  T := Timer;
  CopyRoutes(Source1,Target);
  WritelnLog('CopyRoutes(Source1,Target): %f s', [T.ElapsedTime]);

  T := Timer;
  CopyAnimationOnly(Source2,Target);
  WritelnLog('CopyAnimationOnly(Source2,Target): %f s', [T.ElapsedTime]);

  T := Timer;
  CopyRoutes(Source2,Target);
  WritelnLog('CopyRoutes(Source2,Target): %f s', [T.ElapsedTime]);
end;

```

Before any modifications, the sequence in `onbtnCreateClick` is indeed slow:

```auto
CopyAnimationOnly(Source1,Target): 0.32 s
CopyRoutes(Source1,Target): 0.15 s
CopyAnimationOnly(Source2,Target): 3.67 s
CopyRoutes(Source2,Target): 0.78 s

```

### 1. First, let’s optimize `CopyAnimationOnly` calls:

- The `CopyAnimationOnly` is expensive but it doesn’t have to be. It calls multiple times `Target.RootNode.AddChildren` which does a heavy processing at the end.

- Solution: call `Target.BeginChangesSchedule;` at the beginning of `onbtnCreateClick` and then call `Target.EndChangesSchedule` at the end. This makes all `Target.RootNode.AddChildren` in-between happen much faster, they will delay the “big expensive processing” to `Target.EndChangesSchedule`, so it will happen only once.

- To have most reliable code, call `Target.EndChangesSchedule` in a `finally` section, to make sure it is performed even in case of exception. The `Begin/EndChangesSchedule` calls must always match, or the regular scene updates may not have the necessary effect.

This gives great speedup to the two `CopyAnimationOnly` calls (and doesn’t change the two `CopyRoutes` calls, as expected):

```auto
CopyAnimationOnly(Source1,Target): 0.01 s
CopyRoutes(Source1,Target): 0.14 s
CopyAnimationOnly(Source2,Target): 0.03 s
CopyRoutes(Source2,Target): 0.75 s

```

## 2. Let’s optimize `CopyRoute` now:

In `CopyRoute`, the expensive part is `Target.RootNode.TryFindNodeByName`, called 2x for 6300+ routes. This can be optimized a lot: our `Target.RootNode.TryFindNodeByName` naively assumes that everything in the nodes graph potentially changes every time. But that’s not true, we can actually build a trivial fast map name-\>node at the beginning `CopyRoute` and then just use this map thousands of times.

I added a utility to the engine to do this (you will need to update the engine to the latest version to have this). In effect, you do once

```auto
NodesMap := Target.RootNode.CreateNodeNamesMap(false);

```

and then query the dictionary multiple times like

```auto
if not NodesMap.TryGetValue(R.SourceNode.X3DName, NewSourceNode) then
  Continue;

```

There’s no need to do now `TryFindNodeByName`.

Now I have almost zero time:)

```auto
CopyAnimationOnly(Source1,Target): 0.01 s
CopyRoutes(Source1,Target): 0.00 s
CopyAnimationOnly(Source2,Target): 0.03 s
CopyRoutes(Source2,Target): 0.01 s

```

## Testing in “release” and final notes

1. As one tiny additional thing, I tested building in “release” mode. This changes 2nd `CopyAnimationOnly(Source2,Target)` time from `0.03 s` to `0.02 s` (with more precision, the new number is `0.017347 s`). More testing, without rounding times, would be good to really compare the gain.

The complete resulting file is here: [Optimized copying of animation nodes + routes, see https://forum.castle-engine.io/t/sharing-animations-between-multiple-characters-with-the-same-skeleton/2094/20 · GitHub](https://gist.github.com/michaliskambi/f776920939b9c2280d72cb1e29ea4a2f) . In summary:

- I modified `CopyRoutes` to use `Target.RootNode.CreateNodeNamesMap` (once) and then, in a loop, `NodesMap.TryGetValue` (instead of `Target.RootNode.TryFindNodeByName`).
- I modified `onbtnCreateClick` to do `Target.BeginChangesSchedule` + `Target.EndChangesSchedule` and do time measurements using `Timer`.
- For extra precision of times display, use `%.6f` instead of `%f`.

---

<div class="post-metadata">

**Author:** ![Hamid](https://forum.castle-engine.io/user_avatar/forum.castle-engine.io/hamid/32/2741_2.png) [@Hamid](https://forum.castle-engine.io/u/Hamid)\
**Post date:** [July 23, 2026, 9:35am UTC](https://forum.castle-engine.io/t/sharing-animations-between-multiple-characters-with-the-same-skeleton/2094/22 "2026-07-23T09:35:37Z")

</div>

Hi,

Thank you so much for not forgetting about this issue and for taking the time to investigate it after all these months.

I really appreciate the detailed analysis and the optimizations you made. A **100x improvement** is impressive! I’ll definitely go through your changes and test them.

To be honest, I’ve become a bit discouraged with game development (and, lately, with building anything at all), so I haven’t touched this project for quite some time. Still, your work motivates me to take another look at it.

In my original tests, the biggest performance issue was with **FPC** , especially on **Android**. At the moment I don’t have access to Delphi to compare the results there, but I can test it on Android and see how much these optimizations help.

Thanks again for your effort and for coming back to this thread. I really appreciate it!

---

<div class="post-metadata">

**Author:** ![Hamid](https://forum.castle-engine.io/user_avatar/forum.castle-engine.io/hamid/32/2741_2.png) [@Hamid](https://forum.castle-engine.io/u/Hamid)\
**Post date:** [August 1, 2026, 4:43pm UTC](https://forum.castle-engine.io/t/sharing-animations-between-multiple-characters-with-the-same-skeleton/2094/23 "2026-08-01T16:43:58Z")

</div>

I tested it today, and it’s fantastic! Thank you so much for your work.

[Previous page](https://forum.castle-engine.io/t/sharing-animations-between-multiple-characters-with-the-same-skeleton/2094.md?page=1)
