godot-scene-management

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Scene Management

场景管理

Async loading, transitions, instance pooling, and caching define smooth scene workflows.
异步加载、过渡、实例池和缓存是实现流畅场景工作流的关键。

Available Scripts

可用脚本

async_scene_manager.gd

async_scene_manager.gd

Expert async scene loader with progress tracking, error handling, and transition callbacks.
专业的异步场景加载器,包含进度跟踪、错误处理和过渡回调功能。

scene_pool.gd

scene_pool.gd

Object pooling for frequently spawned scenes (bullets, godot-particles, enemies).
针对频繁生成的场景(子弹、godot-particles、敌人)实现对象池功能。

scene_state_manager.gd

scene_state_manager.gd

Preserves and restores scene state across transitions using "persist" group pattern.
MANDATORY - For Smooth Transitions: Read async_scene_manager.gd before implementing loading screens.
使用"persist"组模式在场景过渡期间保存和恢复场景状态。
实现流畅过渡的强制要求:在实现加载界面之前,请先阅读async_scene_manager.gd的代码。

NEVER Do in Scene Management

场景管理中的禁忌操作

  • NEVER use load() in gameplay code
    var scene = load("res://level.tscn")
    blocks entire game until loaded. Use
    preload()
    OR
    ResourceLoader.load_threaded_request()
    .
  • NEVER forget to check THREAD_LOAD_FAILED — Async loading without status check? Silent failure = black screen. MUST handle
    THREAD_LOAD_FAILED
    state.
  • NEVER change scenes without cleaning up — Active timers/tweens persist across scenes = memory leak + unexpected behavior. Stop timers, disconnect signals before transition.
  • NEVER use get_tree().change_scene_to_file() during _ready() — Changing scene in
    _ready()
    = crash (scene tree locked). Use
    call_deferred("change_scene")
    .
  • NEVER instance scenes without null check
    var obj = scene.instantiate()
    if scene load failed? Crash. Check scene != null first.
  • NEVER forget queue_free() on dynamic instances — Spawned 1000 enemies, all dead, but not freed? Memory leak. Use
    queue_free()
    OR instance pooling.

gdscript
undefined
  • 绝对不要在游戏运行代码中使用load() ——
    var scene = load("res://level.tscn")
    会阻塞整个游戏直到加载完成。请使用
    preload()
    ResourceLoader.load_threaded_request()
  • 绝对不要忘记检查THREAD_LOAD_FAILED状态 —— 异步加载却不检查状态?静默失败会导致黑屏。必须处理
    THREAD_LOAD_FAILED
    状态。
  • 绝对不要不做清理就切换场景 —— 活跃的计时器/补间会在场景间残留,导致内存泄漏和意外行为。切换前请停止计时器、断开信号。
  • 绝对不要在_ready()中使用get_tree().change_scene_to_file() —— 在
    _ready()
    中切换场景会导致崩溃(场景树已锁定)。请使用
    call_deferred("change_scene")
  • 绝对不要在实例化场景前做空值检查 —— 如果场景加载失败,
    var obj = scene.instantiate()
    会导致崩溃。请先检查scene != null。
  • 绝对不要忘记对动态实例调用queue_free() —— 生成了1000个敌人,全部死亡但未释放?会造成内存泄漏。请使用
    queue_free()
    或实例池。

gdscript
undefined

Instant scene change

Instant scene change

get_tree().change_scene_to_file("res://levels/level_2.tscn")
get_tree().change_scene_to_file("res://levels/level_2.tscn")

Or with packed scene

Or with packed scene

var next_scene := load("res://levels/level_2.tscn") get_tree().change_scene_to_packed(next_scene)
undefined
var next_scene := load("res://levels/level_2.tscn") get_tree().change_scene_to_packed(next_scene)
undefined

Scene Transition with Fade

带淡入淡出效果的场景过渡

gdscript
undefined
gdscript
undefined

scene_transitioner.gd (AutoLoad)

scene_transitioner.gd (AutoLoad)

extends CanvasLayer
signal transition_finished
func change_scene(scene_path: String) -> void: # Fade out $AnimationPlayer.play("fade_out") await $AnimationPlayer.animation_finished
# Change scene
get_tree().change_scene_to_file(scene_path)

# Fade in
$AnimationPlayer.play("fade_in")
await $AnimationPlayer.animation_finished

transition_finished.emit()
extends CanvasLayer
signal transition_finished
func change_scene(scene_path: String) -> void: # Fade out $AnimationPlayer.play("fade_out") await $AnimationPlayer.animation_finished
# Change scene
get_tree().change_scene_to_file(scene_path)

# Fade in
$AnimationPlayer.play("fade_in")
await $AnimationPlayer.animation_finished

transition_finished.emit()

Usage:

Usage:

SceneTransitioner.change_scene("res://levels/level_2.tscn") await SceneTransitioner.transition_finished
undefined
SceneTransitioner.change_scene("res://levels/level_2.tscn") await SceneTransitioner.transition_finished
undefined

Async (Background) Loading

异步(后台)加载

gdscript
extends Node

var loading_status: int = 0
var progress := []

func load_scene_async(path: String) -> void:
    ResourceLoader.load_threaded_request(path)
    
    while true:
        loading_status = ResourceLoader.load_threaded_get_status(
            path,
            progress
        )
        
        if loading_status == ResourceLoader.THREAD_LOAD_LOADED:
            var scene := ResourceLoader.load_threaded_get(path)
            get_tree().change_scene_to_packed(scene)
            break
        
        # Update loading bar
        print("Loading: ", progress[0] * 100, "%")
        await get_tree().process_frame
gdscript
extends Node

var loading_status: int = 0
var progress := []

func load_scene_async(path: String) -> void:
    ResourceLoader.load_threaded_request(path)
    
    while true:
        loading_status = ResourceLoader.load_threaded_get_status(
            path,
            progress
        )
        
        if loading_status == ResourceLoader.THREAD_LOAD_LOADED:
            var scene := ResourceLoader.load_threaded_get(path)
            get_tree().change_scene_to_packed(scene)
            break
        
        # Update loading bar
        print("Loading: ", progress[0] * 100, "%")
        await get_tree().process_frame

Loading Screen Pattern

加载界面模式

gdscript
undefined
gdscript
undefined

loading_screen.gd

loading_screen.gd

extends Control
@onready var progress_bar: ProgressBar = $ProgressBar
func load_scene(path: String) -> void: show() ResourceLoader.load_threaded_request(path)
var progress := []
var status: int

while true:
    status = ResourceLoader.load_threaded_get_status(path, progress)
    
    if status == ResourceLoader.THREAD_LOAD_LOADED:
        var scene := ResourceLoader.load_threaded_get(path)
        get_tree().change_scene_to_packed(scene)
        break
    elif status == ResourceLoader.THREAD_LOAD_FAILED:
        push_error("Failed to load scene: " + path)
        break
    
    progress_bar.value = progress[0] * 100
    await get_tree().process_frame

hide()
undefined
extends Control
@onready var progress_bar: ProgressBar = $ProgressBar
func load_scene(path: String) -> void: show() ResourceLoader.load_threaded_request(path)
var progress := []
var status: int

while true:
    status = ResourceLoader.load_threaded_get_status(path, progress)
    
    if status == ResourceLoader.THREAD_LOAD_LOADED:
        var scene := ResourceLoader.load_threaded_get(path)
        get_tree().change_scene_to_packed(scene)
        break
    elif status == ResourceLoader.THREAD_LOAD_FAILED:
        push_error("Failed to load scene: " + path)
        break
    
    progress_bar.value = progress[0] * 100
    await get_tree().process_frame

hide()
undefined

Dynamic Scene Instances

动态场景实例

Add Scene as Child

将场景作为子节点添加

gdscript
undefined
gdscript
undefined

Spawn enemy at runtime

Spawn enemy at runtime

const ENEMY_SCENE := preload("res://enemies/goblin.tscn")
func spawn_enemy(position: Vector2) -> void: var enemy := ENEMY_SCENE.instantiate() enemy.global_position = position add_child(enemy)
undefined
const ENEMY_SCENE := preload("res://enemies/goblin.tscn")
func spawn_enemy(position: Vector2) -> void: var enemy := ENEMY_SCENE.instantiate() enemy.global_position = position add_child(enemy)
undefined

Instance Management

实例管理

gdscript
undefined
gdscript
undefined

Keep track of spawned enemies

Keep track of spawned enemies

var active_enemies: Array[Node] = []
func spawn_enemy(pos: Vector2) -> void: var enemy := ENEMY_SCENE.instantiate() enemy.global_position = pos add_child(enemy) active_enemies.append(enemy)
# Clean up when enemy dies
enemy.tree_exited.connect(
    func(): active_enemies.erase(enemy)
)
func clear_all_enemies() -> void: for enemy in active_enemies: enemy.queue_free() active_enemies.clear()
undefined
var active_enemies: Array[Node] = []
func spawn_enemy(pos: Vector2) -> void: var enemy := ENEMY_SCENE.instantiate() enemy.global_position = pos add_child(enemy) active_enemies.append(enemy)
# Clean up when enemy dies
enemy.tree_exited.connect(
    func(): active_enemies.erase(enemy)
)
func clear_all_enemies() -> void: for enemy in active_enemies: enemy.queue_free() active_enemies.clear()
undefined

Sub-Scenes

子场景

gdscript
undefined
gdscript
undefined

Load UI as sub-scene

Load UI as sub-scene

@onready var ui := preload("res://ui/game_ui.tscn").instantiate()
func _ready() -> void: add_child(ui)
undefined
@onready var ui := preload("res://ui/game_ui.tscn").instantiate()
func _ready() -> void: add_child(ui)
undefined

Scene Persistence

场景持久化

gdscript
undefined
gdscript
undefined

Keep scene loaded when changing scenes

Keep scene loaded when changing scenes

var persistent_scene: Node
func make_persistent(scene: Node) -> void: persistent_scene = scene scene.get_parent().remove_child(scene) get_tree().root.add_child(scene)
func restore_persistent() -> void: if persistent_scene: get_tree().root.remove_child(persistent_scene) add_child(persistent_scene)
undefined
var persistent_scene: Node
func make_persistent(scene: Node) -> void: persistent_scene = scene scene.get_parent().remove_child(scene) get_tree().root.add_child(scene)
func restore_persistent() -> void: if persistent_scene: get_tree().root.remove_child(persistent_scene) add_child(persistent_scene)
undefined

Reload Current Scene

重新加载当前场景

gdscript
undefined
gdscript
undefined

Restart level

Restart level

get_tree().reload_current_scene()
undefined
get_tree().reload_current_scene()
undefined

Scene Caching

场景缓存

gdscript
undefined
gdscript
undefined

Cache frequently used scenes

Cache frequently used scenes

var scene_cache: Dictionary = {}
func get_cached_scene(path: String) -> PackedScene: if not scene_cache.has(path): scene_cache[path] = load(path) return scene_cache[path]
var scene_cache: Dictionary = {}
func get_cached_scene(path: String) -> PackedScene: if not scene_cache.has(path): scene_cache[path] = load(path) return scene_cache[path]

Usage:

Usage:

var enemy := get_cached_scene("res://enemies/goblin.tscn").instantiate()
undefined
var enemy := get_cached_scene("res://enemies/goblin.tscn").instantiate()
undefined

Best Practices

最佳实践

1. Use SceneTransitioner AutoLoad

1. 使用SceneTransitioner自动加载

gdscript
undefined
gdscript
undefined

Centralized scene management

Centralized scene management

All transitions go through one system

All transitions go through one system

Consistent fade effects

Consistent fade effects

undefined
undefined

2. Preload Common Scenes

2. 预加载常用场景

gdscript
undefined
gdscript
undefined

✅ Good - preload at compile time

✅ Good - preload at compile time

const BULLET := preload("res://projectiles/bullet.tscn")
const BULLET := preload("res://projectiles/bullet.tscn")

❌ Bad - load at runtime

❌ Bad - load at runtime

var bullet := load("res://projectiles/bullet.tscn")
undefined
var bullet := load("res://projectiles/bullet.tscn")
undefined

3. Clean Up Before Transition

3. 过渡前进行清理

gdscript
func change_level() -> void:
    # Clear timers, tweens, etc.
    for timer in get_tree().get_nodes_in_group("timers"):
        timer.stop()
    
    SceneTransitioner.change_scene("res://levels/next.tscn")
gdscript
func change_level() -> void:
    # Clear timers, tweens, etc.
    for timer in get_tree().get_nodes_in_group("timers"):
        timer.stop()
    
    SceneTransitioner.change_scene("res://levels/next.tscn")

4. Error Handling

4. 错误处理

gdscript
func load_scene_safe(path: String) -> bool:
    if not ResourceLoader.exists(path):
        push_error("Scene not found: " + path)
        return false
    
    get_tree().change_scene_to_file(path)
    return true
gdscript
func load_scene_safe(path: String) -> bool:
    if not ResourceLoader.exists(path):
        push_error("Scene not found: " + path)
        return false
    
    get_tree().change_scene_to_file(path)
    return true

Reference

参考资料

Related

相关内容

  • Master Skill: godot-master
  • 核心技能:godot-master