Godot 4 GDScript 教程 / 29 - 从 Godot 3 迁移到 Godot 4
29 - 从 Godot 3 迁移到 Godot 4
Godot 4 是一次重大升级,涉及 API、语法和工作流的全面变化。本文提供完整的迁移指南,帮助你高效地将 Godot 3 项目迁移到 Godot 4。
API 变更对照表
节点重命名
| Godot 3 |
Godot 4 |
说明 |
Spatial |
Node3D |
3D 空间节点基类 |
KinematicBody |
CharacterBody3D |
角色控制器 |
RigidBody |
RigidBody3D |
刚体 |
StaticBody |
StaticBody3D |
静态碰撞体 |
Area |
Area3D |
区域检测 |
Camera |
Camera3D |
3D 相机 |
MeshInstance |
MeshInstance3D |
网格实例 |
Light |
Light3D |
3D 光源基类 |
DirectionalLight |
DirectionalLight3D |
平行光 |
OmniLight |
OmniLight3D |
点光源 |
SpotLight |
SpotLight3D |
聚光灯 |
Navigation |
NavigationRegion3D |
导航区域 |
NavigationAgent |
NavigationAgent3D |
导航代理 |
Sprite |
Sprite2D |
2D 精灵 |
Sprite3D |
Sprite3D |
3D 精灵(名称不变) |
Position2D |
Marker2D |
2D 标记点 |
Position3D |
Marker3D |
3D 标记点 |
YSort |
Node2D + Y Sort 属性 |
Y 排序 |
VisibilityNotifier |
VisibleOnScreenNotifier3D |
可见性通知 |
AnimatedSprite |
AnimatedSprite2D |
动画精灵 |
CPUParticles |
CPUParticles3D |
CPU 粒子 |
Particles |
GPUParticles3D |
GPU 粒子 |
方法与属性重命名
| Godot 3 |
Godot 4 |
get_tree().change_scene() |
get_tree().change_scene_to_file() |
get_tree().paused |
get_tree().paused (不变) |
OS.window_size |
DisplayServer.window_get_size() |
OS.window_fullscreen |
DisplayServer.window_get_mode() |
OS.get_screen_size() |
DisplayServer.screen_get_size() |
OS.alert() |
OS.alert() (不变) |
Input.is_action_just_pressed() |
Input.is_action_just_pressed() (不变) |
Input.get_action_strength() |
Input.get_action_strength() (不变) |
rand_range(a, b) |
randf_range(a, b) |
randi() % n |
randi_range(0, n-1) or randi() % n |
stepify(a, b) |
snapped(a, b) |
lerp_angle(a, b, t) |
lerp_angle(a, b, t) (不变) |
deg2rad() |
deg_to_rad() |
rad2deg() |
rad_to_deg() |
instance() |
instantiate() |
add_child(node, true) |
add_child(node) + node.owner = ... |
is_network_master() |
is_multiplayer_authority() |
rpc_id(id, ...) |
method.rpc_id(id, ...) |
yield(obj, signal) |
await obj.signal |
connect(s, t, m) |
s.connect(callable) |
GDScript 2.0 语法迁移
类型声明
# Godot 3 — 类型声明可选,语法不统一
var health = 100
var speed: float = 5.0
func take_damage(amount):
health -= amount
# Godot 4 — 类型声明统一,更严格
var health: int = 100
var speed: float = 5.0
func take_damage(amount: int) -> void:
health -= amount
@export 替代 export
# Godot 3
export(int) var health = 100
export(float, 0, 100, 0.1) var speed = 5.0
export(String, FILE, "*.json") var config_path = ""
export(Array, String) var tags = []
export(NodePath) var target_path
# Godot 4
@export var health: int = 100
@export_range(0, 100, 0.1) var speed: float = 5.0
@export_file("*.json") var config_path: String = ""
@export var tags: Array[String] = []
@export var target_path: NodePath
@onready 替代 onready
# Godot 3
onready var sprite = $Sprite
onready var timer = $Timer
# Godot 4
@onready var sprite: Sprite2D = $Sprite2D
@onready var timer: Timer = $Timer
信号语法变更
# Godot 3 — 信号声明
signal health_changed(old_value, new_value)
signal died()
# Godot 4 — 信号声明(相同)
signal health_changed(old_value: int, new_value: int)
signal died()
# Godot 3 — 连接信号
connect("health_changed", self, "_on_health_changed")
emit_signal("health_changed", old_health, new_health)
# Godot 4 — 连接信号
health_changed.connect(_on_health_changed)
health_changed.emit(old_health, new_health)
# Godot 3 — 断开信号
disconnect("health_changed", self, "_on_health_changed")
# Godot 4 — 断开信号
health_changed.disconnect(_on_health_changed)
await 替代 yield
# Godot 3
func delayed_action():
yield(get_tree().create_timer(2.0), "timeout")
print("2 秒后执行")
func load_data():
var result = yield(http_request, "request_completed")
print(result)
# Godot 4
func delayed_action():
await get_tree().create_timer(2.0).timeout
print("2 秒后执行")
func load_data():
var result = await http_request.request_completed
print(result)
lambda 与 Callable
# Godot 3 — 匿名函数不支持,需要写具名函数
func _ready():
get_tree().create_timer(2.0).connect("timeout", self, "_on_timer_timeout")
func _on_timer_timeout():
print("计时结束")
# Godot 4 — 支持 Lambda
func _ready():
get_tree().create_timer(2.0).timeout.connect(func():
print("计时结束")
)
# 带参数的 Lambda
health_changed.connect(func(old_val, new_val):
print("生命值从 %d 变为 %d" % [old_val, new_val])
)
物理 API 变更
CharacterBody3D (原 KinematicBody)
# Godot 3 — KinematicBody
extends KinematicBody
var velocity = Vector3.ZERO
const GRAVITY = -9.8
func _physics_process(delta):
velocity.y += GRAVITY * delta
var input = Input.get_vector("left", "right", "forward", "back")
velocity.x = input.x * speed
velocity.z = input.y * speed
velocity = move_and_slide(velocity, Vector3.UP)
# Godot 4 — CharacterBody3D
extends CharacterBody3D
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
func _physics_process(delta):
# 重力
if not is_on_floor():
velocity.y -= ProjectSettings.get_setting("physics/3d/default_gravity") * delta
# 跳跃
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = JUMP_VELOCITY
# 移动
var input_dir = Input.get_vector("left", "right", "forward", "back")
var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED * delta)
velocity.z = move_toward(velocity.z, 0, SPEED * delta)
move_and_slide()
⚠️ 注意:move_and_slide() 在 Godot 4 中不再需要传入 velocity 参数,它直接使用节点的 velocity 属性。
碰撞检测变更
# Godot 3
func _physics_process(delta):
var collision = move_and_collide(velocity * delta)
if collision:
velocity = velocity.slide(collision.normal)
# Godot 4
func _physics_process(delta):
var collision = move_and_collide(velocity * delta)
if collision:
velocity = velocity.slide(collision.get_normal())
动画系统变更
AnimationPlayer
# Godot 3
$AnimationPlayer.play("run")
$AnimationPlayer.connect("animation_finished", self, "_on_anim_finished")
# Godot 4
$AnimationPlayer.play("run")
$AnimationPlayer.animation_finished.connect(_on_anim_finished)
AnimationTree
# Godot 3
var state_machine = $AnimationTree.get("parameters/playback")
state_machine.travel("run")
# Godot 4 — AnimationMixer 替代 AnimationTree 的部分功能
@onready var anim_tree: AnimationTree = $AnimationTree
func _ready():
var state_machine = anim_tree.get("parameters/playback") as AnimationNodeStateMachinePlayback
state_machine.travel("run")
# Godot 4 新的动画回调
func _ready():
$AnimationPlayer.animation_finished.connect(_on_anim_finished)
资源迁移
.tres 文件格式变更
# Godot 4 可以自动导入 Godot 3 的 .tres 文件
# 但某些格式可能需要手动调整
# 常见需要修改的资源类型:
# - SpatialMaterial → StandardMaterial3D
# - ParticlesMaterial → ParticleProcessMaterial
# - DynamicFont → 项目设置中配置默认字体
# - ShaderMaterial 中的 shader 语法变更
Shader 迁移
// Godot 3
shader_type spatial;
render_mode unshaded;
uniform sampler2D texture_albedo : hint_albedo;
uniform vec4 color : hint_color;
void fragment() {
ALBEDO = texture(texture_albedo, UV).rgb * color.rgb;
}
// Godot 4
shader_type spatial;
render_mode unshaded;
uniform sampler2D texture_albedo : source_color;
uniform vec4 color : source_color;
void fragment() {
ALBEDO = texture(texture_albedo, UV).rgb * color.rgb;
}
| Shader 变更 |
Godot 3 |
Godot 4 |
| 颜色提示 |
hint_color |
source_color |
| 纹理提示 |
hint_albedo |
source_color |
| 世界坐标 |
VERTEX (world) |
顶点函数中变换 |
| 光照模型 |
light() 函数 |
相同,API 变化 |
| 渲染模式 |
blend_mix |
blend_mix (不变) |
自动迁移工具
Godot 4 项目转换器
# Godot 4 内置的项目转换器
# 编辑器 → 项目 → 工具 → 升级到 Godot 4
# 转换器会自动处理:
# ✅ 节点类型重命名
# ✅ API 方法重命名
# ✅ export → @export
# ✅ onready → @onready
# ✅ 部分信号连接语法
#
# 转换器不会处理:
# ❌ 复杂的信号连接逻辑
# ❌ 自定义 Shader 语法
# ❌ GDScript 2.0 类型系统
# ❌ 第三方插件兼容性
手动迁移脚本
# 使用 sed 批量替换节点名称
find . -name "*.gd" -exec sed -i 's/KinematicBody/CharacterBody3D/g' {} \;
find . -name "*.gd" -exec sed -i 's/Spatial/Node3D/g' {} \;
find . -name "*.gd" -exec sed -i 's/Sprite\b/Sprite2D/g' {} \;
# 替换导出语法
find . -name "*.gd" -exec sed -i 's/export(\(.*\))/@export/g' {} \;
find . -name "*.gd" -exec sed -i 's/onready var/@onready var/g' {} \;
# 替换方法名
find . -name "*.gd" -exec sed -i 's/\.instance()/.instantiate()/g' {} \;
find . -name "*.gd" -exec sed -i 's/rand_range/randf_range/g' {} \;
find . -name "*.gd" -exec sed -i 's/deg2rad/deg_to_rad/g' {} \;
find . -name "*.gd" -exec sed -i 's/rad2deg/rad_to_deg/g' {} \;
find . -name "*.gd" -exec sed -i 's/stepify/snapped/g' {} \;
常见陷阱与解决方案
陷阱 1:move_and_slide 参数变化
# ❌ Godot 3 写法在 Godot 4 中报错
velocity = move_and_slide(velocity, Vector3.UP)
# ✅ Godot 4 正确写法
move_and_slide()
# velocity 已经是 CharacterBody3D 的内置属性
陷阱 2:信号连接语法
# ❌ Godot 3 旧语法
$Button.connect("pressed", self, "_on_button_pressed")
# ✅ Godot 4 新语法
$Button.pressed.connect(_on_button_pressed)
陷阱 3:get_node 类型
# Godot 4 更严格的类型检查
# ❌ 可能在运行时崩溃
var sprite = $Sprite # 没有类型提示
sprite.rotation += 0.1 # 如果节点不存在会崩溃
# ✅ 安全做法
@onready var sprite: Sprite2D = $Sprite2D
# 或使用 get_node_or_null
var sprite = get_node_or_null("Sprite2D") as Sprite2D
if sprite:
sprite.rotation += 0.1
陷阱 4:类型转换
# Godot 4 更严格的类型转换
# ❌ 可能失败
var node = get_node("SomeNode") as CharacterBody3D # 如果类型不匹配返回 null
# ✅ 使用 is 检查
var node = get_node("SomeNode")
if node is CharacterBody3D:
var character = node as CharacterBody3D
character.velocity = Vector3.ZERO
迁移策略
渐进式迁移(推荐)
| 阶段 |
步骤 |
预计耗时 |
| 1 |
备份项目,创建 Git 分支 |
1 小时 |
| 2 |
运行自动迁移工具 |
2 小时 |
| 3 |
修复编译错误(节点重命名、API 变更) |
1-3 天 |
| 4 |
修复信号连接 |
1-2 天 |
| 5 |
修复 Shader |
1 天 |
| 6 |
测试核心功能 |
2-3 天 |
| 7 |
修复边缘情况 |
1-2 天 |
| 8 |
优化和清理 |
1-2 天 |
全量迁移(适合小项目)
# 1. 备份原始项目
cp -r my_game my_game_backup
# 2. 在 Godot 4 中打开项目
# 允许自动转换
# 3. 逐个修复错误
# 编辑器会标记所有错误
# 4. 全面测试
混合策略(适合大项目)
# 保留部分 Godot 3 脚本,逐步替换
# 使用兼容层:
# compatibility_layer.gd
# 封装 Godot 3 → 4 的 API 差异
static func move_and_slide_compat(body: CharacterBody3D, vel: Vector3, up: Vector3) -> Vector3:
body.velocity = vel
body.up_direction = up
body.move_and_slide()
return body.velocity
static func instance_scene(scene: PackedScene) -> Node:
return scene.instantiate()
💡 扩展阅读