I feel like a lot Unity developers coming into Godot are scared about performance of it’s approach, and I think they don’t understand the approach of the engine in regards to performance.
In general, there are two types of complex functions in a game:
A: complex functions that have to run every frame
B: complex functions that run maybe a 100 times per gameplay hour
The B functions don’t impact performance that much, especially since it’s unlikely that multiple will happen on a single frame - if needed, splitting them off to a separate thread is usually fine. They don’t need APIs optimized for extreme performance.
Godot’s approach is to push as much of A functions into engine code as possible, giving rich APIs for you to have simple functions that run every frame and if some A isn’t there, engine code is right there, for you to extend.
Unity on the other hand, without having the fairly graceful fallback of “just extend the engine” and, IMO, having mostly ignored building rich, nice to use APIs in favor of building more features, couldn’t really operate without a massive focus on performance - because the API’s weren’t that fit to utilize as much of A as was in the engine, nor could you just extend the engine.
Honestly, after working with Godot a bunch, I came to a conclusion that most of the time, game logic running every frame is a code smell. Rather, most of the code should utilize signals to be triggered by engine code only as needed - on a colision, on a timer, on a button press, etc. That way, you utilize what’s optimized for performance (the engine runtime) when you need performance, and utilize what’s optimized for usability(GDScript, Node system, signals) when you need usability.
It sounds like your experience with Godot has led to a fundamental change in thinking about the problem, so it’d be worth sharing. I’d love to read a post elaborating your comment with examples and how things would (should?) look in Godot.
The bit near the end is the key bit. I think it’s unfair to Godot to claim that it was designed to be slow. It was[1] designed to be easy to use, with other goals being secondary. GDScript is incredibly concise. Using C# with Godot requires a lot more boilerplate. The late binding dynamic behaviour of GDScript makes it easily approachable by people with little programming experience, perhaps some JavaScript.
In particular, it isn’t designed as a tool for experienced game developers to write high-performance systems, it’s designed as a way for people that want to build games to delegate the high-performance parts to the experienced game developers who write the engine. As someone who is not an experienced game developer, this appeals to me. The goal for the authors was, as I understand produce something that let them quickly write games for customers.
It sounds as if the author wants instead a set of building blocks that they can reuse to build their own game engine. Decomposing Godot so that it can expose those building blocks may attract more contributors and so might be a worthy goal.
The examples from the article had a bit of a ‘you’re holding it wrong’ feel to me. Why are you doing ray casting from the scripting layer on the hot path? You have a game engine, it knows how to do the rendering and exposes physics things for object collisions. This kind of code seems like something that you’d provide as a shader, not write in the scripting layer. But, as I said, I’m not a game developer, so maybe there’s a good reason.
[1] From the perspective of someone who has read the docs but never got around to using it.
Yeah, the conclusion sounds like some of the “Python slow” FOMO I sometimes hear. Slow interpreted languages don’t affect the overall performance much when they’re just glue code. Computers are fast, most small indie game logic is trivial, and the expensive parts of the engine are usually implement in highly optimized kernel code that’s internal to the engine just like Numpy for Python.
You might run up against a handful of high-level performance issue sooner than you would in a compiled language (e.g. some naïve AI logic will only run at acceptable speeds on 10 units rather than 100 units as it would with a compiled language), but when the focus is prototyping and experimentation that’s really not a primary concern. If you need a new high-performance kernel, it’s quite easy to drop into C# or even C and implement a tight loop or two that you can call in your glue code in the scripting layer.
Why are you doing ray casting from the scripting layer on the hot path?
Raycasts are quite common in game logic, since it’s often used for checks that find nearby objects, line of site calculation for AI units, hit detection for bullet paths, and character controller code, which isn’t usually driven by rigid-body physics simulation in most games but is hand crafted with non-physical logic so controls feel responsive and snappy. But I don’t think it’s much of a hot path relative to what else an engine does.
That’s not to say I wouldn’t like a scripting language with good performance characteristics for games! Spare compute every frame would still be appreciated. I’d love something with greater control over memory layout via ownership semantics. Late binding isn’t often useful, and there’s probably some low-hanging fruit these interpreters could take advantage of, but my gamedev pain points are rarely caused by poor scripting language speed.
FWIW, I’ve done several game jams in Python (with Raylib) and never had an issue with performance, even when doing realtime streaming audio processing in a Python callback in a simple 3D game! From my extremely unscientific experiments, Python is usually around 10x slower than an equivalent impl in C/Zig if a lot of the time is spent in Python loops. If you’re using 60% of the frame time on game logic, it might be worth porting some segment of that to a lower level language, but if it’s <10% (as it always has been in my games), you don’t gain much.
Why are you doing ray casting from the scripting layer on the hot path?
Raycasts are quite common in game logic, since it’s often used for checks that find nearby objects, line of site calculation for AI units, hit detection for bullet paths, and character controller code, which isn’t usually driven by rigid-body physics simulation in most games but is hand crafted with non-physical logic so controls feel responsive and snappy. But I don’t think it’s much of a hot path relative to what else an engine does.
But most of those can be moved out for hot path. Checks for hit detection for bullet paths happens only when a bullet is shot - that happens less than once per frame in not bullet-hell type games, and can easily be implemented with the node solution, without even changing the positions of it (as the post complained). Line of sight calculations for AI units can be implemented similarly with a raycast colider, only triggering a response when an appropriate target has been hit. Checks for nearby objects should mostly be doable with the same approach. Character control does depend more on your code, but even then, you can easily use the node solution, again, without changing the positions of it (rather, the raycast would usually be needed in a fixed position in relation to the character, and that’s trivial to do in Godot).
It is IMO trivial to offload most of the times whatever you want to do with raycasts to be mostly done in-engine.
Implementations of languages with similar ergonomic properties, like js and lua, have fairly good performance (with fast gc, shape inference, and jit compilation). The language is not the principal issue here.
The issue at hand is not the language but the performance of the interoperability layer with C++. This remains hard. As the article says, Unity has a custom C# compiler for this. C++ interop is often a bottleneck in JavaScript (with v8, it’s often faster to rewrite something in JavaScript than call into C++: the JavaScript is a good fraction of C++ performance and anything on a hot path that goes through the interop layer is a bad idea). Surfacing objects into the managed language from the unmanaged world often incurs multiple levels of indirection in both cases.
The issue at hand is that the performance profile of the engine is inadequate for a would-be user’s needs. If gdscript were fast enough, there would be no need to use c#. (Let me add, I can imagine alternate ways of structuring the implementation that would enable fast interoperation with other languages without harming gdscript ergonomics—even using a naive interpreter for the latter—still only an implementation issue.)
Even if that was the bottleneck, a truly mind-boggling amount of effort went into making JS fast.
And because of its niche Lua was designed so it could be made relatively light and fast, and luajit is still a heroic amount of effort by Mike Pall.
I have no idea what informed the design of gdscript. But language design absolutely matters when it comes to the effort necessary to making a fast implementation (ignoring other issues such as exposed APIs / FFI, which can also severely hobble the effort).
In what respects is lua easier to implement performantly than js? I think it has most, if not all, of the same interesting dynamic characteristics. I am pretty sure it was originally designed as a configuration language—and hence not expected to be fast at all—and grew organically into its current role.
I think there are a good number of people who could produce something not much worse in quality than luajit given appropriate time (0.5–2 years) and interest/incentive. (I count myself in that number.) Especially considering that luajit already exists and has done a good amount of exploration of its design space (you could even just use its code as is…)
I feel like a lot Unity developers coming into Godot are scared about performance of it’s approach, and I think they don’t understand the approach of the engine in regards to performance.
In general, there are two types of complex functions in a game:
The B functions don’t impact performance that much, especially since it’s unlikely that multiple will happen on a single frame - if needed, splitting them off to a separate thread is usually fine. They don’t need APIs optimized for extreme performance.
Godot’s approach is to push as much of A functions into engine code as possible, giving rich APIs for you to have simple functions that run every frame and if some A isn’t there, engine code is right there, for you to extend.
Unity on the other hand, without having the fairly graceful fallback of “just extend the engine” and, IMO, having mostly ignored building rich, nice to use APIs in favor of building more features, couldn’t really operate without a massive focus on performance - because the API’s weren’t that fit to utilize as much of A as was in the engine, nor could you just extend the engine.
Honestly, after working with Godot a bunch, I came to a conclusion that most of the time, game logic running every frame is a code smell. Rather, most of the code should utilize signals to be triggered by engine code only as needed - on a colision, on a timer, on a button press, etc. That way, you utilize what’s optimized for performance (the engine runtime) when you need performance, and utilize what’s optimized for usability(GDScript, Node system, signals) when you need usability.
It sounds like your experience with Godot has led to a fundamental change in thinking about the problem, so it’d be worth sharing. I’d love to read a post elaborating your comment with examples and how things would (should?) look in Godot.
The bit near the end is the key bit. I think it’s unfair to Godot to claim that it was designed to be slow. It was[1] designed to be easy to use, with other goals being secondary. GDScript is incredibly concise. Using C# with Godot requires a lot more boilerplate. The late binding dynamic behaviour of GDScript makes it easily approachable by people with little programming experience, perhaps some JavaScript.
In particular, it isn’t designed as a tool for experienced game developers to write high-performance systems, it’s designed as a way for people that want to build games to delegate the high-performance parts to the experienced game developers who write the engine. As someone who is not an experienced game developer, this appeals to me. The goal for the authors was, as I understand produce something that let them quickly write games for customers.
It sounds as if the author wants instead a set of building blocks that they can reuse to build their own game engine. Decomposing Godot so that it can expose those building blocks may attract more contributors and so might be a worthy goal.
The examples from the article had a bit of a ‘you’re holding it wrong’ feel to me. Why are you doing ray casting from the scripting layer on the hot path? You have a game engine, it knows how to do the rendering and exposes physics things for object collisions. This kind of code seems like something that you’d provide as a shader, not write in the scripting layer. But, as I said, I’m not a game developer, so maybe there’s a good reason.
[1] From the perspective of someone who has read the docs but never got around to using it.
Yeah, the conclusion sounds like some of the “Python slow” FOMO I sometimes hear. Slow interpreted languages don’t affect the overall performance much when they’re just glue code. Computers are fast, most small indie game logic is trivial, and the expensive parts of the engine are usually implement in highly optimized kernel code that’s internal to the engine just like Numpy for Python.
You might run up against a handful of high-level performance issue sooner than you would in a compiled language (e.g. some naïve AI logic will only run at acceptable speeds on 10 units rather than 100 units as it would with a compiled language), but when the focus is prototyping and experimentation that’s really not a primary concern. If you need a new high-performance kernel, it’s quite easy to drop into C# or even C and implement a tight loop or two that you can call in your glue code in the scripting layer.
Raycasts are quite common in game logic, since it’s often used for checks that find nearby objects, line of site calculation for AI units, hit detection for bullet paths, and character controller code, which isn’t usually driven by rigid-body physics simulation in most games but is hand crafted with non-physical logic so controls feel responsive and snappy. But I don’t think it’s much of a hot path relative to what else an engine does.
That’s not to say I wouldn’t like a scripting language with good performance characteristics for games! Spare compute every frame would still be appreciated. I’d love something with greater control over memory layout via ownership semantics. Late binding isn’t often useful, and there’s probably some low-hanging fruit these interpreters could take advantage of, but my gamedev pain points are rarely caused by poor scripting language speed.
FWIW, I’ve done several game jams in Python (with Raylib) and never had an issue with performance, even when doing realtime streaming audio processing in a Python callback in a simple 3D game! From my extremely unscientific experiments, Python is usually around 10x slower than an equivalent impl in C/Zig if a lot of the time is spent in Python loops. If you’re using 60% of the frame time on game logic, it might be worth porting some segment of that to a lower level language, but if it’s <10% (as it always has been in my games), you don’t gain much.
But most of those can be moved out for hot path. Checks for hit detection for bullet paths happens only when a bullet is shot - that happens less than once per frame in not bullet-hell type games, and can easily be implemented with the node solution, without even changing the positions of it (as the post complained). Line of sight calculations for AI units can be implemented similarly with a raycast colider, only triggering a response when an appropriate target has been hit. Checks for nearby objects should mostly be doable with the same approach. Character control does depend more on your code, but even then, you can easily use the node solution, again, without changing the positions of it (rather, the raycast would usually be needed in a fixed position in relation to the character, and that’s trivial to do in Godot).
It is IMO trivial to offload most of the times whatever you want to do with raycasts to be mostly done in-engine.
Implementations of languages with similar ergonomic properties, like js and lua, have fairly good performance (with fast gc, shape inference, and jit compilation). The language is not the principal issue here.
The issue at hand is not the language but the performance of the interoperability layer with C++. This remains hard. As the article says, Unity has a custom C# compiler for this. C++ interop is often a bottleneck in JavaScript (with v8, it’s often faster to rewrite something in JavaScript than call into C++: the JavaScript is a good fraction of C++ performance and anything on a hot path that goes through the interop layer is a bad idea). Surfacing objects into the managed language from the unmanaged world often incurs multiple levels of indirection in both cases.
The issue at hand is that the performance profile of the engine is inadequate for a would-be user’s needs. If gdscript were fast enough, there would be no need to use c#. (Let me add, I can imagine alternate ways of structuring the implementation that would enable fast interoperation with other languages without harming gdscript ergonomics—even using a naive interpreter for the latter—still only an implementation issue.)
Even if that was the bottleneck, a truly mind-boggling amount of effort went into making JS fast.
And because of its niche Lua was designed so it could be made relatively light and fast, and luajit is still a heroic amount of effort by Mike Pall.
I have no idea what informed the design of gdscript. But language design absolutely matters when it comes to the effort necessary to making a fast implementation (ignoring other issues such as exposed APIs / FFI, which can also severely hobble the effort).
In what respects is lua easier to implement performantly than js? I think it has most, if not all, of the same interesting dynamic characteristics. I am pretty sure it was originally designed as a configuration language—and hence not expected to be fast at all—and grew organically into its current role.
I think there are a good number of people who could produce something not much worse in quality than luajit given appropriate time (0.5–2 years) and interest/incentive. (I count myself in that number.) Especially considering that luajit already exists and has done a good amount of exploration of its design space (you could even just use its code as is…)
Waiting for Godot…