Skip to content

Latest commit

 

History

History
97 lines (77 loc) · 105 KB

best_practices.md

File metadata and controls

97 lines (77 loc) · 105 KB

Advices

Don't reference scenes / resources in code by string

If you reference a path anywhere in your code using a string, you'll have to go through every script and change it, and Godot won't help you. It'll just throw an error at runtime when it can't find the file. In this case I reccomend using a text editor to find and replace on all of your files. This is a big reason to reference scene and resource paths using export var resource_inst : Resource rather than using Strings.

Dividing your content into scenes, running scenes on their own, and why you should NEVER use get_parent()

Any group of nodes you want to have more than one instance of should be its own scene. For instance, the player should be its own scene, areas should have their own scene, enemies should have their own scene, and bullets should have their own scene. Dividing things up like this has implications for how you structure your file system, because you might need to reference the path to other scenes. So it's good to keep scenes that are instances of other scenes in sub-folders and group them in whatever way makes sense.

You can run scenes on their own with the 'Play Scene' button at the top right of the editor. Try it and watch the remote tab. Godot will treat it as though the scene you have open is the main scene. Your autoloads will still be there, but none of your other nodes. This is why you should try to avoid using get_parent(). If your nodes only know about their children that are guaranteed to exist at runtime, you can break any branch of the tree off into its own scene and run it to test things out without any problems.

If you use get_parent(), your logic will behave differently when running the scene on its own versus running your actual game.

The lie of the change_scene() function, what it actually does, and how to think about scenes

When you use change_scene(), Godot frees the main node and replaces it with the nodes in whatever scene you pass it. So if you want to 'change scenes', you can use change_scene(), but know that it is replacing everything you have loaded other than autoload singletons. No matter where in the tree you called it from. The bigger your game gets, the more data you will need to pass between scenes, and storing and passing tons of data between scenes isn't something you want to be doing with AutoLoads.

Never use change_scene() because you always want the Main node and others around to pass data between things.

For example you can have a 'main_menu' and a 'game_world' scene, the first being a Control node and second being Node2D or Spatial for 3D. Then when you want to go from your main menu into the game, you can free the main menu node and make an instance of the game world. You have 'changed scenes', from the main menu to the game world, but you still have your "Main" node. This has the added benefit of letting you do any other setup you want in between changing scenes, which wouldn't be possible if you had freed the entire scene tree.

How to get nodes, and why you should never use get_node("PathToNode")

There are a couple ways to reference child nodes in a script. Two very common ones are:

get_node("PathToNode")
$PathToNode

There's a better way to use get_node() though:

var path_to_node := NodePath("PathToNode")
get_node(path_to_node)

Although using "$" is usually still preferable when you know exactly which node you want. The problem with using string literals is that they are imperfect. You can type in anything you want and the compiler/interpreter will accept it until it has to deal with with your messy typos and by then it's already too late.

The use of "$" isn't a string literal, but it isn't checked by the compiler either, it just converts everything after it into a node path object and calls get_node() during runtime. You can even just change a node's name using get_node("PathToNode").name="NowIDontWorkAnymore".

It's better to figure out the path to the node you need in some other way like using an exported NodePath variable, and then keep a reference to it like this:

export var path_to_character : NodePath
var character : KinematicBody2D

func _ready() -> null:
	character = get_node(path_to_character)

Since you might want to refer to different characters here, you have to and should use get_node.

Splitting off branches, now you can too!

The reason all of that node naming stuff is important is that the way your scripts interact with their children has big implications for splitting branches of your scene off into their own scenes. And splitting branches off into their own scenes is very very helpful for keeping your project well organized.

Save Branch as Scene Instance Child Scene

Try to make sure your nodes only interact with their direct children, or at least very near children. If you can keep everything that way, you can always break any branch of the scene tree off into its own scene and have its logic fully contained in that scene.

When you absolutely need to deal with distant children, use signals or groups. So instead of

$Me/World/Level/House/Room/Guys/Enemy1.hp -= 100
$Me/World/Level/House/Room/Guys/Enemy2.hp -= 100
$Me/World/Level/House/Room/Guys/Enemy3.hp -= 100

Put the enemy nodes in a group, give them a take_damage(dmg:int) function, and use something like:

func explode() -> void:
	get_tree().call_group("enemies", "take_damage", 100)

or

signal exploded(damage)

func _ready_() -> void:
	for enemy in get_tree().get_nodes_in_group("enemies"):
		connect("exploded", enemy, "take_damage")

func explode() -> void:
	emit_signal("exploded", 100)

This way, it doesn't matter if the enemies exist or not or if there's even a house or room or level. It will just throw the signal to explode into the void and keep doing its thing.

Of course, the platonic ideal of a godot project would only ever interact with nodes by using get_children() or exporting node paths or putting things in groups, but at some point you're going to have to reference nodes by their names. There's really no way around it. When you do have to, just try to make sure it's a node that is guaranteed to be in the scene and won't ever be freed, moved, or duplicated.