Setting AnimationTree Parameters Quickly

Godot Version

4.3

Question

I have animated (3D) turrets in my game. I’m using a set of animations (rotation, elevation, recoil) supplied by my artist. I’ve got an AnimationTree that adds the three animations, with time seek and zeroed time scale nodes for control.

I calculate the azimuth and elevation for the turret barrel, and use that information to set the time seek parameters to pick the appropriate animation frames to make the turret aim where I want:

atree.set("parameters/azimuth/seek_request", azimuth)
atree.set("parameters/elevation/seek_request", elevation)

This all works, but it does mean every call to _process() I’m calling this for each active turret, and I presume the call is having to parse the path strings each time to figure out what to do.

Is there any way to optimize this? Some way I could directly set those parameters without having to parse a pile of strings every frame? My frame rate is not where I’d like it to be (~28fps on my dev machine, it’s CPU bound, all the time is in “process time”), so I’m looking for low hanging fruit to pick.

I am optimizing other things; I have hundreds of these turrets, but I early-out of _process() for them if the player is out of visual range (using squared distance to avoid all the square roots…).

Maybe using StringName may be faster although I think they are converted automatically in this case.

You could also only change the parameter if the value changes. Something like:

var azimuth:float = 0.0:
    set(value):
        if not is_equal_approx(azimuth, value):
            azimuth = value
            atree.set(&"parameters/azimuth/seek_request", azimuth)

I doubt the performance issues you are having are there. You may want to use VisibleOnScreenEnabler3D instead of doing the calculations yourself.

Also, check the Performance section in the documentation. It may have more information.

Thanks! I’ll try using StringName; I imagine it can’t hurt.

Unfortunately, the turrets are tracking something that’s moving fairly quickly, so the values are changing constantly; I’ll test it to make sure, but I strongly suspect checking before setting will never fail to set, so I’ll just wind up eating the additional cost of the test.

The distance calculation I’m doing is essentially just:

const AGGRO_RANGE = 1000.0
const AGGRO_RANGE_SQ = AGGRO_RANGE * AGGRO_RANGE

func _process():
    if position.distance_squared_to(target) < AGGRO_RANGE_SQ):
        [do stuff]

So it shouldn’t be particularly expensive.

I have looked at the performance section of the docs, but it’s kind of thin on the ground for my needs. I’m a fairly experienced game dev (I cut my teeth professionally on Saturn and PS1…), so I’m familiar with game optimization and I know how to use a profiler.

What I have from the profiler is that “Process Time” is well over 35ms, but the detailed breakdown below only adds to around 4.5ms. Since writing this post, I think I’ve determined that the performance problem is actually the number of animated models I have; if I disable animation (that is, if I don’t create the AnimationTree for each turret), the process time seems to drop significantly. I’m pretty sure it’s all the skeletal animation happening in-engine rather than anything I’ve got going on in my scripts.

I have a large map populated with these turrets, and I’m starting to wonder if I need to make a pool of them instead, keep track of where they are, and populate visible map locations out of the pool. I’m going to try messing with visibility range first, and see if telling Godot to distance cull things stops it from updating their animations. I’d assumed Godot did distance culling automatically, but it looks like it’s something I have to enable.

As a followup, I’ve started setting visible on the model and active on the animation tree based on distance; if the turret is out of aggro range, I set them both to false.

That’s got “Process Time” down from ~38ms to ~22ms. Still not great; I’d like a solid 60fps, which means I need to be reliably under 16.67ms. It’s a good start, though.

The main thing that’s giving me difficulty is, the profiler has a bunch of time that’s unaccounted for in “Process Time”. Even now, it’s ~22ms. There.s 4.5ms under “Script Functions”, ~0.2ms under “Audio Thread”, and pretty much nothing else on the board. I’m not using Godot’s physics; “Physics 2D” and “Physics 3D” both show 0ms, and “Physics Time” is 0.02ms. The visual profiler shows the GPU is consistently bringing in frames under 16.67ms.

Presumably the other ~17.5ms is stuff that’s happening inside Godot, and whereas it seems like I can influence that (for example, by frobbing animation tree activity), I don’t see an obvious way to get Godot to tell me where it’s spending its time, and therefore what I could do differently to improve things.

I can hunt this down with trial and error, but it would be nice if there was some way to look inside “Process Time” to see details.

You could try using an external profiler CPU optimization — Godot Engine (stable) documentation in English and see if you can pinpoint exactly where the performance loss is. But, yeah, I think it’s all the animation stuff.

I’d try with VisibleOnScreenEnabler3D as it can disable the process of the target node and all its children as long as their Node.process_mode is set to inherit (the default value)

Ok, I’ll give VisualOnScreenEnabler3D a whirl. Thanks!

At the advice of a friend, I’m currently adding type annotation to all my scripts; I had a lot of inferred types. I’ve got a ways to go, but I’ve already got another half millisecond or so improvement from it.