强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Godot 4 GDScript 教程 / 29 - 从 Godot 3 迁移到 Godot 4

29 - 从 Godot 3 迁移到 Godot 4

Godot 4 是一次重大升级,涉及 API、语法和工作流的全面变化。本文提供完整的迁移指南,帮助你高效地将 Godot 3 项目迁移到 Godot 4。


API 变更对照表

节点重命名

Godot 3Godot 4说明
SpatialNode3D3D 空间节点基类
KinematicBodyCharacterBody3D角色控制器
RigidBodyRigidBody3D刚体
StaticBodyStaticBody3D静态碰撞体
AreaArea3D区域检测
CameraCamera3D3D 相机
MeshInstanceMeshInstance3D网格实例
LightLight3D3D 光源基类
DirectionalLightDirectionalLight3D平行光
OmniLightOmniLight3D点光源
SpotLightSpotLight3D聚光灯
NavigationNavigationRegion3D导航区域
NavigationAgentNavigationAgent3D导航代理
SpriteSprite2D2D 精灵
Sprite3DSprite3D3D 精灵(名称不变)
Position2DMarker2D2D 标记点
Position3DMarker3D3D 标记点
YSortNode2D + Y Sort 属性Y 排序
VisibilityNotifierVisibleOnScreenNotifier3D可见性通知
AnimatedSpriteAnimatedSprite2D动画精灵
CPUParticlesCPUParticles3DCPU 粒子
ParticlesGPUParticles3DGPU 粒子

方法与属性重命名

Godot 3Godot 4
get_tree().change_scene()get_tree().change_scene_to_file()
get_tree().pausedget_tree().paused (不变)
OS.window_sizeDisplayServer.window_get_size()
OS.window_fullscreenDisplayServer.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() % nrandi_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 3Godot 4
颜色提示hint_colorsource_color
纹理提示hint_albedosource_color
世界坐标VERTEX (world)顶点函数中变换
光照模型light() 函数相同,API 变化
渲染模式blend_mixblend_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修复 Shader1 天
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()

💡 扩展阅读