Autoloads versus internal nodes

    For example, what if a developer is building a platformer and they want to collect coins that play a sound effect? Well, there’s a node for that: the . But they notice during their testing that if they “play” an AudioStreamPlayer while it is already playing the sound, then the new sound interrupts the first sound, terminating it before it can play.

    Users tend to think the best solution is to make the whole system smarter by making a SoundManager autoload node. It generates a pool of AudioStreamPlayers that cycle through as each new request for sound effects comes in. They then make this SoundManager an autoload so that they can access it from anywhere with SFX.play(“coin_pickup.ogg”). Little do they know, they’ve invited a great many complications into their code.

    • global state: One object is now responsible for all objects’ data. If SFX has errors or doesn’t have an AudioStreamPlayer available, everything will break.
    • global access: Now that any object can call SFX.play(sound_path) from anywhere, there’s no longer an easy way to track where an SFX bug began.
    • global resource allocation: If all objects’ data and processing is centralized from the start, then one must either…
      1. risk under-allocating resources which might lead to faulty behavior.
      2. over-allocate resources and use more memory/processing than it needs.
        • Ex: Have an arbitrarily large number of AudioStreamPlayers, with many of them idling away and not doing anything.
      3. have each object that needs an AudioStreamPlayer register exactly how many it needs and for which sounds. This defeats the purpose of using a 3rd party though; it is now coupled to each object, just as a child node would have been. One has added an unnecessary middleman to the equation.
    • Each scene manages its own state information. If there is a problem with the data, it will only cause issues in that one scene.
    • Each scene knows exactly how many resources it needs for the task it performs. No waste of memory or processing due to a lack of information.

    The typical justifications for the Autoload include, “I have common Xs that involve many nodes across many scenes, and I want each scene to have X.”

    If X is a function, then the solution is to create a new type of Node that deals with providing that feature for an individual scene or node subtree.

    So when should one use an autoload?

    • Static Data: if you need static data, i.e. data that should be associated with a class (so there is only ever one copy of the data), then autoloads are good opportunities for that. Static data doesn’t exist in Godot’s scripting API, so autoload singletons are the next best thing. If one creates a class as an autoload, and never creates another copy of that class within a scene, then it will function in place of a formal singleton API.
    • Convenience: autoloaded nodes have a global variable for their name generated in GDScript. This can be very convenient for defining objects that should always exist, but which need object instance information. The alternative is to create a namespace script: a script that’s purpose is only to load and create constants to access other Script or PackedScene resources, resulting in something like .
      • Note that the introduction of script classes in Godot 3.1 questions the validity of this reason. With them, one can access scripts using an explicit name from GDScript. Using an autoload to get to a namespace script becomes unnecessary, e.g. MyScriptClass.MyPreloadedScript.new().

    If the singleton is managing its own information and not invading the data of other objects, then it’s a great way to create a “system” class that handles a broad-scoped task. For example a targeting system, quest system, or dialogue system would be great use cases of singleton implementations.