+6 votes

Hi guys,
I'm following Andreas Esaus tutorial on the tool mode:
https://www.youtube.com/watch?v=vsxB4L6rufo

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?

in Engine by (196 points)

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.

2 Answers

+7 votes
Best answer

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

by (1,594 points)
selected by

That stuff is hard to get :)
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)

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 gettree() ) you can just used the Node method isinside_tree()

+4 votes

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.

by (94 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 Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.