Godot 3 GDScript 教程 / Godot 3 GDScript 教程(十三):场景切换与数据持久化
场景切换与数据持久化
场景切换决定了玩家在不同关卡、菜单之间的体验流畅度,而数据持久化确保了玩家的进度和设置在退出后得以保留。
场景切换 change_scene
# 基本切换
get_tree().change_scene("res://scenes/level_2.tscn")
# 带淡入淡出的切换
extends Node
var is_transitioning: bool = false
func change_scene_with_fade(scene_path: String, fade_time: float = 0.5):
if is_transitioning:
return
is_transitioning = true
var fade = ColorRect.new()
fade.color = Color(0, 0, 0, 0)
fade.set_anchors_and_margins_preset(Control.PRESET_WIDE)
get_tree().root.add_child(fade)
var tween = Tween.new()
add_child(tween)
# 淡出
tween.interpolate_property(fade, "color:a", 0.0, 1.0, fade_time)
tween.start()
yield(tween, "tween_all_completed")
get_tree().change_scene(scene_path)
yield(get_tree(), "idle_frame")
# 淡入
tween.interpolate_property(fade, "color:a", 1.0, 0.0, fade_time)
tween.start()
yield(tween, "tween_all_completed")
fade.queue_free()
tween.queue_free()
is_transitioning = false
异步加载大场景
var loader: ResourceInteractiveLoader = null
func start_async_load(scene_path: String):
loader = ResourceLoader.load_interactive(scene_path)
set_process(true)
func _process(delta):
if loader == null:
return
var err = loader.poll()
if err == ERR_FILE_EOF:
var resource = loader.get_resource()
loader = null
set_process(false)
get_tree().change_scene_to(resource)
elif err == OK:
var progress = float(loader.get_stage()) / float(loader.get_stage_count())
$LoadingBar.value = progress * 100
⚠️ 注意:change_scene 会销毁当前整个场景树。需要保留的数据请保存到 Autoload 中。
Autoload 全局数据
配置:Project → Project Settings → Autoload
路径: res://scripts/game_manager.gd
名称: GameManager
# game_manager.gd(Autoload)
extends Node
var player_name: String = ""
var player_hp: int = 100
var player_max_hp: int = 100
var player_gold: int = 0
var current_level: int = 1
var unlocked_levels: Array = [1]
# 游戏设置
var master_volume: float = 1.0
var music_volume: float = 0.8
var fullscreen: bool = false
signal player_data_changed()
func add_gold(amount: int):
player_gold += amount
emit_signal("player_data_changed")
func take_damage(amount: int):
player_hp = max(0, player_hp - amount)
emit_signal("player_data_changed")
# 任意场景中访问:GameManager.player_hp
存档系统(File/JSON)
# save_manager.gd(Autoload)
extends Node
const SAVE_PATH = "user://save_game.json"
func save_game():
var save_data = {
"version": 1,
"timestamp": OS.get_datetime(),
"player": {
"hp": GameManager.player_hp,
"gold": GameManager.player_gold,
"level": GameManager.current_level
},
"settings": {
"master_volume": GameManager.master_volume,
"fullscreen": GameManager.fullscreen
}
}
var file = File.new()
var err = file.open(SAVE_PATH, File.WRITE)
if err != OK:
push_error("保存失败: " + str(err))
return
file.store_string(JSON.print(save_data, "\t"))
file.close()
func load_game() -> bool:
var file = File.new()
if not file.file_exists(SAVE_PATH):
return false
file.open(SAVE_PATH, File.READ)
var json = JSON.new()
var result = json.parse(file.get_as_text())
file.close()
if result.error != OK:
return false
var data = result.result
GameManager.player_hp = data["player"]["hp"]
GameManager.player_gold = data["player"]["gold"]
GameManager.current_level = data["player"]["level"]
return true
func has_save() -> bool:
return File.new().file_exists(SAVE_PATH)
func delete_save():
var dir = Directory.new()
if dir.file_exists(SAVE_PATH):
dir.remove(SAVE_PATH)
多存档槽位
func get_save_path(slot: int) -> String:
return "user://save_slot_%d.json" % slot
func get_all_slots_info() -> Array:
var slots = []
for i in range(1, 4):
var exists = File.new().file_exists(get_save_path(i))
slots.append({"slot": i, "exists": exists})
return slots
⚠️ 注意:user:// 路径在不同平台上位置不同(Windows 在 %APPDATA%,Linux 在 ~/.local/share/godot/)。不要使用 res://,打包后它是只读的。
ConfigFile
ConfigFile 适用于保存设置和简单的键值对数据。
func save_settings():
var config = ConfigFile.new()
config.set_value("audio", "master", GameManager.master_volume)
config.set_value("audio", "music", GameManager.music_volume)
config.set_value("video", "fullscreen", GameManager.fullscreen)
config.save("user://settings.cfg")
func load_settings():
var config = ConfigFile.new()
if config.load("user://settings.cfg") != OK:
return
GameManager.master_volume = config.get_value("audio", "master", 1.0)
GameManager.fullscreen = config.get_value("video", "fullscreen", false)
OS.window_fullscreen = GameManager.fullscreen
ConfigFile vs JSON 对比
| 特性 | ConfigFile | JSON |
|---|---|---|
| 格式 | INI 样式 | JSON 格式 |
| 嵌套支持 | 有限(Section-Key) | 完整支持 |
| 适用场景 | 游戏设置、简单数据 | 复杂存档、结构化数据 |
存档加密
const ENCRYPTION_KEY = "MyGameSecretKey2026"
func save_encrypted(data: Dictionary):
var json_str = JSON.print(data)
var encrypted = _xor_encrypt(json_str.to_utf8(), ENCRYPTION_KEY.to_utf8())
var file = File.new()
file.open("user://save_encrypted.dat", File.WRITE)
file.store_var(json_str.hash()) # 校验和
file.store_buffer(encrypted)
file.close()
func _xor_encrypt(input: PoolByteArray, key: PoolByteArray) -> PoolByteArray:
var output = PoolByteArray()
for i in range(input.size()):
output.append(input[i] ^ key[i % key.size()])
return output
数据迁移策略
# 存档版本迁移(老存档升级)
const CURRENT_VERSION = 3
func migrate_save(data: Dictionary) -> Dictionary:
var version = data.get("version", 1)
if version == 1:
data["player"]["exp"] = 0 # v2: 添加经验值
data["version"] = 2
version = 2
if version == 2:
data["inventory"] = [] # v3: inventory 格式变更
data["version"] = 3
return data
💡 提示:始终在存档中保留版本号字段。每次修改存档格式时,编写一个迁移函数。
游戏进度管理
extends Node
var completed_levels := {}
func complete_level(level_id: int):
completed_levels[level_id] = true
func is_level_unlocked(level_id: int) -> bool:
if level_id == 1:
return true
return completed_levels.get(level_id - 1, false)
func get_level_scene(level_id: int) -> String:
return "res://scenes/levels/level_%02d.tscn" % level_id
func go_to_next_level():
var next = GameManager.current_level + 1
var scene = get_level_scene(next)
var file = File.new()
if file.file_exists(scene):
GameManager.current_level = next
get_tree().change_scene(scene)
else:
get_tree().change_scene("res://scenes/main_menu.tscn")
游戏开发场景
自动保存
func _notification(what):
if what == MainLoop.NOTIFICATION_WM_FOCUS_OUT:
SaveManager.save_game()
if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
SaveManager.save_game()
get_tree().quit()
场景间数据传递
# Autoload 中存储过渡数据
var scene_data := {}
func transition_to(scene_path: String, data: Dictionary = {}):
scene_data = data
get_tree().change_scene(scene_path)
# 目标场景中读取
func _ready():
var data = GameManager.scene_data
if "spawn_point" in data:
$Player.position = data["spawn_point"]
GameManager.scene_data = {}
扩展阅读
💡 总结:Autoload + JSON 是最常用的数据持久化组合。始终为存档添加版本号,方便后续数据迁移。
user://是跨平台存档的标准路径。