中文文档 链接
Long Scene Manager is a powerful and easy-to-use Godot 4.x scene management plugin. It is specifically designed to solve various pain points in complex scene switching and multi-scene management in game projects. The plugin provides built-in asynchronous loading technology, an intelligent three-tier caching system, and customizable loading transitions, making your game scene switching efficient, smooth, controllable, and predictable.
Whether you are an indie game developer or a commercial project with a full team, Long Scene Manager can help you:
- 🚀 Eliminate Loading Stutters - Fully asynchronous loading mechanism makes loading large scenes seamless
- 💾 Smart Memory Management - LRU/FIFO hybrid caching strategy ensures memory is used wisely
- ✨ Ultimate User Experience - Custom loading screens, fade in/out effects, progress bar display, turns waiting into enjoyment
- 🎯 Flexible Scene Reuse - Preserve scene states, switch anytime, making game flow smoother
- 🔧 Zero Learning Curve - Clean API design, can be integrated with just a few lines of code
🌟 Special Note: The plugin provides both GDScript and C# versions with identical functionality. No matter which language you use, you can enjoy the same features and performance!
If you find this plugin helpful for your project, please:
- ⭐ Give the GitHub repository a Star: https://github.com/AWAUOX/GodotPlugin_LongSceneManager
- 🎮 Mention this plugin and its author LongZhan in your game's credits
- 📢 Recommend this plugin to other Godot developers
Your support is my motivation to keep improving!
- Long Scene Manager Plugin
- ✅ Fully Asynchronous Loading - Non-blocking main thread with progress callbacks
- ✅ Three-Tier Cache System - Instance cache + temp preload cache + fixed preload cache
- ✅ Five Loading Strategies - Flexible control over cache lookup priority
- ✅ Customizable Loading Screens - Supports fade in/out, progress bars, and more
- ✅ Multi-Frame Instantiation - Avoids stuttering when instantiating large scenes
- ✅ Complete Signal System - Easy monitoring and debugging
- Copy the
addons/long_scene_managerfolder into your project'saddonsfolder - Enable the plugin in Godot:
- Go to
Project → Project Settings → Plugins - Find "Long Scene Manager" and set its status to "Enabled"
- Go to
- The plugin will automatically register as an Autoload singleton:
- GDScript version: Access name is
LongSceneManager - C# version: Access name is
LongSceneManagerCs
- GDScript version: Access name is
Note: Both versions have identical functionality. Which one to use depends on your project's language environment. The GDScript version works out of the box, while the C# version requires your project to have C# support (.NET) configured.
Select the LongSceneManager node in the editor to configure the following parameters in the inspector:
| Variable | Type | Default | Description |
|---|---|---|---|
max_cache_size |
int (1~20) | 4 | Maximum instance cache capacity; LRU eviction when exceeded |
max_temp_preload_resource_cache_size |
int (1~50) | 8 | Maximum temp preload resource cache capacity; FIFO eviction when exceeded |
max_fixed_preload_resource_cache_size |
int (0~50) | 4 | Maximum fixed preload resource cache capacity; FIFO eviction when exceeded |
use_async_loading |
bool | true | Whether to use async loading (recommended) |
always_use_default_load_screen |
bool | false | Whether to force using the default loading screen |
instantiate_frames |
int (1~10) | 3 | Number of frames to delay during scene instantiation |
# Access singleton
var manager = LongSceneManager
# Scene switching
manager.switch_scene("res://scenes/level_02.tscn")
# Preloading
manager.preload_scene("res://scenes/level_02.tscn")
# Get cache info
var info = manager.get_cache_info()// Access singleton
var manager = LongSceneManagerCs.Instance;
// Scene switching
await manager.SwitchScene("res://scenes/Level02.tscn", LongSceneManagerCs.LoadMethod.BothPreloadFirst, true, "");
// Preloading
manager.PreloadScene("res://scenes/Level02.tscn");
// Batch preloading
manager.PreloadScenes(new string[] { "res://scenes/Level02.tscn", "res://scenes/Level03.tscn" });
// Cancel preloading
manager.CancelPreloadingScene("res://scenes/Level03.tscn");
// Get cache info
var cacheInfo = manager.GetCacheInfo();
// Listen to signals
manager.SceneSwitchStarted += OnSceneSwitchStarted;
manager.SceneSwitchCompleted += OnSceneSwitchCompleted;
private void OnSceneSwitchStarted(string fromScene, string toScene)
{
GD.Print($"Switch: {fromScene} -> {toScene}");
}
private void OnSceneSwitchCompleted(string scenePath)
{
GD.Print($"Completed: {scenePath}");
}switch_scene() / SwitchScene() is the core scene switching function:
GDScript:
# Simplest switch (using default strategy and default loading screen)
LongSceneManager.switch_scene("res://scenes/level_02.tscn")
# Don't cache current scene
LongSceneManager.switch_scene("res://scenes/main_menu.tscn", LongSceneManager.LoadMethod.BOTH_PRELOAD_FIRST, false)
# Use SCENE_CACHE strategy (prioritize reusing instances)
await LongSceneManager.switch_scene("res://scenes/level_02.tscn", LongSceneManager.LoadMethod.SCENE_CACHE)
# No transition switch
LongSceneManager.switch_scene("res://scenes/level_02.tscn", LongSceneManager.LoadMethod.BOTH_PRELOAD_FIRST, true, "no_transition")C#:
// Simplest switch (using default strategy and default loading screen)
await LongSceneManagerCs.Instance.SwitchScene("res://scenes/Level02.tscn");
// Don't cache current scene
await LongSceneManagerCs.Instance.SwitchScene("res://scenes/MainMenu.tscn", LongSceneManagerCs.LoadMethod.BothPreloadFirst, false, "");
// Use SceneCache strategy (prioritize reusing instances)
await LongSceneManagerCs.Instance.SwitchScene("res://scenes/Level02.tscn", LongSceneManagerCs.LoadMethod.SceneCache, true, "");
// No transition switch
await LongSceneManagerCs.Instance.SwitchScene("res://scenes/Level02.tscn", LongSceneManagerCs.LoadMethod.BothPreloadFirst, true, "no_transition");Preloading allows you to load scene resources in the background ahead of time, so switching is nearly instantaneous when needed:
GDScript:
# Temp preloading: resource may be automatically evicted
LongSceneManager.preload_scene("res://scenes/level_02.tscn")
# Fixed preloading: resource won't be auto-evicted, can be instantiated multiple times
LongSceneManager.preload_scene("res://scenes/boss_fight.tscn", true)
# Batch preloading
LongSceneManager.preload_scenes(["res://scenes/level_02.tscn", "res://scenes/level_03.tscn"])
# Cancel preloading
LongSceneManager.cancel_preloading_scene("res://scenes/level_03.tscn")C#:
// Temp preloading: resource may be automatically evicted
LongSceneManagerCs.Instance.PreloadScene("res://scenes/Level02.tscn");
// Fixed preloading: resource won't be auto-evicted, can be instantiated multiple times
LongSceneManagerCs.Instance.PreloadScene("res://scenes/BossFight.tscn", true);
// Batch preloading
LongSceneManagerCs.Instance.PreloadScenes(new string[] { "res://scenes/Level02.tscn", "res://scenes/Level03.tscn" });
// Cancel preloading
LongSceneManagerCs.Instance.CancelPreloadingScene("res://scenes/Level03.tscn");
// Cancel all preloading
LongSceneManagerCs.Instance.CancelAllPreloading();GDScript:
# 1. After entering a level, preload the next level
func _on_level_01_ready():
LongSceneManager.preload_scene("res://scenes/level_02.tscn")
# 2. Player reaches exit, switch scene
func _on_exit_reached():
await LongSceneManager.switch_scene("res://scenes/level_02.tscn")C#:
// 1. After entering a level, preload the next level
public override void _Ready()
{
LongSceneManagerCs.Instance.PreloadScene("res://scenes/Level02.tscn");
}
// 2. Player reaches exit, switch scene
private async void OnExitReached()
{
await LongSceneManagerCs.Instance.SwitchScene("res://scenes/Level02.tscn");
}When switching scenes, the load_method parameter controls cache lookup behavior:
| Strategy | Enum Value | Lookup Order | Use Case |
|---|---|---|---|
DIRECT |
Direct load | Preload cache → direct load | Don't care about instance cache, just need the resource |
PRELOAD_CACHE |
Preload first | Preload cache → fallback direct load | Only use preloaded resources |
SCENE_CACHE |
Instance cache first | Instance cache → fallback direct load | Prioritize reusing instantiated scenes (preserve state) |
BOTH_PRELOAD_FIRST |
Hybrid preload first | Preload → instance → preloading → direct | Default strategy, recommended for most cases |
BOTH_INSTANCE_FIRST |
Hybrid instance first | Instance → preload → preloading → direct | Prioritize reusing scene instances (preserve state) |
Important: Only when hitting the instance cache will an already-instantiated scene be reused. That scene will retain its complete runtime state from before the switch.
┌─────────────────────────────────────────────────────────┐
│ LongSceneManager │
├─────────────────────────────────────────────────────────┤
│ Tier 1: Instance Cache │
│ Stores: Complete scene Node + cache timestamp │
│ Eviction: LRU (Least Recently Used) │
│ Default capacity: 4 │
│ Feature: Preserves complete runtime state before switch│
├─────────────────────────────────────────────────────────┤
│ Tier 2: Temp Preload Cache │
│ Stores: PackedScene resource │
│ Eviction: FIFO (First In, First Out) │
│ Default capacity: 8 │
│ Feature: Resource is removed from cache after use │
│ (consumable) │
├─────────────────────────────────────────────────────────┤
│ Tier 3: Fixed Preload Cache │
│ Stores: PackedScene resource │
│ Eviction: FIFO (First In, First Out) │
│ Default capacity: 4 │
│ Feature: Resource remains in cache after use │
│ (persistent) │
└─────────────────────────────────────────────────────────┘
Core Design Principle: A scene instance is either in the scene tree (currently active) or in the cache (inactive but retained in memory). The two are strictly separated and never coexist.
GDScript:
# Clear all cache
LongSceneManager.clear_all_cache()
# Clear specific cache
LongSceneManager.clear_temp_preload_cache() # Temp preload cache
LongSceneManager.clear_fixed_cache() # Fixed preload cache
LongSceneManager.clear_instance_cache() # Instance cache
# Remove single resource
LongSceneManager.remove_temp_resource("res://scenes/level_02.tscn")
LongSceneManager.remove_fixed_resource("res://scenes/shop.tscn")
LongSceneManager.remove_cached_scene("res://scenes/level_02.tscn")
# Move resource
LongSceneManager.move_to_fixed("res://scenes/boss_fight.tscn") # Temp → Fixed
LongSceneManager.move_to_temp("res://scenes/boss_fight.tscn") # Fixed → Temp
# Dynamically adjust cache size
LongSceneManager.set_max_cache_size(10)C#:
// Clear all cache
LongSceneManagerCs.Instance.ClearAllCache();
// Clear specific cache
LongSceneManagerCs.Instance.ClearTempPreloadCache(); // Temp preload cache
LongSceneManagerCs.Instance.ClearFixedCache(); // Fixed preload cache
LongSceneManagerCs.Instance.ClearInstanceCache(); // Instance cache
// Remove single resource
LongSceneManagerCs.Instance.RemoveTempResource("res://scenes/Level02.tscn");
LongSceneManagerCs.Instance.RemoveFixedResource("res://scenes/Shop.tscn");
LongSceneManagerCs.Instance.RemoveCachedScene("res://scenes/Level02.tscn");
// Move resource
LongSceneManagerCs.Instance.MoveToFixed("res://scenes/BossFight.tscn"); // Temp → Fixed
LongSceneManagerCs.Instance.MoveToTemp("res://scenes/BossFight.tscn"); // Fixed → Temp
// Dynamically adjust cache size
LongSceneManagerCs.Instance.SetMaxCacheSize(10);GDScript:
# Listen to scene switching events
LongSceneManager.scene_switch_started.connect(_on_switch_started)
LongSceneManager.scene_switch_completed.connect(_on_switch_completed)
LongSceneManager.scene_preload_started.connect(_on_preload_started)
LongSceneManager.scene_preload_completed.connect(_on_preload_completed)
LongSceneManager.scene_cached.connect(_on_scene_cached)
# Or use auto-connect
LongSceneManager.connect_all_signals(self)
# Then implement corresponding method: _on_scene_manager_scene_switch_started()C#:
// Listen to scene switching events
LongSceneManagerCs.Instance.SceneSwitchStarted += OnSceneSwitchStarted;
LongSceneManagerCs.Instance.SceneSwitchCompleted += OnSceneSwitchCompleted;
LongSceneManagerCs.Instance.ScenePreloadStarted += OnScenePreloadStarted;
LongSceneManagerCs.Instance.ScenePreloadCompleted += OnScenePreloadCompleted;
LongSceneManagerCs.Instance.SceneCached += OnSceneCached;
// Unsubscribe
LongSceneManagerCs.Instance.SceneSwitchStarted -= OnSceneSwitchStarted;Available Signals:
| Signal | Parameters | Description |
|---|---|---|
scene_preload_started |
scene_path | Preloading started |
scene_preload_completed |
scene_path | Preloading completed |
scene_preload_cancelled |
scene_path | Preloading cancelled |
scene_switch_started |
from_scene, to_scene | Switching started |
scene_switch_completed |
scene_path | Switching completed |
scene_cached |
scene_path | Scene cached |
scene_removed_from_cache |
scene_path | Scene removed from cache |
load_screen_shown |
instance | Loading screen shown |
load_screen_hidden |
instance | Loading screen hidden |
scene_preload_failed |
scene_path | Preloading failed |
scene_switch_failed |
scene_path | Switching failed |
-
Strict Separation of Scene Tree and Cache
- A scene instance is either in the scene tree (currently active) or in the cache
- The two never coexist, which avoids node management chaos
-
State Machine Driven
- Each scene resource goes through five states:
NOT_LOADED → LOADING → LOADED → INSTANTIATED - Supports
CANCELLEDstate for cancelled preloading
- Each scene resource goes through five states:
-
Async First
- Resource loading uses
ResourceLoader.load_threaded_request() - Multi-frame instantiation avoids main thread stuttering
- Resource loading uses
-
Extensible Loading Screen
- Supports custom loading screen scenes
- Compatibility through method name conventions
addons/long_scene_manager/
├── autoload/
│ ├── long_scene_manager.gd # GDScript core manager (singleton)
│ └── LongSceneManagerCs.cs # C# core manager (singleton)
├── ui/
│ └── loading_screen/
│ ├── GDScript/ # GDScript version loading screen
│ │ └── loading_black_screen.tscn
│ └── CSharp/ # C# version loading screen
│ └── loading_black_screen_cs.tscn
└── image_icon/
└── icon3.png
long_scene_manager.gd core code sections:
| Section | Line Range | Description |
|---|---|---|
| Constants and Enums | 1-50 | LoadState, LoadMethod enum definitions |
| Signal Definitions | 52-65 | All scene event signals |
| Exported Variables | 67-75 | Plugin configuration items |
| Internal State | 77-95 | Cache and scene state variables |
| Internal Class | 96-102 | CachedScene data class |
| Lifecycle | 104-115 | _ready() initialization |
| Public API - Scene Switching | 117-145 | switch_scene() |
| Public API - Preloading | 147-185 | preload_*() |
| Public API - Cache Management | 187-320 | clear_, remove_, move_* |
| Public API - Query | 322-420 | get_cache_info(), get_*() |
| Public API - Debugging | 422-450 | print_debug_info() |
| Private Functions - Initialization | 550-600 | _init_default_load_screen() |
| Private Functions - Preloading Core | 650-750 | _async_preload_scene() |
| Private Functions - Loading Screen | 760-830 | _show_load_screen() |
| Private Functions - Scene Loading | 850-1000 | _load_scene_by_method() |
| Private Functions - Instantiation | 1000-1100 | _instantiate_scene_deferred() |
| Private Functions - Cache Management | 1100-1200 | _add_to_cache() |
switch_scene() is called
│
├─► Validate scene path
│
├─► Emit scene_switch_started signal
│
├─► Get loading screen instance
│
├─► Show loading screen (_show_load_screen)
│ │
│ ├─► Set visibility
│ │
│ └─► Call fade_in() or show_loading()
│
├─► Select loading strategy based on LoadMethod
│ │
│ ├─► DIRECT: Direct load or use preload resource
│ │
│ ├─► PRELOAD_CACHE: Only check preload cache
│ │
│ ├─► SCENE_CACHE: Only check instance cache
│ │
│ ├─► BOTH_PRELOAD_FIRST: preload → instance → preloading → direct
│ │
│ └─► BOTH_INSTANCE_FIRST: instance → preload → preloading → direct
│
├─► Execute scene switch (_perform_scene_switch)
│ │
│ ├─► Remove old scene
│ │
│ ├─► If caching needed, add to instance cache
│ │
│ ├─► Add new scene to scene tree
│ │
│ └─► Wait for ready signal
│
├─► Hide loading screen (_hide_load_screen)
│ │
│ ├─► Call fade_out() or hide_loading()
│ │
│ └─► Cleanup custom loading screen
│
└─► Emit scene_switch_completed signal
preload_scene() is called
│
├─► Check if already in cache or loading
│
├─► Create preload state (LoadState.LOADING)
│
├─► Emit scene_preload_started signal
│
└─► Start background preload (_preload_background)
│
├─► Async mode: ResourceLoader.load_threaded_request()
│ │
│ └─► Poll status until LOADED
│
└─► Sync mode: Direct load()
│
└─► Store to preload cache
│
├─► Temp cache (FIFO eviction)
└─► Fixed cache (persistent)
enum LoadState {
NOT_LOADED, # Initial state
LOADING, # Async loading in progress
LOADED, # Resource loaded (not instantiated)
INSTANTIATED, # Instantiated (in instance cache)
CANCELLED # Preloading cancelled
}class CachedScene:
var scene_instance: Node # Scene instance
var cached_time: float # Cache timestamp
func _init(scene: Node):
scene_instance = scene
cached_time = Time.get_unix_time_from_system()# Instance cache structure
instantiate_scene_cache = {
"res://scenes/level_01.tscn": CachedScene,
"res://scenes/level_02.tscn": CachedScene
}
instantiate_scene_cache_order = ["level_01", "level_02"] # LRU order
# Preload resource cache structure
temp_preloaded_resource_cache = {
"res://scenes/level_03.tscn": PackedScene
}
temp_preloaded_resource_cache_order = ["level_03"] # FIFO order
fixed_preload_resource_cache = {
"res://scenes/boss.tscn": PackedScene
}
# Preload state
_preload_resource_states = {
"res://scenes/level_01.tscn": {
"state": LoadState.LOADED,
"resource": PackedScene,
"fixed": false
}
}| Function | Description |
|---|---|
switch_scene(new_scene_path, load_method, cache_current_scene, load_screen_path) |
Switch scene |
| Function | Description |
|---|---|
preload_scene(scene_path, fixed) |
Preload single scene |
preload_scenes(scene_paths, fixed) |
Batch preload |
cancel_preloading_scene(scene_path) |
Cancel single preload |
cancel_all_preloading() |
Cancel all preloading |
| Function | Description |
|---|---|
clear_all_cache() |
Clear all cache |
clear_temp_preload_cache() |
Clear temp preload cache |
clear_fixed_cache() |
Clear fixed preload cache |
clear_instance_cache() |
Clear instance cache |
remove_temp_resource(scene_path) |
Remove temp preload resource |
remove_fixed_resource(scene_path) |
Remove fixed preload resource |
remove_cached_scene(scene_path) |
Remove cached scene |
move_to_fixed(scene_path) |
Move to fixed cache |
move_to_temp(scene_path) |
Move to temp cache |
set_max_cache_size(new_size) |
Set instance cache capacity |
set_max_temp_preload_resource_cache_size(new_size) |
Set temp cache capacity |
set_max_fixed_cache_size(new_size) |
Set fixed cache capacity |
| Function | Description |
|---|---|
get_cache_info() |
Get complete cache status |
is_scene_cached(scene_path) |
Check if scene is in cache |
is_scene_preloading(scene_path) |
Check if scene is preloading |
get_preloading_scenes() |
Get list of scenes being preloaded |
get_current_scene() |
Get current scene node |
get_previous_scene_path() |
Get previous scene path |
get_loading_progress(scene_path) |
Get loading progress (0.0~1.0) |
is_in_fixed_cache(scene_path) |
Check if in fixed cache |
| Function | Description |
|---|---|
print_debug_info() |
Print debug info |
connect_all_signals(target) |
Auto-connect all signals |
# Empty string or "default" uses default black screen
await LongSceneManager.switch_scene("res://scenes/level_02.tscn", LoadMethod.BOTH_PRELOAD_FIRST, true, "")A custom loading screen scene should implement the following methods (optional, implement as needed):
extends CanvasLayer
@onready var progress_bar: ProgressBar = $ProgressBar
func fade_in():
# Fade in animation when showing
var tween = create_tween()
tween.tween_property(self, "modulate:a", 1.0, 0.3)
func fade_out():
# Fade out animation when hiding
var tween = create_tween()
tween.tween_property(self, "modulate:a", 0.0, 0.3)
func set_progress(progress: float):
# Update progress (0.0 ~ 1.0)
progress_bar.value = progress * 100.0await LongSceneManager.switch_scene(
"res://scenes/level_02.tscn",
LoadMethod.BOTH_PRELOAD_FIRST,
true,
"res://ui/my_loading_screen.tscn" # Custom path
)The plugin comes with a demo project demo_test_scene_manager/, which includes:
main_scene.tscn- Main interface, demonstrating basic switching functionalitytest_scene_1.tscn- Test scene 1test_scene_2.tscn- Test scene 2, demonstrating complete cache management functionality
Running the demo project provides a intuitive understanding of the plugin's features.
This plugin is developed and maintained by me in my spare time. If you find it helpful, please consider supporting me in the following ways:
Visit the GitHub repository and click the Star button in the upper right corner. This is the simplest and most powerful way to show your support!
- Share this plugin in Godot communities, forums, and Discord groups
- Write a usage experience or tutorial
- Mention this plugin in your game development videos/livestreams
Add the following to your game's credits:
Scene Manager Plugin - LongSceneManager by LongZhan
https://github.com/AWAUOX/GodotPlugin_LongSceneManager
- Found a bug? Submit an Issue on GitHub
- Have a great idea? Submit a Feature Request
- Want to contribute code? Submit a Pull Request
- GitHub: @AWAUOX
The path of solo development can be lonely, and every Star from you gives me the motivation to keep going! Thank you! 🙏