Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save belzecue/4e4f64a579037e5b9c1088a9b7e82a1a to your computer and use it in GitHub Desktop.
Save belzecue/4e4f64a579037e5b9c1088a9b7e82a1a to your computer and use it in GitHub Desktop.
GDScript 1.0 to 2.0 Beginner Friendly Conversion Guide
Beginner-friendly GDScript 1 to GDScript 2 Conversion Guide
First and foremost, this should not be considered a replacement for the official migration guide,
found at https://docs.godotengine.org/en/latest/tutorials/migrating/upgrading_to_godot_4.html!
Instead, this document is intended to be a friendly guide to helping you upgrade your projects from
GDScript 1 to 2. To do this, I'm going to list the most common conversions I've run into while upgrading
a few of my games and tools. This document is written as of the first release candidate version of Godot 4.0.
The first thing to do when converting a project (no need if you're starting from new) is to either have your
project in source control, or make a duplicate of your entire project's folder and rename it. This will
ensure that you can go back if something breaks beyond repair during conversion.
The second thing to do is to actually try to import your project into the Godot 4 editor! You can do this
by opening the 4.0 executable as normal, then go to the Import button and tapping that, navigating to your
project, selecting it and hitting import. This will warn you that the project needs to be upgraded to the
latest version - go ahead and proceed with the upgrade; I always suggest doing a full upgrade instead of
just project-file-only. When it's done, you can open your project.
At this point, your project will have a *ton* of changes, including some things that aren't exactly errors,
but may appear to be, and will not be as pretty as they could be - we'll fix those as well. You can treat
this as a guide you can use to update the code to modernize it using new GDScript 2 syntax, or a guide that
can be used to understand how to start writing new code in GDScript 2!
The first and probably largest change you'll notice has to do with signals. Any sufficiently complex system
in Godot tends to make heavy use of signals, and the changes to them mdoernize signals to remove the unsafe
"stringy" nature of them and turn them into a first class language feature. The converter, however, uses a
long-hand form for signal conversion because it's the only way to do it that works in all circumstances
without having to make other changes. So, to rectify that, we can close the gap here and make the code a lot
cleaner.
In all places, you'll generally see signal connections changed to something like this:
`_animation_player.connect("animation_finished",Callable(self,"_on_damage_animation_finished").bind(),CONNECT_ONE_SHOT)`
The first thing to notice is that it appears to still be using a sort of older form of the code that uses
mostly strings rather than the new syntax changes. As previously mentioned, this is because this is
_technically_ the only way to ensure that the conversion always works, but I'll explain when and why you
might have to do it this way vs a much simpler way. The much simpler way would be to convert the above to
the following:
`_animation_player.animation_finished.connect(_on_damage_animation_finished, CONNECT_ONE_SHOT)`
This is not only much simpler to understand, it utilizes two new elegant changes to GDScript 2.0: signals
as first-class entities, and functions as first-class entities.
Signals being first-class entities means they can be referenced in code rather than in a string, allowing
a signal to be passed around to other functions in a type-safe fashion. This will ensure that signals
don't require any kind of weird type-checking to ensure that the signal you're trying to connect to exists
on the type of node that you've got: the compiler can tell for you if it exists or not and help you out
with an error if it doesn't exist.
Functions being first-class entities means they, too, can be passed around to other functions in a type-safe
fashion without having to rely on a generic "call" method, nor on FuncRefs. While FuncRefs served for a while
as an almost-successful stand-in for lambda functions that so many other languages have had for a long time,
the new function as an entity allows us to finally have lambdas as well! The syntax for that is, for example:
`func(): _current_value += float(_text.text)`. Do note however that, from experience, converting to a lambda
doesn't always make sense, and can lead to code that is hard to maintain - it is often better just to pass an
actual function instead. To do that, you can just pass it like I do in the above example with
_on_damage_animation_finished - note that there are no quotes; it is referencing the function directly.
The reason the project converter ultimately takes a much longer form of this is that when you have a function
on a different script that you still want to connect to, or you want to ensure that extra parameters are passed
to the methods that get called when the signal is raised (commonly done in GDScript 1.0 by passing the array
binds in your connect call, i.e. `connect("signal", self, "function", [])`), the longer form is mostly the only
way to handle it universally. Note that, in general, you won't have to do the long form: it seems like the short
form works best in most cases. To pass extra values with bindings into the functions that get called when the
signal is raised, you can include the `.bind()` syntax on the end of the function name that is being connected,
and include any variable values you want to be included in the parentheses.
One thing you'll notice is a quick rename, as well: if you've ever connected to a signal in 3.x and had to do a
oneshot connection, you'll remember at the end of the connect call, you'd include a CONNECT_ONESHOT enum value
passed in. In GDScript 2.0, there's a large number of updated API names to modernize and simplify (even if it
makes them longer) the names for beginners. Quite a few of the changes are documented here
https://gist.github.com/WolfgangSenff/168cb0cbd486c8c9cd507f232165b976. You can search this other document easily
in your browser, so if you get an error in Godot 4.0 that isn't clear how to fix, take the code word that may be
the error and search for it in this gist. If you don't find it, you can post a question about it on the gist and
I'll try to find it for you! Here are a few common examples so you can see the types of changes:
CONNECT_ONESHOT -> CONNECT_ONE_SHOT
change_scene -> change_scene_to_file
change_scene_to -> change_scene_to_packed
update -> queue_redraw
TileMap's world_to_map -> local_to_map and vice versa
deg2rad -> deg_to_rad and vice versa
PoolVector2Array -> PackedVector2Array - this is true for all PoolXArray versions, they all change to PackedXArray
File -> FileAccess (some functions formerly of Directory have been changed to FileAccess)
Directory -> DirAccess
instance -> intantiate
idle_frame (when yielding) -> process_frame (when awaiting)
A very common node that has been renamed and comes with appropriate API updates is KinematicBody2D (and 3D):
it has now been updated to CharacterBody2D, and one of the changes that's going to propagate throughout your
GDScript code is this: instead of having a bunch of unclear parameters passed to `move_and_slide` or
`move_and_collide`, you now set a lot of those parameters ahead of time and just call `move_and_slide`. An
example of this might be:
```
func _physics_process(delta: float) -> void:
velocity += Vector2.DOWN * delta * GRAVITY # velocity is a new property included on CharacterBody2D automatically)
move_and_slide()
```
Note you no longer need to include a velocity property in your script at all - it is included automatically
as a matter of course.
Shaders have had a number of renames and changes. See the bottom of the document here for a list of common
ones: https://gist.github.com/WolfgangSenff/168cb0cbd486c8c9cd507f232165b976
The next biggest set of changes is likely going to be with your exported variables. These are fairly well-defined
and the converter does a great job of converting them, but if you're working on a new project, the following
should help you understand the differences between 1.0 and 2.0.
First, most "annotations" such as "export" and "onready" have been changed to have an at symbol (@) before the
annotation name in order to make it clearer what their purpose is. It also makes things simpler internally for
Godot, but that's not the point of this document. An example of what this conversion may look like could be:
3.x:
`onready var _anim = $AnimationPlayer`
4.0:
`@onready var _anim = $AnimationPlayer`
Note it also adds the ability to have a newline after the annotations if you like. For instance:
```
@onready
var HPValue
```
This will function as if it was all on the exact same line.
A similar change can be seen with @export. However, with the @export annotation, there are a number
of new annotations to keep in mind. An example of them is:
@export
@export_range
@export_enum
@export_node_path
See others here: https://docs.godotengine.org/en/latest/tutorials/scripting/gdscript/gdscript_exports.html
Finally on the topic of annotations, one change that I've personally been having to update a lot is
my set of tool scripts. Tool scripts have `tool` at the top of them in 3.x, but in 4.0, it's simply changed
to @tool.
Properties (commonly known as values with setters and getters) have been upgraded as well. Previously, you
defined a setter and a getter like this in 3.x:
`var property setget set_property, get_property # not pretty if you want just a getter and no setter: var property setget , get_property - yikes!`
Then, later on in the script, you'd define functions for set_property and get_property. Now, the setters
and getters have a much cleaner syntax and are defined much closer to the definition of the property, which
is nice, as previously they could get easily lost in among the other functions of the script. Now, properties
with setters and getters look like:
var hp_value: int:
set(value):
hp_value = value
hpvalue_changed.emit(hp_value) # (example of how to emit a signal now that they are first-class entities)
get:
return hp_value
Note that `get` here does not have a parameter, and elides the functional parentheses as such.
Finally for this document, the changes you're most likely to need next have to do with yielding. In
previous versions of Godot, yield was built on top of the concept of coroutines, or the idea that a
function can be placed into an executable set of steps that can be stopped and resumed at will, returning
the function automatically to the parent to manage the executing/paused state. This is an extremely powerful
concept, but added a lot of syntactical idiosyncracies in the code that were sometimes hard to follow. An
example of this in 3.x is when you want to yield a function so that it only gives you control back after
all yields used in that function were completed. For beginners, most didn't even know you could do this,
and for advanced users, it wasn't clear how it worked, just that it did. Coroutines are what made that happen,
utilizing a return type called GDScriptFunctionState. You could then do something like:
`yield(function_to_execute(), "completed")`
The problem with this is obvious: if the script which is running function_to_execute does not contain a
"completed" signal, where on earth did it come from?! It is actually coming from the GDScriptFunctionState
return type listed above! Which, if we're being honest, was not clear at all.
With 4.0, we get a much cleaner, clearer syntax for how these things work, with no extra trappings of
mysterious completed signals or anything like that. In fact, awaiting signals or functions is effectively
the same thing now with absolutely no extraneous cruft required to be memorized. An example of the above
using the new await syntax is:
`await function_to_execute()`
And that's it! No longer do you have to await for a seemingly-random "completed" signal coming from
who-knows-where. It's really nice.
Note that the autoconverter will often get this wrong, and instead change it to:
`await function_to_execute().completed`
You can just delete the completed signal off the end of it and get the exact same functionality
you had as before.
To await a signal, it's similar to the problematic code above (you can see why the converter
struggles with this):
`await $AnimationPlayer.animation_finished`
Okay, that's all for now! If you have any questions about conversions, feel free to drop me a
line here down below, on Twitter (@KyleTheCoder), Mastodon (https://mastodon.gamedev.place/@BackAt50Ft),
or wherever you see me on Discord (typically @BackAt50Ft)!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment