All Questions

Community

R
RealityStop

PackedScene preload vs direct reference

I looked around and didn't find an answer for what the difference is between the various ways of referencing and loading scenes.  Can you provide insight on what the difference between these four are (and any I haven't bumped into yet)?


  1. export var other_scene: PackedScene
    Handily provides a picker in the inspector, but creates a hard reference that will load the asset immediately.  Can cause circular load dependencies.  Useful for instantiating projectiles, etc.   Multiple loads are cached by the engine, so only one load occurs?
  2. export var scene_path: String
    References via string, so and manually loading the dependency later.  Avoids circular loading by relying on the code to resolve the "when".  Not sure on caching of resources if multiple occur.
  3. const FadingSprite: PackedScene = preload("res://Objects/FadingSprite.tscn")
    Immediately loads from the string before the scene can begin playing (whenever the script is first run?).  Can reduce performance hit if it is loaded before gameplay begins.  Multiple loads are cached by the engine, so only one load occurs.   -- what happens if a preload is in a delay-loaded resource?  You still get a performance hit right?  Or are ALL preloads handled before the game even begins?
  4. const FadingSprite: PackedScene = load("res://Objects/FadingSprite.tscn")
    As far as I can tell, the same as #1, but more fragile.


I'm sure there are more nuances than I am aware of, but it seems most discussion I could find revolved around comparing #3 and #4, not all of them together.  Is #1 fairly new?  I can't find any mention of it in the 3.0 timeframe or earlier.  What have I gotten wrong, and what have I missed?

  • GDQuestions replied

    You're correct.

    In general, any time you make multiple load operations on any resource (by path or reference to a path like with an exported PackedScene variable) Godot will recognize the resource was already loaded.

    So it doesn't matter which approach you use if your concern is to only read the resource from the disk once.

    Preload loads when the script compiles, so yes, when the script loads and after it got parsed. That can be before instantiating the scene, which means before gameplay. But you do need the script in question to get parsed and compiled.


    Exporting variables is not new, you can do it since the early days of Godot. If you're wondering why you'd use a constant instead, typically we favor explicit references when the thing we're trying to load is specific and in the same directory.

    I'm not fond of using general code when making something very specific like visual effects, a specific kind of enemy... unless the project calls for a lot of asset and code reuse. That's because a teammate can easily reuse the scene in an unintended way, and a change in the source can break the unintended use.

    It's certain though that the editor will track when you rename a resource and update the reference when using the exported variable.

    At the same time, with a hard-coded constant you're sure to get a clear error message at compile time. Even when working in the script editor.

    In the example above, the path is absolute, and I think it's due to a limitation we faced in an older Godot version. But you can and should use a relative path whenever possible.

    These days, we write almost all those preloads like const SomeType := preload("FileInSameDirectory"). We group related resources in the same directory.

    Using constants with a loaded script is also the only way to register a type locally in a script, something you want in some cases. One case is when making plugins or assets for other game developers.

    Let me know if you have more questions.

  • R
    RealityStop replied

    Thanks for diving into this with me!


    I think that covers most of it, but I'm still not solid on what the difference between the exported PackedScene and preload/load is.  I suspect that an exported PackedScene is editor-integrated syntactic sugar for preload?  (That's what I was asking if it was new, all old topics seem to just look at preload/load).  If it is NOT functionally equivalent to a preload, what are the differences?


    I was not aware that preload/load verified the resource path, so I was afraid the string-based paths would be fragile and more susceptible to breaking at runtime, but I can see that as long as the script is loaded in the editor, that's not the case.  (and trying to load a stale path causes a debugger break, rather than just logging to the console)


    Let me see if I can summarize your information:

    export var other_scene: PackedScene

    Best used when making reusable components that will need to load different resources.  

    export var scene_path: String

    Fragile reference to a resource that must be defer-loaded.  Should only be used to avoid circular load dependencies.

    const FadingSprite: PackedScene = preload("res://Objects/FadingSprite.tscn")

    Loads as soon as the script class is loaded, frequently before gameplay begins.  Best used for scenes/resources that gameplay will need to be immediately available to prevent gameplay hiccups.  Preferred for hardcoded resources.

    const FadingSprite: PackedScene = load("res://Objects/FadingSprite.tscn")

    Background loads during gameplay, to avoid needing to load *everything* in advance.  Could be used to bring additional audio tracks into memory, etc.


    Just trying to make sure I've got it, and get more clarity on the behavior of an exported PackedScene.  Thanks!

  • GDQuestions replied

    Yes, when you assign a resource to a variable, it needs to be loaded. When you do this through the editor, it's equivalent to doing load("path/to/file").

    Note it's only preload that checks the path at compile time. Because load executes only when the code runs and takes a non-constant argument, it won't raise errors until you call it.

    Once again, your observations are correct.

    export var other_scene: PackedScene
    

    Regarding this, I'd say it can also be a matter of consistency. One case I'd choose this is defining it as a convention for a given game and team, so that designers working on the project know that they're expected to configure and update most nodes that way. It has the advantage of using Godot's dependency system to track moved files. But it's a decision that's up to you.

    For me, it's whatever works best for the team.

    And note that load() pauses the code's execution, it doesn't run in a separate thread or in the background. It's fine for small resources, but if you need to load big files, you'll want to look into the ResourceLoader class: https://docs.godotengine.org/en/stable/tutorials/io/background_loading.html