Search code examples
godotgdscriptgodot4

How can I dynamically instantiate nodes from a scene using a constructor?


I have a game_board scene with only a root Node2D and no children. I wish to programmatically create playing slots on this board. These slots are to be instantiated from a separate scene file.

Since GDScript doesn't have a constructor, I wrote a static constructor initializing the required members (currently just a slot_id: int)

game_board.gd

extends Node2D

const play_slot_scene = preload("res://scenes/PlaySlot/play_slot.tscn")

func _ready():
    if not play_slot_scene.can_instantiate():
        push_error("Couldn't instantiate play slot")

    var firstSlot: PlaySlot = play_slot_scene.instantiate()
    firstSlot.slot_id = 0;
    
    add_child(firstSlot)
    
    for n in range(1,7):
        var nextChild = firstSlot.constructor(n)
        add_child(nextChild)

play_slot.gd

class_name PlaySlot
extends Node2D

const self_scene = preload("res://scenes/PlaySlot/play_slot.tscn")

@export var slot_id: int

static func constructor(id: int = 0)-> PlaySlot:
    var obj = self_scene.instantiate()
    
    obj.slot_id = id
    
    return obj

The code errors out at var firstSlot: PlaySlot = play_slot_scene.instantiate() because firstSlot is a Node2d obj (not an instance of PlaySlot class).

If I remove the static typing, the next line fails because slot_id does not exist on Node2D.

How do I instantiate these nodes with the right class? TIA


Solution

  • At the _ready() function in the game board, instanced child scenes have not bound to the attached script. In essence, the instanced node is Node2D and can't bind slot_id

    The right way

    A better way do achieve what I was trying is to simply use the static constructor(). This avoids preloading the PackedScene.

    game_board.gd

    
    extends Node2D
    @onready var play_slot_grid: GridContainer = %PlaySlotGrid
    
    func _ready():
        generate_play_slots()
    
    func generate_play_slots():
        for i in range(0,9):
            var nextChild: PlaySlot = PlaySlot.constructor(i)
            
            nextChild.name = "PlaySlot" + str(i)
            nextChild.add_to_group("PlaySlots")
            play_slot_grid.add_child(nextChild)
    

    play_slot.gd

    class_name PlaySlot
    extends Control
    
    # Nodes
    const self_scene = preload("res://scenes/PlaySlot/play_slot.tscn")
    
    # Members
    @export var slot_id: int
    
    static func constructor(id: int = 0)-> PlaySlot:
        var obj = self_scene.instantiate()
        obj.slot_id = id
        return obj
    

    Using a PackedScene

    However, if you must do things using a PackedScene, you need to have an instance ready with instantiate()

    game_board.gd

    
    extends Node2D
    @onready var play_slot = preload("res://scenes/PlaySlot/play_slot.tscn").instantiate()
    
    func _ready():
        # Doesn't complain because play_slot is not Node2D here
        play_slot.slot_id = 123