├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md └── SceneLoader.gd /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Godot-specific ignores 3 | .import/ 4 | export.cfg 5 | export_presets.cfg 6 | 7 | # Mono-specific ignores 8 | .mono/ 9 | data_*/ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 @November_dev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SceneLoader.gd for the Godot Game Engine 2 | This script provides an easy way to asynchronously load in assets or scenes at runtime without the usual hiccup! 3 | More details are written in the script. 4 | 5 | DiSCLAIMER: Does not work on web, since Threads are not supported 6 | 7 | # Installation 8 | Download and extract the .zip-File and drag "SceneLoader.gd" into your project. (You can also download this addon on the Godot Asset library) 9 | Create a new Autoload with "SceneLoader.gd" in Project > Settings > (Tab) Autoload, set the Path and click the ADD-Button. 10 | 11 | # Usage 12 | Once the script has been set as AutoLoad, you will be able to call SceneManager.load_scene("...") from any Script-File. 13 | 14 | * To queue a scene or asset for loading, type: 15 | 16 | `SceneLoader.load_scene("res://my_scene.tscn", { prop1 = "Hi!" })` 17 | 18 | * To be notified about the end of the scene loading, add this to your _ready() function: 19 | 20 | ```javascript 21 | func _ready(): 22 | SceneLoader.connect("on_scene_loaded", self, "do_scene_loaded") 23 | 24 | func do_scene_loaded(scene): 25 | # Do something with the scene 26 | pass 27 | ``` 28 | * The SceneLoader will pass an object with following structure to your do_scene_loaded function: 29 | 30 | ```javascript 31 | { 32 | path = path, 33 | loader = (ResourceInteractiveLoader), 34 | instance = (your scene instance), 35 | props = props (Properties you passed with the load_scene call) 36 | } 37 | ``` 38 | 39 | * You can now add the scene to the scene tree, examine the props and filter by path: 40 | 41 | ```javascript 42 | if scene.path == "res://my_scene.tscn": 43 | print(scene.props.prop1) 44 | add_child(scene.instance) 45 | ``` 46 | 47 | # Additional info 48 | 49 | SceneLoader.gd loads a scene in the background using a seperate thread and a queue. 50 | Foreach new scene there will be an instance of ResourceInteractiveLoader 51 | that will raise an on_scene_loaded event once the scene has been loaded. 52 | Hooking the on_progress event will give you the current progress of any 53 | scene that is being processed in realtime. The loader also uses caching 54 | to avoid duplicate loading of scenes and it will prevent loading the 55 | same scene multiple times concurrently. -------------------------------------------------------------------------------- /SceneLoader.gd: -------------------------------------------------------------------------------- 1 | # Loads a scene in the background using a seperate thread and a queue. 2 | # Foreach new scene there will be an instance of ResourceInteractiveLoader 3 | # that will raise an on_scene_loaded event once the scene has been loaded. 4 | # Hooking the on_progress event will give you the current progress of any 5 | # scene that is being processed in realtime. The loader also uses caching 6 | # to avoid duplicate loading of scenes and it will prevent loading the 7 | # same scene multiple times concurrently. 8 | # 9 | # Sample usage: 10 | # 11 | # # Copy & Paste this script and create and AutoLoad for it, then on your world 12 | # # manager copy & paste this snippet, make sure to replace the load_scene with 13 | # # a scene that exists in your project 14 | # 15 | #func _ready(): 16 | # SceneLoader.connect("on_scene_loaded", self, "do_scene_loaded") 17 | # SceneLoader.load_scene("res://myscene.tscn", { hii = "cool" }) 18 | # 19 | #func do_scene_loaded(scene): 20 | # # You can hook the instance name to run your specific per scene logic 21 | # # Example: parse the name for a substring such as "ITEM_" and then 22 | # # run your item specific logic 23 | # print(scene.path) 24 | # print(scene.instance.name) 25 | # print(props.hii) 26 | # -- 27 | # 28 | # Author: @November_Dev 29 | # 30 | 31 | extends Node 32 | 33 | var thread 34 | var scene_queue = {} 35 | var file = File.new() 36 | var cache = {} 37 | var awaiters = [] 38 | 39 | signal on_progress 40 | signal on_scene_loaded 41 | 42 | func _ready(): 43 | thread = Thread.new() 44 | thread.start(self, "_thread_runner", null) 45 | 46 | func _thread_runner(o): 47 | while true: 48 | OS.delay_msec(5) 49 | 50 | if scene_queue.size() > 0: 51 | for i in scene_queue: 52 | var err = scene_queue[i].loader.poll() 53 | call_deferred("emit_signal", "on_progress", scene_queue[i].path, scene_queue[i].loader.get_stage_count(), scene_queue[i].loader.get_stage()) 54 | 55 | if err == ERR_FILE_EOF: 56 | scene_queue[i].loader = scene_queue[i].loader.get_resource() 57 | scene_queue[i].instance = scene_queue[i].loader.instance() 58 | cache[scene_queue[i].path] = scene_queue[i] 59 | call_deferred("emit_signal", "on_scene_loaded", scene_queue[i]) 60 | scene_queue.erase(scene_queue[i].path) 61 | elif err != OK: 62 | print("Failed to load: " + scene_queue[i].path) 63 | scene_queue.erase(scene_queue[i].path) 64 | 65 | for awaiter in awaiters: 66 | if cache.has(awaiter.path): 67 | if awaiter.path == cache[awaiter.path].path: 68 | awaiter.loader = cache[awaiter.path].loader 69 | awaiter.instance = cache[awaiter.path].instance.duplicate() 70 | call_deferred("emit_signal", "on_scene_loaded", awaiter) 71 | awaiters.remove(awaiters.find(awaiter)) 72 | 73 | func load_scene(path, props = null): 74 | if !file.file_exists(path): 75 | print("File does not exist: " + path) 76 | return 77 | 78 | if cache.has(path): 79 | call_deferred("emit_signal", "on_scene_loaded", { path = path, loader = cache[path].loader, instance = cache[path].loader.instance(), props = props }) 80 | return 81 | 82 | if !scene_queue.has(path): 83 | scene_queue[path] = { path = path, loader = ResourceLoader.load_interactive(path), instance = null, props = props } 84 | else: 85 | awaiters.push_back({ path = path, loader = null, instance = null, props = props }) 86 | 87 | func is_loading_scene(path): 88 | return scene_queue.has(path) 89 | 90 | func clear_cache(): 91 | for item in cache: 92 | item.instance.queue_free() 93 | cache = {} --------------------------------------------------------------------------------