Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Godot 4.0 Migration/Upgrade guide
## For a beginner-friendly version of the following (more advanced users likely will get better use of the below,
## if you're just starting out...), see this new gist:
This document represents the beginning of an upgrade or migration document for GDScript 2.0 and Godot 4.0. I'm focusing on 2D
at the moment as I'm upgrading a 2D game, but will hopefully have more to add for 3D afterward.
## If you want more content like this, please help fund my cat's medical bills at - thank you very much! On to the migration guide.
# Godot 4.0 Upgrade/Migration Guide
# GDScript:
Renames - the following classes have been renamed:
Node.raise() -> CanvasItem.move_to_front()
SceneTree.change_scene -> SceneTree.change_scene_to_file
SceneTree.change_scene_to -> SceneTree.change_scene_to_packed
ssl -> tls in properties and methods across the board
in range properties: or_lesser -> or_less
Curve/Curve2D/Curve3D/Gradient interpolate -> sample
caret_blink_speed -> caret_blink_interval
CanvasItem update -> queue_redraw
AABB/Rect2/Rect2i has_no_* methods -> has_* methods
TileMap/GridMap world_to_map -> local_to_map
TileMap/GridMap map_to_world -> map_to_local
uniform -> parameter in various places across the engine
ProgressBar percent_visible -> show_percentage
JavaScript singleton -> JavaScriptBridge
range_lerp -> remap
PopupMenu get_current_index -> get_focused_item
StreamPeerSSL -> StreamPeerTLS
EditorInterface get_editor_main_control -> get_editor_main_screen
str2var -> str_to_var
deg2rad -> deg_to_rad
rad2deg -> rad_to_deg
ParticlesMaterial -> ParticlesProcessMaterial
Position2D -> Marker2D
Position3D -> Marker3D
Label & RichTextLabel percent_visible -> visible_ratio
PathFollow's offsets -> progress & progress_ratio
hint_tooltip -> tooltip_text
Rename JavaScript platform to Web
PoolByteArray -> PackedByteArray
PoolColorArray -> PackedColorArray
PoolFloat32Array -> PackedFloat32Array
PoolFloat64Array -> PackedFloat64Array
PoolInt32Array -> PackedInt32Array
PoolInt64Array -> PackedInt64Array
PoolStringArray -> PackedStringArray
PoolVector2Array -> PackedVector2Array
PoolVector3Array -> PackedVector3Array
# Multiplayer:
The arguments that can be used with @rpc must be one of:
mode: call_local, call_remote
sync: any_peer, authority
transfer_mode: reliable, unreliable, or unreliable_ordered
channel: this is just an integer, not a string, and represents what channel you may want to send data on, so you aren't cluttering up the main channel with text for chat (for instance)
With the above values for mode, sync, and transfer_mode, you can just write the exact value as a string in your @rpc annotation; it does not have to be quoted like most strings in GDScript.
## Variables:
`instance()` has been renamed to `instantiate()`.
## Exports:
Switch to @export; if you have a type hint in parentheses and no default value, add the hint at the end after a colon, like so:
@export var Speed: float
You *must* provide a minimum amount of type information at the current time, by including a : hint value, or a default value representing the type.
If you have a range to limit your floats, switch to use @export_range(float, float);
If exporting a string or integer as an enum, switch to @export_enum(VALUE1, VALUE2, VALUE3) var Name
If exporting a named enum, switch to @export with hint at the end, e.g.:
enum weapon_type { Sword, Gun, SwordGun, Knife, KnifeGun}
@export var WeaponType: weapon_type
If exporting strings, you can force the editor to show a large textbox for your string with @export_multiline
You may use @export and @onready at the same time, just like before:
@export @onready var MaxHitPoints = 10 # See onready below for more
Other useful hints:
## Onready:
Switch to @onready
## Properties:
Properties now follow the form:
var hp_value: int:
hp_value = value
return hp_value
## Tweens:
Tweens are not nodes anymore! These are references, similar to timers you create with the scene tree.
var sprite = $Sprite
var tween := get_tree().create_tween() # can omit get_tree() if you wish; this will bind the tween to the node, see below for more
tween.tween_property(sprite, "position", sprite.position + Vector2(16.0, 16.0), 1.0)
# Object to tween, nodepath to property to tween, end goal for the value, amount of time to take in seconds
There is also tween_interval, which waits for a given amount of time before tweening (similar to a timer), tween_callback, which does
the same thing as interpolate_callback, and tween_method, which calls a method on an object with an argument that goes from the initial
value to the final value, or in other words the same as interpolate_method.
You can chain tweening methods together - call one after the other, like so:
// above
tween.tween_property(sprite, "position", sprite.position + Vector2(16.0, 16.0), 1.0)
tween.tween_property(sprite, "rotation", sprite.rotation + deg2rad(180.0), 1.0)
### Tween binding:
A tween can be bound to a node so that if the node is either not in the tree or paused, it will affect the tween as well.
### Tween looping:
Tweens can have any number of loops by using the set_loops(loops) method; 0 will cause infinite loops.
You can stop/reset, pause, resume, or destroy a tween
Tweens can run in parallel with tween.parallel()
Similarly, tweens can have all subsequent tween calls be parallel after calling Tween.set_parallel(true)
You can use tween.set_ease() and tween.set_trans() to set the easing function and transition type respectively
You can use as_relative() to make the final value be relative:
// Equivalent to above
tween.tween_property(sprite, "position", Vector2(16.0, 16.0), 1.0).as_relative()
To consistently move the same amount from one spot to the next, such as grid-based movement, you can do the above and set the number of loops.
# Yield/await:
No more yields, only awaits! Ultimately, this comes down to:
yield(get_tree(), "idle_frame")
await get_tree().process_frame
Similarly, to await a common signal, you might do something like:
await $AnimationPlayer.animation_finished
# Signals:
For yielding/awaiting signals, see yield/await above.
Note that signals are now first-class language features, meaning you no longer rely on strings for them. That means you can swap any connect calls like this:
button.connect("pressed", self, "_on_button_pressed")
button.pressed.connect(_on_button_pressed) # Feel the power!
button.connect("pressed", self, "_on_button_pressed", [button], CONNECT_DEFERRED)
button.pressed.connect(_on_button_pressed.bind(button)), CONNECT_DEFERRED)
`button.connect("pressed", other_object, "on_button_pressed", [button], CONNECT_DEFERRED)`
`button.pressed.connect(other_object.on_button_pressed.bind(button)), CONNECT_DEFERRED)`
This also changes how you have to call to emit signals. It is now:
signal_name.emit(arg1, arg2, etc_arg)
## Functions:
Functions are now also first-class language features. That means you can use lambda expressions, which are concise bits of code representing a function, like so:
var _current_value = 0
@onready var _button = $Button
@onready var _text = $LineEdit
func _ready():
_button.pressed.connect(func(): _current_value += float(_text.text)) # Convert to a float and add to the current value to place a bet
It's important to note that just because you *can* add a lambda expression, you probably shouldn't always do it. It is often much cleaner to make a separate method,
like in 3.x, and attach it instead of a lambda.
## Resources:
If you need to load a resource in a script, when you drag the resource into the script in order to get the exact path, you can hold down ctrl while doing so and have it automatically place the preload.
# Nodes:
YSort node removed, functionality moved to Node2D
### Renamed nodes:
Spatial → Node3D
GIProbe → VoxelGI
BakedLightmap → LightmapGI
Light2D -> PointLight2D
VisibilityNotifier2D -> VisibleOnScreenNotifier2D (this removes Viewport-related signals)
VisibilityNotifier3D -> VisibleOnScreenNotifier3D (this removes Viewport-related signals)
VisibilityEnabler2D -> VisibleOnScreenEnabler2D
VisibilityEnabler3D -> VisibleOnScreenEnabler3D
The Pool*Array nodes have been renamed to Packed*Array, and instead of being passed by value, they are now passed by reference.
## Physics changes:
is renamed to:
The physics queries now have to have parameters created and passed to the various ray functions, like so:
var params =
params.from = first_pos = second_pos
params.exclude = obstacles
var result = space_state.intersect_ray(params)
RayCast2D.cast_to -> target_position
File dropping functionality is now moved from SceneTree (3.x) to Viewport (4.x).
Also it doesn't emit the screen argument anymore.
# 3.x
func _ready() -> void:
get_tree().connect("files_dropped", self, "_on_files_dropped")
func _on_files_dropped(files: PoolStringArray, screen: int) -> void:
# 4.x
func _ready() -> void:
func on_files_dropped(files: PackedStringArray) -> void:
File is now FileAccess and open() is now a static method that returns such object. To then check for errors, call file_access.get_error()
## KinematicBody2D:
KinematicBody2D has been renamed to CharacterBody2D, and a bunch of features have been added to it, such as:
- A new template script, that already implements basic movement and jumping for a 2d-platform style game
- Moving is now built-in to the class, which contains lots of properties to define how the movement works
- move_and_slide now no longer takes any parameters, and uses built-in velocity to control where it is going.
var velocity = Vector2.ZERO
func _process(delta):
velocity = velocity.move_towards(direction*200)
velocity = move_and_slide(velocity)
func _process(delta)
velocity = velocity.move_towards(direction*200)
- It has built-in collision including slopes and moving platforms
CollisionObject2D: The type of the first parameter of the method _input_event() was changed from Object to Viewport. (This may only matter if static typing is being used.)
CollisionObject3D (renamed from CollisionObject): The type of the first parameter of the method _input_event() was changed from Object to Camera3D. (This may only matter if static typing is being used.)
String constructor String(int) removed. Suggested replacement: global method str().
""" triple quote multi line string can no longer be used as comment
In Godot 3, the modulate would be independent of the shader material and it would affect whatever the shader material produced in COLOR. However, in Godot 4, modulate is the "input" of the shader material and whatever is written to COLOR in the shader will be the final result, see: godotengine/godot#49781
Godot 3's FuncRef
was replaced & extended with
Godot 4's Callable
Texture get_data renamed to Texture2D get_image
3.x Image get_rect renamed in 4 to get_region
3.x Image create
var img =
img.create(width, height, false, Image.FORMAT_RGBA8)
4.x Image create
var img = Image.create(width, height, false, Image.FORMAT_RGBA8)
Engine.editor_hint is now Engine.is_editor_hint()
EditorImportPlugin, and probably most other plugin methods, have renamed all the require function overloads. In most cases, though, it should just be adding and underscore _ to the function names. For instance, import(parameters) becomes _import(parameters).
OS.get_window_safe_area() -> DisplayServer.get_display_safe_area()
Godot 4 beta 13 removed the global class list cache altogether from project.godot into a separate file in the .godot folder. Also removed retrieving the global class list from ProjectSettings. The new way to retrieve that is:
var config =
var classes = config.get_value('', 'list', [])
# Shaders:
SCREEN_TEXTURE is no longer available in shaders; instead, you do this at the top of your shader where you define your uniforms:
`uniform sampler2D SCREEN_TEXTURE: hint_screen_texture, filter_linear_mipmap;`
When using a ShaderMaterial, shader_param has been renamed to shader_parameter, e.g.:
`create_tween().tween_property(target.material, "shader_param/transparency", 1.0, 2.0)`
changes to
`create_tween().tween_property(target.material, "shader_parameter/transparency", 1.0, 2.0)`
Some further changes to shader naming:
depth_draw_alpha_prepass -> depth_draw_opaque
hint_aniso -> hint_anisotropy
hint_black -> hint_default_black
hint_black_albedo -> hint_default_black
hint_albedo -> source_color
hint_color -> source_color
hint_white -> hint_default_white
has become:
## Logical tests:
You can no longer test an object's nullness simply by saying "if object:", nor with `not` like so: "if not object:". There's no longer any implicit conversion between these.
The end of array.slice() is no longer inclusive of the final element, as it has been changed to exclusive ending.
Change log:
1/27 - initial add
1/27 - Bitron adds yield/await, signals
1/27 - Kyle adds functions
1/29 - Kyle adds instanciate(), preload trick
1/29 - Kobewi adds new example for connecting in a deferred fashion to signals
1/30 - Kyle adds YSort and other node info, add property syntax
2/1 - Dardan adds Pool* vs Packed* array info
2/20 - Deanut adds Physics-related changes
3/27 - Deanut adds further examples for await changes
9/13 - Kyle adds renames
9/28 - Bitbrain adds File changes, and Fenix adds file dropping changes.
9/29 - Kyle adds some multiplayer love with the @rpc annotation information
9/29 - Zemeio adds KinematicBody2D to CharacterBody2D updates
11/27 - hsandt updates signal connect examples
1/2/23 - DaveTheOldCoder adds CollisionObjectXD updates! As well as String constructor.
- filipworksdev adds triple quote comment note
- bitbrain adds modulate-related changes
- Structed adds note about FuncRef
- afk-mario adds notes about Texture's get_data change and get_rect on 3.x Image, and finally creating an image by code.
1/9/23 - sztrovacsek92 adds shader editor info for hint_color vs source_color.
2/8/23 - dploeger adds editor_hint changes
- chucklepie adds some other VisibleOnScreenNotifier2D changes and EditorImportPlugin changes
- kristijandraca adds change to OS for window safe area to DisplayServer
- Structed adds global class list cache changes
- Bitbrain adds u-seful shader updates
- nonunknown adds multiple shader-related renames
- Added link to new beginner-friendly guide that I've created to help beginners understand the differences
3/9/23 - naturally-intelligent added change for OS.has_touchscreen_ui_hint(), and changes in logical tests
- Kyle adds some formatting to make the beginnger conversion guide more obvious
- afk-mario adds RayCast2D changes
- ChronoDK adds array slice changes
Copy link

Texture get_data renamed to Texture2D get_image

Copy link

Copy link

3.x Image create

var img =
img.create(width, height, false, Image.FORMAT_RGBA8)

4.x Image create

var img = Image.create(width, height, false, Image.FORMAT_RGBA8)

Copy link

In the Shader Editor hint_color got renamed to source_color.

Copy link

dploeger commented Jan 9, 2023

Engine.editor_hint is now Engine.is_editor_hint()

Copy link

VisibilityNotifier2D -> VisibleOnScreenNotifier2D, etc now have no viewport signals (clearly by the name change). Don't know whether functionality will be the same with notifier triggering from viewports as I haven't fixing all my code yet....

func _on_VisibilityNotifier2D_viewport_exited(_viewport: Viewport) -> void:

instead need to use:

func _on_visible_on_screen_notifier_2d_screen_exited():

Copy link

EditorImportPlugin, and I expect most other plugin methods have renamed all the require function overloads, however it should be a simply case of adding _ to the function names.

int import ( String source_file, String save_path, Dictionary options, Array platform_variants, Array gen_files ) virtual


int _import ( String source_file, String save_path, Dictionary options, String[] platform_variants, String[] gen_files ) virtual const

Copy link

OS.get_window_safe_area() -> DisplayServer.get_display_safe_area()

Copy link

Structed commented Jan 20, 2023

Godot 4 Beta 13 removed the global class list cache from project.godot into a separate file beneath the .godot folder.
This also removed the possibility to retrieve the global class list from the ProjectSettings:

var classes = ProjectSettings.get_setting("_global_script_classes")

The new way of retrieving the global class list is via the new ClassDB class:
It would have been cool if it would use this syntax:

var classes = ClassDB.get_class_list()

Alas, it doesn't: godotengine/godot#71744

The actual new way of retrieving the global class list is:

var config =
var classes = config.get_value('', 'list', [])

This will yield exactly the same dictionary as in Godot 3.5

Copy link

SCREEN_TEXTURE is no longer available in shaders. Instead, you have to do this on top of your shader where you define your uniforms:

uniform sampler2D SCREEN_TEXTURE: hint_screen_texture, filter_linear_mipmap;

Copy link

When using ShaderMaterial, shader_param is now called shader_parameter (important when specifying tween paths) e.g.:

create_tween().tween_property(target.material, "shader_param/transparency", 1.0, 2.0)

changes to

create_tween().tween_property(target.material, "shader_parameter/transparency", 1.0, 2.0)

Copy link

Some Shader changes:


Copy link

translates to:

Copy link

You can no longer use the not keyword to check if a String is empty

Which removes some ducking ability on variables, a real annoyance for me...

Copy link

naturally-intelligent commented Feb 8, 2023

Checking if a file exists, used to be something along the lines of:

static func file_exists(file):

Now, I need to do something a bit more complex:

static func file_exists(file):
	if ResourceLoader.exists(file):
		return true
	if file.find('://') != -1:
		var split = file.split('://')
		var dir =[0]+'://')
		return dir.file_exists(split[1])
	return false

I know this is a more obscure upgrade, but if anyone has a better solution please share it!

Copy link

Gnumaru commented Feb 8, 2023

I know this is a more obscure upgrade, but if anyone has a better solution please share it!

Copy link

do somebody know what name replaced the GDScriptFunctionState class name ?

Copy link

Gnumaru commented Feb 12, 2023

GDScriptFunctionState is not exposed to gdscript anymore, but it is still possible to obtain one by using

Screenshot (209)

extends Node
func asdf():
	print('= asdf before')
	await get_tree().process_frame
	print('= asdf middle')
	await get_tree().process_frame
	print('= asdf after')
func _ready():
	print('===== BEGIN')
	var a
#	a = asdf() # parser error: function asdf() is a coroutine etc
#	a = # does not give a parser error, but yields a runtime error: trying to call an async function without await
	a = call('asdf') # does not yield any error
	print('_ ready before')
	print('_ ready middle')
	await a
	print('_ ready after')
	print(a.get_class()) # GDScriptFunctionState
	print(ClassDB.get_parent_class(a.get_class())) # RefCounted
	var r =
	print(a.get_method_list().map(func(i):return not r.has_method(i))) # ["resume", "is_valid", "_signal_callback", "_notification", "_set", "_get", "_get_property_list", "_property_can_revert", "_property_get_revert", "_init", "_to_string"]
	print(a.get_property_list().map(func(i):return not i in r and not i in ['RefCounted', 'GDScriptFunctionState'])) # []
	print('===== END')

Copy link

daveTheOldCoder commented Feb 21, 2023

I recently found this page in the official documentation:

Does that supercede this Godot 4.0 Migration/Upgrade guide? I.e., is it better to submit new information at, rather than here?

Copy link

@daveTheOldCoder ideally, anything that is on here but not on yet should be "ported over" - however, this document is still a great place to collect information.

Copy link

Raycast2D cast_to renamed to target_position

Copy link

ChronoDK commented Mar 9, 2023

Array slice method "end" is exclusive. It was inclusive before.

Copy link

hsandt commented Mar 9, 2023

Maybe we should start the gist by mentioning and, to follow a similar structure, that you should --convert-3to4 to auto-convert most of the code (under VCS to ensure control of changes), then finish the job manually for the least obvious changes?

Otherwise there will be a lot of renames to fill according to

So I think we should focus on the big changes may be automated but still important for the user to know about (conceptual change, very common symbols, etc.), the changes that need manual work (when the new code we need depends on user intention/design, when function signature changes, etc.) and in particular things not already mentioned in the official upgrade guide (so Godot devs can easily find just the extra tips and merge them fast later).

I see a lot of info not in the official guide so I'll point the devs at this page so they can gradually add such info.

Copy link

@hsandt - that's probably why I included this line near the very beginning:

Which includes the step-by-step conversion tutorial that I've put together, including the very link you mentioned.

Copy link

hsandt commented Mar 16, 2023

I saw the new gist, it looks good!

Copy link

I saw the new gist, it looks good!

It's not new, been there a while. 😉

Copy link

hsandt commented Mar 18, 2023

Then: I saw the new link to the old gist, it looks good!

Btw I mentioned this page on godotengine/godot-docs#5121, will see from times to times if we got nice tips to include the official doc.

Copy link

Gnumaru commented Mar 21, 2023

The quit confirmation editor setting apparently doesn't exist anymore. In was previously in "Editor >> Editor Settings >> Interface >> Editor >> Quit Confirmation" but it's no longer there nor there seems to be any equivalent.

This doesn't impact migrating projects from 3 to 4 but is something worth knowing.

Copy link

The quit confirmation feature was intentionally removed. There was an explanation somewhere. If I find it, I'll post it or a link.

Copy link

Looks like ShortCut has been renamed Shortcut - the letter 'c' became lowercase, causing compiler errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment