setget in tool script throws error

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By twinpixel
:warning: Old Version Published before Godot 3 was released.

Hi guys,
I’m following Andreas Esaus tutorial on the tool mode:

Here is my code:

tool
extends Area2D
export(String, "A", "B") var keyname = "A" setget set_newkeyname

func _ready():
	if !get_tree().is_editor_hint():
		print(keyname)

func set_newkeyname(newkeyname):
	keyname = newkeyname
	if keyname == "A":
		get_node("Sprite").set_frame(0)
	elif keyname == "B":
		get_node("Sprite").set_frame(1)

Switching the keyname/frame within the editor works as expected, but as soon as I press play scene an error is thrown:

Attempt to call function ‘set_frame’ in base ‘null instance’ on a null instance.

What am I doing wrong?

tool scripts are confusing because they work differently than normal scripts. Maybe get_node() doesn’t work inside the editor? I’d look up some other tool scripts for reference.

batmanasb | 2016-04-11 10:51

:bust_in_silhouette: Reply From: Bojidar Marinov

The problem is that when the scene is instanced (e.g. PackedScene::instance is called, which happens for all scenes before they are displayed), it would start setting all properties of the nodes… until it gets to your tool-scripted node, and attempts to set its property. At that point, neither _enter_tree nor _ready has occurred, which means that get_node is still not able to do work, thus get_node("Sprite") results in null.

“In theory, this sounds good,” you say, “but what about in practice? How can this be fixed?”

Well, the fix is rather simple, you can just check has_node’s output, like so

if !has_node("Sprite"):
    return

Another option would be to set a variable to true in _ready, and calling all setgets right afterwards:

var ready_called = false
func _ready():
    ready_called = true
    set_newkeyname(keyname)
    # Or `self.keyname = keyname`/`set("keyname", keyname)`

func set_newkeyname(newkeyname):
    if ready_called:
        # Do stuff...

There are a few other options, but I guess that is enough for an answer ;-)

That stuff is hard to get :slight_smile:
Anyways, thanks Bojidar. It’s working like a charm, now.

The code looks like this:

tool
extends Area2D
export(String, "A", "B") var keyname = "A" setget set_newkeyname

func _ready():
    if !get_tree().is_editor_hint():
        print(keyname)

func set_newkeyname(newkeyname):
 	if !has_node("Sprite"):
	    return   
 
    keyname = newkeyname
    if keyname == "A":
        get_node("Sprite").set_frame(0)
    elif keyname == "B":
        get_node("Sprite").set_frame(1)

twinpixel | 2016-04-11 12:56

I would like to add that in the case you don’t have a specific node you are interracting with (for me the issue was with get_tree() ) you can just used the Node method is_inside_tree()

Geaxle | 2016-08-26 08:11

:bust_in_silhouette: Reply From: Geaxle

To add to Bojinar’s very good answer, I would like to add you can also use the Node method is_inside_tree() as a check. This will work even if you don’t have to check for a specific node.