-
-
Save WolfgangSenff/168cb0cbd486c8c9cd507f232165b976 to your computer and use it in GitHub Desktop.
## 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: | |
## https://gist.github.com/WolfgangSenff/0a9c1d800db42a9a9441b2d0288ed0fd | |
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 https://ko-fi.com/kyleszklenski - 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 | |
Object constant CONNECT_ONESHOT -> CONNECT_ONE_SHOT | |
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: | |
https://docs.godotengine.org/en/latest/tutorials/scripting/gdscript/gdscript_exports.html | |
## Onready: | |
Switch to @onready | |
## Properties: | |
Properties now follow the form: | |
var hp_value: int: | |
set(value): | |
hp_value = value | |
hpvalue_changed.emit(hp_value) | |
get: | |
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") | |
becomes: | |
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") | |
to: | |
button.pressed.connect(_on_button_pressed) # Feel the power! | |
Further: | |
button.connect("pressed", self, "_on_button_pressed", [button], CONNECT_DEFERRED) | |
becomes: | |
button.pressed.connect(_on_button_pressed.bind(button)), CONNECT_DEFERRED) | |
Additionally: | |
`button.connect("pressed", other_object, "on_button_pressed", [button], CONNECT_DEFERRED)` | |
becomes: | |
`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: | |
Physics2DDirectSpaceState | |
is renamed to: | |
PhysicsDirectSpaceState2D | |
The physics queries now have to have parameters created and passed to the various ray functions, like so: | |
var params = PhysicsRayQueryParameters3D.new() | |
params.from = first_pos | |
params.to = 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: | |
get_viewport().files_dropped.connect(on_files_dropped) | |
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. | |
Before: | |
var velocity = Vector2.ZERO | |
func _process(delta): | |
velocity = velocity.move_towards(direction*200) | |
velocity = move_and_slide(velocity) | |
Now: | |
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 = Image.new() | |
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 = ConfigFile.new() | |
config.load("res://.godot/global_script_class_cache.cfg") | |
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: | |
ALPHA_SCISSOR -> ALPHA_SCISSOR_THRESHOLD | |
CAMERA_MATRIX -> INV_VIEW_MATRIX | |
INV_CAMERA_MATRIX -> VIEW_MATRIX | |
NORMALMAP -> NORMAL_MAP | |
NORMALMAP_DEPTH -> NORMAL_MAP_DEPTH | |
TRANSMISSION -> BACKLIGHT | |
WORLD_MATRIX -> MODEL_MATRIX | |
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 | |
OS.has_touchscreen_ui_hint() | |
has become: | |
DisplayServer.is_touchscreen_available() | |
## 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 |
The class Reference
has been renamed to RefCounted
A few things I've noticed so far:
Node.filename
is nowNode.scene_file_path
AnimatedSprite2D.frames
is nowAnimatedSprite2D.sprite_frames
.- My input map in project settings was completely reset and I had to set it all again, for some reason.
- Stretch modes changed (and the docs still use the 3.0 terminology it seems). What used to be
2d
is now calledcanvas_items
, I had to change it manually.- Textures are now told to repeat via the
CanvasItem
section of the node they belong to. I'm updating them all manually, not sure if there's a better way.
For this, you can change that under I believe it's Textures in Project Settings. Leave the value on the node set to inherit, and change it in the Textures.
RectangleShape2D
no longer has a property calledextents
, but rathersize
. Instead of the half-width/height, it stores the full width and height. The built-in 3-to-4 converter correctly renamesextents
tosize
in scene files, but it keeps the numbers the same, which results in every singleRectangeShape2D
being half the intended size. Circles seem unaffected, but I haven't checked any other shapes. I think you can at least speed up the process by searching fortype="RectangleShape2D"
across all tscn files. Not sure if there's a better way, but it's better than opening scene files one-by-one.
Great callouts, will try to add them when I have time, thanks!
set_as_toplevel
set_as_toplevel -> set_as_top_level
#or just set the value:
top_level = true | false
I have also noticed tool
has become @tool
and export
has become @export
https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_exports.html
From @hsandt :
Otherwise there will be a lot of renames to fill according to https://github.com/godotengine/godot/blob/master/editor/renames_map_3_to_4.cpp
How did I miss this? Yes this is exactly what is needed.
They renamed speed
to velocity
for example like this one for InputEventMouseMotion
: godotengine/godot#56322
stepify
> snapped
The cartesian2polar and polar2cartesian built-in functions are gone. I haven't found an alternative built-in function or a static method on Vector2 to do that. I'm pretty sure there must be something somewhere, but I couldn't find it. yet
Also, the ord(String)->int:
built-in function which gives you an unicode codepoint of a given one character string is gone (as a built-in function). Now we should use the String instance function unicode_at(int)->int:
which gives you the unicode code point at the given character index.
I used the VisibilityNotifier2D
to make the owner invisible or visible.
But the new VisibleOnScreenNotifier2D
only works if it is visible itself, so it's a nasty trap if it makes its owner invisible and thus itself too.
I used the
VisibilityNotifier2D
to make the owner invisible or visible.
But the newVisibleOnScreenNotifier2D
only works if it is visible itself, so it's a nasty trap if it makes its owner invisible and thus itself too.
I suppose that by "owner" you actually meant "parent". If this is happening I think this should actually be a bug, not the intended behavior. Meanwhile, instead of hiding the parent, you could set "modulate.a8 = 1", which will basically make it humanly impossible to see BUT will count as visible and allow VisibleOnScreenNotifier2D to work as expected
Making many objects that are not currently on the screen invisible saves a lot of computing time, at least in Godot 3. So the invisibility is just a side effect. :)
But I suspect the culling under Godot 4 will make this technique obsolete anyway.
The behavior of the VisibleOnScreenNotifier2D is actually not a bug. It is mentioned in the documentation:
Note: VisibleOnScreenNotifier2D uses the render culling code to determine whether it's visible on screen, which also means that its CanvasItem.visible must be true to work correctly.
I knew about the impact of hiding offscreen nodes on godot 3 but I haven't noticed until now that the expected behavior of VisibleOnScreenNotifier2D is different from the old VisibilityNotifier2D. I've just made some tests and indeed there is no longer need to hide offscreen nodes, when the canvas item is offscreen it does not affect rendering
2023-08-22_17-11-52.mp4
Don't mind the weird bug where the icon is not rendered. If I add at least one icon directly in the scene and save it it gets rendered (but the performance is even worse when the icons are on screen)
2023-08-22_17-21-27.mp4
Cool, that's good news! :)
A few things I had to deal with during the upgrade:
-
The
RigidBody2D
has thelinear_damp
property. Under Godot 3 and 4 it is stated that for linear damping normally the project default value applies. And as soon as you enter something in linear_damp, it is automatically added to the default value.
But under Godot 3 it is not added, it replaces the default value. Under Godot 4 everything works as described (and you can also choose Replace there if you want). -
With
Area2D
there are problems with themasks
andlayers
under Godot 3.5. If Area2D is set to monitoring only, you should only have to set mask bits. But sometimes I had to set layer bits to make it work.
Anyway, under Godot 4 everything seems to be corrected, so you have to swap bits here and there. -
In
RigidBody2D
there is a methodapply_impulse
. Under Godot 3 you first enter an offset and then an impulse in the arguments. Under Godot 4 it is the other way round. But because both arguments are of type Vector2, you don't notice that immediately.
Under Godot 3 there is the class Physics2DDirectSpaceState or under Godot 4 the class PhysicsDirectSpaceState2D. Both have the method intersect_ray
. This method can be passed a list of objects that the ray ignores, called exclude
.
Under Godot 3 you can specify objects or RIDs. Under Godot 4 ONLY RIDs.
The AnimationPlayer
has the method stop()
. If you don't pass a parameter, the same thing happens in Godot 3 and 4.
However, if you passed the parameter (bool) in Godot 3, you have to invert it in Godot 4 (true <-> false).
Godot 3: void stop ( bool reset=true )
Godot 4: void stop ( bool keep_state=false )
Currently upgrading a large project of mine to Godot 4. Around 10k lines of code, 100+ scenes. Some things I've run into into, that are not detailed anywhere:
FastNoiseLite
, which is the replacement forOpenSimplexNoise
, doesn't have theperiod
property. Since it is a completely different implementation, there really isn't a replacement for it.Input.get_joy_axis()
now takes aJoyAxis
enum instead of anint
.Engine.set_target_fps()
is gone, replaced byEngine.max_fps
.Node
now has aready
property, so if you already have aready
property you'll get an error if you redefine it.ResourceLoader
works completely different now;load_threaded_request()
does not return aResourceLoader
anymore, instead it returns anError
. Rather thanpoll()
, status can be checked byload_threaded_get_status()
, and the loaded resource can then be extracted viaload_threaded_get()
.PhysicsDirectSpaceState2D.intersect_point()
now takes in aPhysicsPointQueryParameters2D
, rather than the parameters themselves.- The
current
property is gone fromCamera2D
, instead it's replaced bymake_current()
to set, andis_current()
to get. - Array
remove()
is nowremove_at()
, same functionality. - One big change is that parent engine methods such as
_ready
,_process
etc are not called automatically anymore; you have manually call them usingsuper._ready()
. This one had me scratching my head for a while, as it is completely different from Godot 3. - Some of your nodes may get renamed to 3D, even though they are not 3D variants. For example, I had a node named
Camera
, which got renamed toCamera3D
, even though it is aCamera2D
node. This only affects the name, the node type is still the same. - Many of my collision shapes were resized, I had to resize them back to they were.
KinematicCollision2D
(returned byCharacterBody2D.get_last_slide_collision()
does not have thecollider
property anymore; it is replaced byget_collider()
.- Some of my nodes' process modes were migrated wrong; for example, a node with the
Process
mode was migrated to thePausable
mode, rather thanAlways
. AnimatedSprite2D
'splay()
method has been changed; it now hascustom_speed
andfrom_end
properties. To play backwards you can now useplay_backwards()
.- My translations were broken; I had to reimport the
text.csv
file, delete thetext.*.translation
file (which was then regenerated by Godot), and then re-add the.translation
file under Localization in the Project Settings. - Like @andrejp88 mentioned above, stretch modes have changed; for example some of my
TextureRect
's went from Scale to Tile. - The
Vector2.angle_to_point
method was changed; it was wrong before, but rather than deprecating the method and creating a new one the method was silently fixed. See thread here.
Will add more as I encounter them.
@Gnumaru Yep, was a gotcha for me as well. Not mentioned anywhere! Might be worth creating a bug report for it.
OS.get_time_zone_info()
has moved to Time.get_time_zone_from_system()
FYI.
Just so I dont forget: The "Control" node had the "rect_" preffix removed from these properties: rect_global_position, rect_pivot_offset, rect_position, rect_rotation, rect_scale, rect_size,
Also, rect_clip_content has been renamed to clip_contents (with "s" in the end) and rect_min_size has been renamed to custom_minimum_size
A few things I've noticed so far:
Node.filename
is nowNode.scene_file_path
AnimatedSprite2D.frames
is nowAnimatedSprite2D.sprite_frames
.2d
is now calledcanvas_items
, I had to change it manually.CanvasItem
section of the node they belong to. I'm updating them all manually, not sure if there's a better way.RectangleShape2D
no longer has a property calledextents
, but rathersize
. Instead of the half-width/height, it stores the full width and height. The built-in 3-to-4 converter correctly renamesextents
tosize
in scene files, but it keeps the numbers the same, which results in every singleRectangeShape2D
being half the intended size. Circles seem unaffected, but I haven't checked any other shapes. I think you can at least speed up the process by searching fortype="RectangleShape2D"
across all tscn files. Not sure if there's a better way, but it's better than opening scene files one-by-one.