When not all strings are Strings. Detect bugs in your GDscript more easily with static typing
One of the benefits of working with Godot Engine is that GDScript allows one to operate high level. GDScript is dynamically typed, so not even variable types have to be specified, but I would strongly recommend using static typing wherever possible. It can help with performance but primarily adds clarity when trying to follow the flow of a script.
var name ="Adam"print(name)
In this case,
name
is a String, but in a more complex code base that could change:
var name ="Adam"name =42# yoinkprint(name)
This flexibility can lead to confusion, especially once multiple nodes and scripts are involved, passing data back and forth. By forcing a data type, you can be sure the data type doesn’t flip on you later:
var name: String ="Adam"name =42# will throw an errorprint(name)
This helps reduce cognitive load while working with the code because the developer doesn’t have to keep track of what a variable might be. And the editor’s static analysis tooling can help point out these static type mismatches:
It doesn’t help avoid all Pitfalls – curveball incoming
It’s quite helpful to be familiar with the data types, especially some of the more specialty types that Godot supports.
Here’s an instance where static typing indirectly saved my bacon: I was prototyping a multiplayer component and wanted to reduce the overhead of sending node paths across the network. Rather than sending strings, I built lookup tables (arrays) with the paths of game objects, which can then be addressed by their index in the lookup table. So rather than sending
"/root/World/TileMap/NPC-22"
, it would just send something like “3”. An ID over an entire node path adds up, especially at scale.
However, at the time I was not yet cognizant of the fact that Godot Engine doesn’t just use
String
s to keep track of node paths, it uses a particular data type: NodePath.
My implementation required a lookup table that kept track of which nodes the other side is already aware of. My initial naïve implementation used this lookup table:
var lut: Array =[]
You may already see where this is going. I expected an array of strings, but since Node.get_node() returns a
NodePath
, that’s what I got instead. What would you expect to happen when you add static typing?
var lut: Array[String]=[]
If you try to add a NodePath object to this
lut
array, it will silently drop it. This is slightly worse than what happens when you pass a NodePath to a method that requires a string parameter.
Try it for yourself. Create a new scene with a simple
Node
, attach a script, and paste in these contents:
If you run the scene you should get something similar to the following output:
--- Debugging process started ---Godot Engine v4.0.beta2.official.f8745f2f7 - https://godotengine.orgVulkan API 1.2.0 - Using Vulkan Device #0: NVIDIA - NVIDIA GeForce GTX 1070 Tinode_path_builder():/root/Node(type:22)string_receiver(/root/Node)(type:4)[^"/root/Node"]--- Debugging process stopped ---
The path that is constructed in
node_path_builder()
is of type 22 (
NodePath
), whereas when it gets passed to the
string_receiver()
method, it’s auto-typed to
String
.
Printing out the lut shows that little caret (^) character before the
"/root/Node"
path, to indicate that it’s not just a plain String.
Now change line 3 so it’s typed to
Array[String]
, and run the scene again:
--- Debugging process started ---Godot Engine v4.0.beta2.official.f8745f2f7 - https://godotengine.orgVulkan API 1.2.0 - Using Vulkan Device #0: NVIDIA - NVIDIA GeForce GTX 1070 Tinode_path_builder():/root/Node(type:22)string_receiver(/root/Node)(type:4)[]--- Debugging process stopped ---
Nothing was added to the array. It’s a bit of an odd scenario. Static typing probably shouldn’t result in things getting dropped or auto-converted silently, but I think the intent was to optimize the performance when dealing with node paths, but trying to abstract it away from the developer, so it ended up in this “things are not as they appear, nor are they otherwise” state.
In my case, the array ended up being a mix of String and NodePath data types, and comparisons would fail. Once I added static typing to the array, more stuff broke, and I took a closer look and noticed this discrepancy. Just being aware and knowing how Godot operates internally helps when encountering these types of things.
The more static typing there is in place, the more opportunity for static analysis tooling to find easily overlooked issues. The idea that you can just hide the data type and automate it away is great, but there’s usually a runtime performance penalty associated with it. There’s also less clarity for the developer of what something is or does.
Whenever possible I recommend specifying variable types, method arguments, and return types in order to build more robust code and interfaces.
New to Godot Engine? Want to get started creating awesome games quickly? Just use AI! AI learns (is trained) from online content (which is a whole separate topic). As a result, the quality of the answers the AI provides is based on the volume and variety of content available to learn from. Since Godot is …
Godot Engine 4 has a bug (#50824) that causes particle jitter when the game is paused. It does look quite distracting. The Bug in Action This is a proof-of-concept wave shooter running in Godot Engine 4.0.2. Pausing the game was initially just a simple process_mode toggle. But as shown in the video clip above, there …
The Terrain3D addon for Godot lets you create and manage 3D terrains within Godot. It looks quite promising for making landscapes, hills, valleys, and other natural environments. The addon provides tools for sculpting the terrain, and you can paint different textures like grass, dirt, or rock, and blend them smoothly. It also supports features like …
Godot Engine 4.0 has been released per official announcement. With version 4.0, Godot has gone through “3+ years of breaking and rebuilding from the ground up, a complete core overhaul and a full engine rewrite, through 17 alphas, 17 betas and 6 release candidates“. The announcement post is credited to “2000+ Godot contributors.” It’s a …
When not all strings are Strings. Detect bugs in your GDscript more easily with static typing
One of the benefits of working with Godot Engine is that GDScript allows one to operate high level. GDScript is dynamically typed, so not even variable types have to be specified, but I would strongly recommend using static typing wherever possible. It can help with performance but primarily adds clarity when trying to follow the flow of a script.
In this case,
name
is a String, but in a more complex code base that could change:This flexibility can lead to confusion, especially once multiple nodes and scripts are involved, passing data back and forth. By forcing a data type, you can be sure the data type doesn’t flip on you later:
This helps reduce cognitive load while working with the code because the developer doesn’t have to keep track of what a variable might be. And the editor’s static analysis tooling can help point out these static type mismatches:
It doesn’t help avoid all Pitfalls – curveball incoming
It’s quite helpful to be familiar with the data types, especially some of the more specialty types that Godot supports.
Here’s an instance where static typing indirectly saved my bacon: I was prototyping a multiplayer component and wanted to reduce the overhead of sending node paths across the network. Rather than sending strings, I built lookup tables (arrays) with the paths of game objects, which can then be addressed by their index in the lookup table. So rather than sending
"/root/World/TileMap/NPC-22"
, it would just send something like “3”. An ID over an entire node path adds up, especially at scale.However, at the time I was not yet cognizant of the fact that Godot Engine doesn’t just use
String
s to keep track of node paths, it uses a particular data type:NodePath
.My implementation required a lookup table that kept track of which nodes the other side is already aware of. My initial naïve implementation used this lookup table:
You may already see where this is going. I expected an array of strings, but since
Node.get_node()
returns aNodePath
, that’s what I got instead. What would you expect to happen when you add static typing?If you try to add a NodePath object to this
lut
array, it will silently drop it. This is slightly worse than what happens when you pass a NodePath to a method that requires a string parameter.Try it for yourself. Create a new scene with a simple
Node
, attach a script, and paste in these contents:If you run the scene you should get something similar to the following output:
The path that is constructed in
node_path_builder()
is of type 22 (NodePath
), whereas when it gets passed to thestring_receiver()
method, it’s auto-typed toString
.Printing out the lut shows that little caret (^) character before the
"/root/Node"
path, to indicate that it’s not just a plain String.Now change line 3 so it’s typed to
Array[String]
, and run the scene again:Nothing was added to the array. It’s a bit of an odd scenario. Static typing probably shouldn’t result in things getting dropped or auto-converted silently, but I think the intent was to optimize the performance when dealing with node paths, but trying to abstract it away from the developer, so it ended up in this “things are not as they appear, nor are they otherwise” state.
In my case, the array ended up being a mix of String and NodePath data types, and comparisons would fail. Once I added static typing to the array, more stuff broke, and I took a closer look and noticed this discrepancy. Just being aware and knowing how Godot operates internally helps when encountering these types of things.
The more static typing there is in place, the more opportunity for static analysis tooling to find easily overlooked issues. The idea that you can just hide the data type and automate it away is great, but there’s usually a runtime performance penalty associated with it. There’s also less clarity for the developer of what something is or does.
Whenever possible I recommend specifying variable types, method arguments, and return types in order to build more robust code and interfaces.
Related Posts
Creating games with Godot Engine using AI
New to Godot Engine? Want to get started creating awesome games quickly? Just use AI! AI learns (is trained) from online content (which is a whole separate topic). As a result, the quality of the answers the AI provides is based on the volume and variety of content available to learn from. Since Godot is …
The Pause Handler: Working around Godot 4’s particle jitter bug
Godot Engine 4 has a bug (#50824) that causes particle jitter when the game is paused. It does look quite distracting. The Bug in Action This is a proof-of-concept wave shooter running in Godot Engine 4.0.2. Pausing the game was initially just a simple process_mode toggle. But as shown in the video clip above, there …
Terrain3D for Godot has been released
The Terrain3D addon for Godot lets you create and manage 3D terrains within Godot. It looks quite promising for making landscapes, hills, valleys, and other natural environments. The addon provides tools for sculpting the terrain, and you can paint different textures like grass, dirt, or rock, and blend them smoothly. It also supports features like …
Godot Engine 4.0 released!
Godot Engine 4.0 has been released per official announcement. With version 4.0, Godot has gone through “3+ years of breaking and rebuilding from the ground up, a complete core overhaul and a full engine rewrite, through 17 alphas, 17 betas and 6 release candidates“. The announcement post is credited to “2000+ Godot contributors.” It’s a …