Skip to content

Instantly share code, notes, and snippets.

@derkork
Last active December 14, 2022 07:50
Show Gist options
  • Save derkork/58f354aeaed8cb03fa289999c9de23f9 to your computer and use it in GitHub Desktop.
Save derkork/58f354aeaed8cb03fa289999c9de23f9 to your computer and use it in GitHub Desktop.

Quick Tip: Refer to nodes the right way

Hello Godotneers! Nodes are the heart of Godot and using them properly in your code will help you a lot in creating and maintaining your code. Here are three ways of referring to nodes that will instantly make your game code better.

One: Create node references in onready variables

In the Godot Discord channel I very often see code like this:

func _process():
   $SomeNode.do_something()
   make_magic()

func make_magic():
   $SomeNode.make_magic()

Such code is both inefficient and easy to break. It is inefficient because the $ notation is just a short way of calling the get_node function so every time you write $SomeNode you actually call get_node("SomeNode"). Every such call will go through the tree to fetch the node. So if you call this 60 times a second in your _process function, you are wasting CPU power for nothing.

This code is also easy to break. Imagine you would rename or move that node in your scene. This would now break all the places in the code where you wrote $SomeNode. Fixing all 30 places where you have that is not fun.

To fix both problems, create node references in your _ready method or even better use the onready variable feature:

onready var _some_node = $SomeNode

func _process():
   _some_node.do_something()
   make_magic()

func make_magic():
   _some_node.make_magic()

Now you don't call get_node 60 times a second and if you rename your node or move it elsewhere, you only have to fix a single place.

Two: Verify that the node exists

Godot's scenes can be edited at any time and the editor will not fix your code for you if you move nodes around or rename them. So imagine you moved some node around that you for some function of your game:

func some_function():
    code.here()
    if even_more.code() == "here":
	return
    else:
	_my_node.do_something()

When will you notice that the node is no longer there? When your game calls some_function. This is very late in the process and if you happen to call that function rarely (maybe because it is in some settings dialog that is only occasionally open) you may never find this problem. But the player will surely notice that somehow the settings dialog isn't working.

To fix this, you want to check that the node exists as early as possible. While get_node prints a warning when the node does not exist, this is easy to overlook during development. A better way would be something like this:

var some_node

func _ready():
   some_node = $SomeNode
   assert(some_node != null, "SomeNode does not exist anymore!")

The assert function will actually stop your game when the node is not there. This way you can immediately see and fix the problem. However writing that much code for every node you need is very tedious, so you better write yourself some helper function for this.

class_name NodeUtils

static func at_path(root:Node, path:String) -> Node:
    var result:Node = root.get_node_or_null(path)
    assert(result != null, "Referenced node %s not found." % [path])
    return result
	

Then you can use this in your onready variable declaration like this:

onready var some_node = NodeUtils.at_path(self, "SomeNode")

Now your game will immediately halt when the node is not there anymore and you can fix the problem quickly.

Three: Avoid nested paths to your nodes

This one happens very often when you do UI. You have a structure of some nested container nodes and need to get access to a text field in it. Lets say you have a login dialog that has this structure:

  • LoginDialog (Control)
    • VBoxContainer (VBoxContainer)
      • UsernameLineEdit (LineEdit)
      • PasswordLineEdit (LineEdit)
      • HBoxContainer
        • LoginButton (Button)
        • CancelButton (Button)

Now you want to refer to your username and password line edits. Using tips one and two you write code like this:

# This is attached to the top 'LoginDialog' node
class_name LoginDialog

onready var username_line_edit = NodeUtils.at_path(self, "VBoxContainer/UsernameLineEdit")
onready var password_line_edit = NodeUtils.at_path(self, "VBoxContainer/PasswordLineEdit")

This is okay but then you think you could make the dialog look a bit nicer by adding a panel background and centering it:

  • LoginDialog (Control)
    • CenterContainer (CenterContainer)
      • PanelContainer (PanelContainer)
        • VBoxContainer (VBoxContainer)
          • UsernameLineEdit (LineEdit)
          • PasswordLineEdit (LineEdit)
          • HBoxContainer
            • LoginButton (Button)
            • CancelButton (Button)

Now your code will need to adapt to this:

class_name LoginDialog

onready var username_line_edit = NodeUtils.at_path(self, "CenterContainer/PanelContainer/VBoxContainer/UsernameLineEdit")
onready var password_line_edit = NodeUtils.at_path(self, "CenterContainer/PanelContainer/VBoxContainer/PasswordLineEdit")

This is both hard to read and hard to write. Also your code will break whenever you reorganize the node structure. It is much better to avoid using a path.

Godot 3.5 or later: use Scene Unique Nodes

Starting with Godot 3.5 you can use a feature called Scene Unique Nodes for this. In the editor you mark the node of interest as a unique node:

This will give the node a little % prefix and you can get the node quickly with a call to:

class_name LoginDialog

onready var username_line_edit = NodeUtils.at_path(self, "%UsernameLineEdit")
onready var password_line_edit = NodeUtils.at_path(self, "%PasswordLineEdit")

This will give you the node no matter where it is located in the tree. If you are using Godot 3.5 or later this is the easiest and most secure way to retrieve deeply nested nodes in your code.

Godot 3.4 or earlier: use find_node

If you are still using Godot 3.4 or earlier, then you cannot use Scene Unique Nodes. As an alternative, you can use Godot's find_node function, which finds a node with the given name anywhere in a tree:

class_name LoginDialog

onready var username_line_edit = find_node("UsernameLineEdit", true, false)
onready var password_line_edit = find_node("PasswordLineEdit", true, false)

This will work no matter where in the tree the two nodes are. You can move them around and you will not have to modify your code. However you just lost the verification that the node is actually there, which NodeUtils.at_path gave you. To fix this, you can write yourself another helper function in the NodeUtils class:

static func with_name(root:Node, name:String) -> Node:
	var result := root.find_node(name, true, false)
	assert(result != null, "Referenced node %s not found." % [name])
	return result

Using this helper function you can now write:

class_name LoginDialog

onready var username_line_edit = NodeUtils.with_name(self, "UsernameLineEdit")
onready var password_line_edit = NodeUtils.with_name(self, "PasswordLineEdit")

And there you have it, three ways referring to nodes that will make your game code instantly better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment