0

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 Jun 04 '21 at 18:17
  • 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 Jun 04 '21 at 18:23
  • 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 Jun 04 '21 at 18:37
  • 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 Jun 04 '21 at 19:02
  • 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 Jun 04 '21 at 19:19

1 Answers1

0

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).

Theraot
  • 31,890
  • 5
  • 57
  • 86
  • Thank you so much for taking the time to help! I think it's pretty obvious that 25+ years of web development doesn't translate 1 to 1 with game development! Two different things! I have a lot to learn! I did end up putting it in the autoloader and it _does_ work but it just feels... I don't know, not elegant, that way I guess? This really is my first "real" project with Godot so I'm too worried about making it perfect (yet) :D Again thanks for your time/help! – JeremyPage Jun 05 '21 at 02:16