Scripts won't work after being attached to node via code

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By EliTheBeeli

I’m trying to make a main menu that allows the player to choose between either a human or computer opponent. I have an Opponent scene that can have either one of two scripts: Human or Computer. I also have a Main Menu scene with two buttons (Human or Computer), and a Level scene. To communicate between the two scenes I have a Global script that’s AutoLoaded, which has a variable OpponentType that = “Computer” by default.

Buttons:

func _on_HumanButton_pressed():
    get_node("/root/GlobalScript").OpponentType = "Human"
    get_tree().change_scene("res://Level/Level.tscn")

func _on_ComputerButton_pressed():
	get_tree().change_scene("res://Level/Level.tscn")

Level:

func _ready():
    if(get_node("/root/GlobalScript").OpponentType == "Human"):
	    $Opponent.set_script(load("res://Player/Human.gd"))
    else:
	    $Opponent.set_script(load("res://Player/Computer.gd"))

The scripts are loaded to Opponent as intended (checked via Remote while running the game), but Opponent still does nothing. What step of my process is wrong? Happy to answer clarifying questions.

:bust_in_silhouette: Reply From: a0kami

TL;DR:

Manually call the post-initalise and processing functions (EXCEPT _init):

func _ready():
    const opponent_type = get_node("/root/GlobalScript").OpponentType
    $Opponent.set_script(load("res://Player/Human.gd" if opponent_type == "Human" else "res://Player/Computer.gd"))
    $Opponent._ready()
    $Opponent.set_process(true) # if you have processing logic
    $Opponent.set_physics_process(true) # if you have physics logic

In-depth

I assume the key here is the node tree, that’s basically through this the engine handles all the interconnection between elements.

From Node’s documentation:

Once all nodes have been added in the scene tree, they receive the NOTIFICATION_READY notification and their respective _ready callbacks are triggered. For groups of nodes, the _ready callback is called in reverse order, starting with the children and moving up to the parent nodes.

The thing is, you are attaching the script as a Reference in the “Level” _ready() function because, in _init() the child “opponent” still doesn’t exist. But attaching a script to that child, it’s too late for the tree to propagate the NOTIFICATION_READY.
If you try to check what happens on a attached script such as:

func _init():
	print("INIT")

func _ready():
	print("READY")

func _process(_delta):
	print("PROCESSING")

You will notice only _init() gets called. Which means the script actually gets attached, just it’s waiting for your input to get “started”.

— Debugging process started —
[…]
INIT

You actually have to called the _ready() function yourself ($Opponent._ready()), the script will then post-initalise itself, BUT it will still not be processed:

— Debugging process started —
[…]
INIT
READY

For some reason the processing functions are disabled.
You will have to tell the node it should be processed each frame with set_process(true) ($Opponent.set_process(true)).
Additionally, the physics process is a different function, so if you have some physics to process you will also have to call set_physics_process(true) ($Opponent.set_physics_process(true)).

— Debugging process started —
[…]
INIT
READY
PROCESSING
PROCESSING
PHYSICS_PROCESSING
[…]

Worked like a charm. Thank you!

EliTheBeeli | 2021-08-18 00:54