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