+2 votes

I'm trying to make a match3 game. I created a script called base_board_object.gd which is meant to be a base class for objects that are placed on the board. This script extends Node2D so that each of them has a position. I created a scene with one of those objects. it looks like this:

|-Node2D as a root
|--Sprite (called "sprite") as a child

I attached a script to Node2D:

tool
extends "res://Scripts/base_board_object.gd"

export(Color) var body_modulate setget set_color_for_sprite

func set_color_for_sprite(new_color): 
   body_modulate = new_color
   $sprite.modulate = new_color

and I can change the color of my sprite in editor as I intended. Then, I instanced four of those scenes in my main scene:

enter image description here

But now here's a problem. This line $sprite.modulate = new_color keeps returning that there's no "sprite" child:

Node not found: sprite res://Scripts/matchable_dolfon.gd:8 - Invalid set index 'modulate' (on base: 'null instance') with value of type 'Color'.

What I've tried is:

  • Check if "sprite" matches the name of the actual node - it does
  • try using get_node() instead of new syntax - no result
  • get rid of this line extends "res://Scripts/base_board_object.gd" and simply inherit from Node2D - no result
  • make sure that "Editable children" option is enabled in each instanced scene - still doesn't work

I can't see any reason why this might not work. I'd be glad if you could give me some hints.

asked Feb 1, 2018 in Engine by CosmicKid (26 points)
edited Feb 1, 2018 by CosmicKid

2 Answers

+3 votes

The issue here is that variables are initialized during (or a bit before? Can' recall) the _init() notification. Nodes enter the tree during the _enter_tree() notification and they can't even be guaranteed to have access to their children until the _ready() notification fires.

This is because every node calls _init() when first instantiated, followed by a series of calls as it's local scene becomes a part of the scene tree: each node, from the top down, calls _enter_tree() (resulting in a cascading downwards function call, so you know its parents exist already), and then _ready() is called when all children have been added and are ready (resulting in a cascading upwards function call, so you know its children now exist).

If you add the onready keyword to your property, then the variables will exist and be accessible because Godot Engine will wait until the ready call is ready-to-go before it attempts to initialize the property (which it will do just before it actually calls _ready()).

answered Feb 1, 2018 by Will Nations (173 points)

Thanks for your answer, I understand what you mean. I changed the line with export variable to export(Color) onready var body_modulate setget set_color_for_sprite. However, it didn't work which is quite strange assuming you're right. I was looking for a workaround and thanks to hilfazer's answer I eventually came up with this:

export(Color) var body_modulate setget set_color_for_sprite

func set_color_for_sprite(new_color):
   body_modulate = new_color
   if (Engine.is_editor_hint()):
      $sprite.modulate = body_modulate

func _ready():
   $sprite.modulate = body_modulate

which does what I want - I can change the color in editor and it's updated in real-time and then it's set when I play the scene without breaking. The only annoyance is that when I save the scene (control+s) it outputs the same error as the last time. It doesn't really change anything but it spams my output window a little.

EDIT: I can check child count using get_child_count() > 0 to get rid of that error but that looks pretty dirty. I'm wondering if this is a bug with onready.

I just tried this all for myself in my own editor.

I didn't really need any of my recommendations. I was able to update the color and prevent errors both at design time and runtime using the following...

- Node2D (script)
    - Sprite

Script:

tool
extends Node2D

export(Color) var color = Color(1,1,1,1) setget set_color

func set_color(p_color):
    color = p_color
    if has_node("Sprite"):
        $Sprite.modulate = color

Now, when I updated it to use the onready keyword and assumed that the node existed, I did get the desired functionality, but I would receive error messages only whenever I saved the scene (not whenever I just set the property in the editor). That might be a bug.

# Caused a bug on save
tool
extends Node2D

export(Color) onready var color = Color(1,1,1,1) setget set_color

func set_color(p_color):
    color = p_color
    $Sprite.modulate = color

That's exactly what happens to me too. I'll stick to has_node, it's much cleaner. Thank you for help!

+1 vote

Move assignment to sprite's property to _ready() function:

func _ready():
    $sprite.modulate = body_modulate

this function is called after node's children are added to scene so $sprite will not return a null.

answered Feb 1, 2018 by hilfazer (2,112 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.