I'm making a tower defence game where different towers shoots different projectiles. Each projectile has a range variable and I want to make sure that the turrets that shoot each projectile has the same range value.

Right now, I have turrets storing a PackedScene of the projectile they'll shoot. E.g.: "fireball tower" will have myProjectile pointing to a scene called "fireball"

To make sure that the tower has the same range as its projectile, I'm trying to do something like this:

func _ready():
    range = myProjectile.range

but this obviously does not work because Godot won't let me read variables from an uninstanced scene (PackedScene).

One possible way to work-around to this (that I don't love) would be instancing a projectile at _ready(), getting its range and then freeing the projectile. but this has the downside of triggering the events of the projectile's creation and destruction (playing sound/exploding) whenever the tower is built, and might interfere with future features that detect projectile creation/destruction

So, I was hoping that someone has a better idea of how to get a user-set variable like "range" from the PackedScene directly without having to instance it.

You can't get data from something that doesn't exist. A packed scene is just a file on your hard-drive. So in order for it to be in the game, you have to instance it. In any case, the projectile can handle it's own speed (this is usually what you want, objects should control themselves as much as possible). If the turret needs data, it can get it immediately after the projectile is fired. But this is largely not needed if you code some objects are independent.

The advantage of coding the objects as inter-dependent would be that I don't need to change multiple objects when I need to change one. E.g.: if I decided to give a projectile more range, the dependency between turret and projectile means that I don't need to find all the turrets that uses the projectile and change their range values manually.

Right now, I'm migrating some of the on-creation/destruction functionalities of projectiles from _ready() to _setUp(), and only calling _setUp() when I wanted to actually use the projectile in game rather than just as a vehicle for information

    GE100 what i would do is have a global variable (or constant) that holds your range and then have your object reference it for its range whenever it is instanced. This would only be a small change to your code, you'd just have to make an autoload script. Let's call it Globals and:

    Put this in your global script:

    const RANGE_DEFAULT = ### whatever value here
    const RANGE_VARIANT = ### if you want additional values, if your projectile "powers-up" or changes states

    Then in your code:

    func _ready():
          range = Globals.RANGE_DEFAULT

    Alternatively, range in the globals can be a variable and you can change it when need be, or set different values for when your projectile changes (like a "Hyper" setting, for example).

    Then in your projectile:

    func _ready():
          myRange = Globals.RANGE_DEFAULT
          ### and if you wanted a different range for your projectile:
          if _hyper == true:
               myRange = Globals.RANGE_VARIANT