Post

in-depth thread: optimizing GDScript performance from ~83ms to ~9ms in a scene our tower defense game Rift Riff, made in Godot, with 1000 enemies. πŸ§΅πŸ‘‡πŸΌ #gamedev #godotengine

135 reposts603 likes

I've struggled with performance in my tower defense game as well, going to try out some of these optimizations! I've noticed using collision shapes on enemies is also very costly but necessary as enemies can get knocked back and need to collide with towers. Do you use collision on your enemies?

Not a big time OOP fan, but I don't read the thread this way: - different physics object type (still using inheritance) - cache strings - avoid unnecessary nav stuff - time slicing :( - move non physics code out of physics updates/callbacks All mostly independent of using OOP or not.

Awesome! As someone contributing to Godot's performance, this insight is super helpful. Hopefully we'll be able to address some of these performance sinks from the Godot side in 4.6!

(note before I start: it was too much work to profile each optimization and this thread already took 3 hours to make (literally πŸ˜…) so instead I opted to start each optimization with plusses, and more plusses == bigger performance gains. you'll just have to trust me the gains are as stated 😏.)

+ we changed the enemy from extending a CharacterBody2D to a RigidBody2D. the character body offers a funky mix between code-controlled and physics controlled movement, but it turned out that this only costs extra for no gains.

It's the opposite - RigidBody allows either code control or physics simulated movement, CharacterBody is code control only. Ideally, you could extend from PhysicsBody, but the engine doesn't allow this for some reason 🫠

++ we removed all String and StringName operations from _process(). enemies have directional move animations and we used to construct the names of those animations on the fly every time. now, we cache them on _ready() per direction. (the code diff of this was messy but here's how we cache them)

+++ we replaced polling nav_agent.is_navigation_finished() with a local variable set by the navigation_finished signal. we looked at the source code of is_navigation_finished() and it turns out the function fully updates pathfinding and avoidance, which is super costly.

Wow, this is an eye opening thread, but not in the way I expected. It reveals that gdscript is absurdly slow and that the runtime really sucks, as 99% of these tips really would NOT make a noticeable difference in C# unless you were running that code millions of time per frame (not thousands).

sorry karel, this is an incredibly hyperbolic takeaway. i feel like every interaction we have is contrarian and it is exhausting. muting.

To clarify, C# does a lot of this work automatically behind the scenes. It has the benefit of compiling to an intermediate language though, which means it can apply all kinds of automatic optimizations while doing it.

If gdscript has no intermediate compilation step and is purely interpreted, I don’t see how it can ever be ready for the big leagues. It just won’t ever work out. I kinda assumed it would have some intermediate language compilation, like almost every other language nowadays.