0
votes

This is difficult to explain with typing...

I have a GameController scene (Node2D) that holds 3 instanced scenes within:

  • Mouse (scenes/Mouse.tscn) - this just swaps the mouse cursor for a custom graphic
  • HeaderBar (scenes/HeaderBar.tscn) - this is the score/label that just sits up top
  • Messages (scenes/Messages.tscn) - this is the "popup" message box that displays text to the user

In the main scene (Level1.tscn) I instance the GameController scene and it "works" fine. The header bar is there with the score/label and the custom mouse cursor is there and the message box is there (but I have it hidden by default but if I toggle its visibility in the remote it will show up).

Here's where my confusion comes in...

If I attempt, in the GameController script, to manipulate any of those nodes in the GameController scene (the mouse, header, messages) they return as null and will throw an error. For example; if I try to update the score in the $HeaderBar/Control/score I get the following:

Invalid set index 'text' (on base: 'null instance') with value of type 'String'.

The code completion will autofill the node names as I type them (so it recognizes them in the group) but any attempt to reference/use them in script throws similar errors to above.

I'm very new to Godot so I'm sure it's just some misunderstanding I have on how this type of thing works but I'm stumped! Any insight is much appreciated!

UPDATE

I will try to simplify my explanation a bit (I have made some changes). Okay here is the object script:

extends StaticBody2D

onready var main = load("res://scenes/MainGame.gd").new()

func _ready():
    pass

# mouse [left] clicked on object
func _on_trigger_input_event(viewport, event, shape_idx):
    if Input.is_action_just_pressed("left-click"):
        main.display_message("you [left] clicked on the object!")

the call to main.display_message() works. It does call the function but here is that function in the MainGame.gd

extends Node2D

onready var box = $Message/Control
onready var label = $Message/Control/Label


func _ready():
    # hide the mouse cursor (will use custom cursor)
    Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
    
func display_message(msg):
    label.text = msg
    box.visible = true

It errors out because label (and box) are null. If I call display_message from the _ready function (in the MainGame.gd) it works as it should. Call it from outside (in the Object.gd) and the nodes are null for some reason.

1
What comes to mind: it is either a typo or there is something removing the node. - Theraot
I have re-typed it about 200 times :D So I don't think it's a typo. I, too, had the thought that something was removing the node but I'll be darned if I can find out what! Another piece of information I just realized is that if I do a "print" statement in the _ready function it's printing 5 times (it's also doing the same behavior if I do a print statement in the _unhandled_input function as well (for right-click on the mouse for example) - JeremyPage
That sounds like you have multiple instances. Perhaps some of them have these nodes and other don't. Is there some other place where you instance them? Are you - for example - using autoloads for any of this? - Theraot
I moved all the functionality from the GameController script into the Messages.gd script instead. extends CanvasLayer onready var message_shell = $messageShell onready var message_label = $messageShell/messageBox/Label func _ready(): print(message_label.text) func display_message(msg): print("yes boy... yes!!") message_label.text = str(msg) # message_shell.visible = true the _ready print statement works. When I call the display_message function (from the gamecontroller script) it errors (null instance) again. Not sure how to get that to format corectly... - JeremyPage
Ok, if I go with the idea that you are instancing multiple times... You are probably calling display_message on an instance that you didn't add to the scene tree. How does the code you call it from looks like? Also, add the code the question, you can edit it. - Theraot

1 Answers

0
votes

This instances the scene as expected:

onready var main = load("res://scenes/MainGame.gd").new()

However, this code will run when the instanced scene is added to the scene tree:

onready var box = $Message/Control
onready var label = $Message/Control/Label


func _ready():
    # hide the mouse cursor (will use custom cursor)
    Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)

Yes, both onready variables and _ready will run when this is added to the scene tree.

And when do you add main to the scene tree? I don't see where.

Then when you do this:

main.display_message("you [left] clicked on the object!")

Both box and label will still be null (which is the default value).

Thus, add it to the scene tree, for example:

func _ready():
    add_child(main)

Except that does not work either, does it? Look at the code again:

onready var main = load("res://scenes/MainGame.gd").new()

You are instancing a script. A script. Not a scene. It will not have its children nodes and so on. You want to instance a scene, for example:

onready var main = load("res://scenes/MainGame.tscn").instance()

However, I find it odd that you are doing this in a StaticBody2D. I don't think it makes sense that MainGame belongs to a StaticBody2D.

I suspect, you are instancing MainGame in multiple places expecting it be the same instance. But it is not the same instance, it is a new instance.

Here is where I suggest you make MainGame into a autoload, so there is a single instance that you can access from anywhere. However, perhaps that is not right either. Perhaps you should use a Signal Bus. See Why does this variable keep returning to its original value? which had a similar problem (they where trying to open a door, you are trying to show a message, but still).