godot-scene-management by thedivergentai/gd-agentic-skills
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-scene-management异步加载、过渡切换、实例池化和缓存机制共同定义了流畅的场景工作流。
具备进度追踪和线程安全切换功能的专业异步场景加载器。
使用 Tween 和 Shader 实现场景淡入淡出与过渡切换的简洁方案。
在不销毁当前世界场景的情况下管理 UI 叠加层和菜单。
在不同场景树之间安全地进行节点重新父级化,并保持变换属性。
使用自动加载节点在场景切换间保持玩家状态和游戏数据的模式。
高性能对象池,消除频繁实例化和释放带来的开销。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
使用 SubViewport 节点运行并行世界或专用渲染层。
使用唯一名称和错误防护的 @onready 实现的健壮节点引用架构。
用于模组系统或高度动态实体行为的运行时脚本操作。
确保大型场景图中零内存泄漏清理和孤立节点检测的模式。
具备进度追踪、错误处理和过渡回调功能的专业异步场景加载器。
为频繁生成的场景(子弹、粒子、敌人)设计的对象池。
使用“持久化”组模式在场景切换间保存和恢复场景状态。
强制要求 - 为了流畅过渡:在实现加载屏幕之前,请先阅读 async_scene_manager.gd。
load("res://large_scene.tscn") 会导致关卡切换时出现“卡顿”或完全冻结。应使用 ResourceLoader.load_threaded_request() 进行异步加载并配合进度条。get_tree().change_scene_to_file() 处理临时状态 — 此方法会清除当前场景及其所有局部变量。应使用**自动加载节点(单例)**或持久化的‘Game’节点来跨关卡存储状态。instantiate() 和 queue_free() 会导致 CPU 使用率飙升并频繁触发垃圾回收器。get_node("../../Path/To/Node") — 一旦在编辑器中移动节点,这些路径就会失效。应使用场景唯一名称(%NodeName)或 @export var target_node: Node 来建立健壮的引用。global_transform 并在 add_child() 调用后重新应用它。PhysicsServer 和 RenderingServer 以获得原始性能。NOTIFICATION_WM_CLOSE_REQUEST — 在桌面平台上,如果不在持久化节点中处理关闭请求,游戏可能在关键保存操作期间被关闭。queue_free() 是高效的。除非有特定的内存泄漏需要调试,否则不要尝试在循环中手动释放每个子节点。SubViewport 和主世界输入 — 默认情况下,输入事件会向上冒泡。应使用 set_input_as_handled() 来防止子视口中的 UI 点击触发主世界中的游戏玩法。change_scene 来“重置”关卡 — 这会从磁盘重新加载所有内容。要实现快速重生,只需重置变量并将玩家移动到起始位置即可。# Instant scene change
get_tree().change_scene_to_file("res://levels/level_2.tscn")
# Or with packed scene
var next_scene := load("res://levels/level_2.tscn")
get_tree().change_scene_to_packed(next_scene)
# 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()
# Usage:
SceneTransitioner.change_scene("res://levels/level_2.tscn")
await SceneTransitioner.transition_finished
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.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()
# 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)
# 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()
# Load UI as sub-scene
@onready var ui := preload("res://ui/game_ui.tscn").instantiate()
func _ready() -> void:
add_child(ui)
# 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)
# Restart level
get_tree().reload_current_scene()
# 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]
# Usage:
var enemy := get_cached_scene("res://enemies/goblin.tscn").instantiate()
# Centralized scene management
# All transitions go through one system
# Consistent fade effects
# ✅ Good - preload at compile time
const BULLET := preload("res://projectiles/bullet.tscn")
# ❌ Bad - load at runtime
var bullet := load("res://projectiles/bullet.tscn")
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")
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
每周安装量
73
代码仓库
GitHub 星标数
59
首次出现
2026年2月10日
安全审计
安装于
codex70
gemini-cli70
opencode70
github-copilot69
kimi-cli68
amp68
Async loading, transitions, instance pooling, and caching define smooth scene workflows.
Expert asynchronous scene loading with progress tracking and thread-safe transition.
Clean implementation of scene fades and transitions using Tweens and Shaders.
Managing UI overlays and menus without destroying the current world scene.
Safe, transform-preserving reparenting of nodes between different scene trees.
Pattern for using Autoloads to maintain player state and game data across scene changes.
High-performance object pooling to eliminate the cost of frequent instantiation and freeing.
Running parallel worlds or specialized rendering layers using SubViewport nodes.
Robust node reference architecture using Unique Names and error-guarded @onready.
Runtime script manipulation for modding systems or highly dynamic entity behavior.
Pattern for ensuring zero-leak cleanup and orphan node detection in huge scene graphs.
Expert async scene loader with progress tracking, error handling, and transition callbacks.
Object pooling for frequently spawned scenes (bullets, godot-particles, enemies).
Preserves and restores scene state across transitions using "persist" group pattern.
MANDATORY - For Smooth Transitions : Read async_scene_manager.gd before implementing loading screens.
load("res://large_scene.tscn") on the Main Thread causes "hiccups" or full freezes during level transitions. Use ResourceLoader.load_threaded_request() for async loading with a progress bar.get_tree().change_scene_to_file() for transient state — This method purges the current scene and all its local variables. Use an Autoload (Singleton) or a persistent 'Game' node to store state across levels.instantiate() and queue_free() calls spike CPU and trigger the Garbage Collector too often.get_node("../../Path/To/Node") — These paths break as soon as you move a node in the editor. Use Scene Unique Names (%NodeName) or for robust references.# Instant scene change
get_tree().change_scene_to_file("res://levels/level_2.tscn")
# Or with packed scene
var next_scene := load("res://levels/level_2.tscn")
get_tree().change_scene_to_packed(next_scene)
# 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()
# Usage:
SceneTransitioner.change_scene("res://levels/level_2.tscn")
await SceneTransitioner.transition_finished
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.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()
# 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)
# 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()
# Load UI as sub-scene
@onready var ui := preload("res://ui/game_ui.tscn").instantiate()
func _ready() -> void:
add_child(ui)
# 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)
# Restart level
get_tree().reload_current_scene()
# 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]
# Usage:
var enemy := get_cached_scene("res://enemies/goblin.tscn").instantiate()
# Centralized scene management
# All transitions go through one system
# Consistent fade effects
# ✅ Good - preload at compile time
const BULLET := preload("res://projectiles/bullet.tscn")
# ❌ Bad - load at runtime
var bullet := load("res://projectiles/bullet.tscn")
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")
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
Weekly Installs
73
Repository
GitHub Stars
59
First Seen
Feb 10, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex70
gemini-cli70
opencode70
github-copilot69
kimi-cli68
amp68
Skills CLI 使用指南:AI Agent 技能包管理器安装与管理教程
46,600 周安装
@export var target_node: Nodeglobal_transform and re-apply it after the add_child() call.PhysicsServer and RenderingServer directly for raw performance.NOTIFICATION_WM_CLOSE_REQUEST — On desktop, if you don't handle the close request in a persistent node, the game may close during a critical save operation.queue_free() on the root is efficient. Don't try to manually free every child in a loop unless you have specific memory leaks to debug.SubViewport and main world inputs without a plan — By default, input events bubble up. Use set_input_as_handled() to prevent UI clicks in a subviewport from triggering gameplay in the main world.change_scene to "Reset" a level — It reloads everything from disk. For a quick respawn, just reset the variables and move the player to the start position.