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()
💡 扩展阅读