Handling Variables in Instantiated Scenes?

Godot Version

4.2.2

Question

Hi all,

I am trying to make a very modular item block scene that will allow a multitude of different items to be spawned into the game when activated. For example, I may place one block in the scene that will spawn a healing item when hit, and another that will spawn a coin. I basically want to be able to drag-and-drop any item into the block.

I was able to get this part done using packed scenes with the code below; if I want it to spawn a powerup, I can drag the powerup scene into the inspector, or if I want it to spawn a coin, I can drag the coin scene in. However, I hit a limitation, in that I cannot control the varaibles for the object I want to spawn this way. I need to be able to specify the object and set the variables depending on what object is specified, e.g if I specify a power-up I need to be able to set the healing factor, or if I spawn a coin I need to be able to specify the score value.

Are there any alternatives to packed scenes that will allow you to set the variables of a scene in the inspector, or tricks you can use to get this working? Basically, I don’t want to create a bunch of different block objects for each item; I’d rather have one block object that can spawn any item, and allow me to set the variables for them.

Here is the code I have so far:

extends StaticBody3D

@export var contained_item: PackedScene ##Need a way to edit the variables of this object in inspector

func _on_break_zone_body_entered(body): ##Check if item spawn area is hit
	if body.is_in_group("Player"): ##
		spawn_item()

func spawn_item():
	var item = contained_item.instantiate()
	item.position = position + $SpawnArea.position
	get_parent().add_child(item)

Use custom Resources

For example:

class_name Item extends Resource


@export var scene:PackedScene


func spawn(position:Vector2) -> Node:
    if is_instance_valid(scene):
        var item = scene.instantiate()
        item.position = position
        
        return item
    else:
        return null
class_name Coin extends Item


@export var value:int


func spawn(position:Vector2) -> Node:
    Global.score += value
    return super.spawn(position)
class_name PowerUp extends Item


enum PowerUpType {
    INVENCIBLE, DOUBLE_JUMP, FLY
}


@export var type:PowerUpType


func spawn(position:Vector2) -> Node:
    Global.player.apply_powerup(type)
    return super.spawn(position)

And then do something like:

extends StaticBody3D


@export var item:Item


func spawn_item():
    var it = item.spawn(position + $SpawnArea.position)
    if is_instance_valid(it):
        get_parent().add_child(it)
1 Like

This helps a lot, some of my research mentioned using Resources to achieve this, but I needed that example. So in short, it looks like the solution is:

  1. Create a generic “Item” resource, that contains a PackedScene variable and a method to spawn it.
  2. For any scene you want to spawn, create additional resources that extend the generic Item, and contains variables that correspond to the ones for the scene you want.
  3. In my case, I think I had to copy/paste this code into every resource, in order to actually apply the variables to the object being spawned and spawn the object. I’m wondering if this is the best way to go about this. Example:
func spawn(position:Vector2) -> Node:
    if is_instance_valid(scene):
        var item = scene.instantiate() #copied from item class_name
        item.position = position #copied from item class_name
        item.var1 = var1
        item.var2 = var3
        item.var3 = var3 #add lines for every variable you want to apply to the object being spawned
        return item
    else:
        return null

I also found an add-on for Custom Packed Scenes which was able to get very similar results, though I may elect to continue using the resources going forward, as that seems to be recognized as the most reliable way to achieve this kind of goal.

Thank you again!

One last thing you can do: instead of creating var1, var2, and var3 variables, you can just make an array of variables to pass in. Then, you can have a variable number of passed in values. The UI for adding elements to an array in the editor is pretty good, if you want to try that.

For multiple variables:

@export inputs := []

func spawn(position:Vector2) -> Node:
    if is_instance_valid(scene):
        var item = scene.instantiate() #copied from item class_name
        item.position = position #copied from item class_name
        
        for i in inputs.size():
            item.set("var" + i, inputs[i])
            #Remember for statments are 0 indexed, so start with var0 and go from there

        return item
    else:
        return null