Up to date
This page is up to date for Godot 4.3.
If you still find outdated information, please open an issue.
Organización de la escena
This article covers topics related to the effective organization of scene content. Which nodes should you use? Where should you place them? How should they interact?
Cómo crear relaciones de manera eficiente
Cuando los usuarios de Godot comienzan a crear sus propias escenas, normalmente se encuentran con el siguiente problema:
Crean la primer escena y la llenan con contenido para luego terminar guardando las ramas en escenas separadas a medida que la sensación de que se deben separar cosas comienza a acumularse. Sin embargo, notan que las referencias rígidas de las que dependían, ya no son posibles de usar. Reutilizar la escena en muchos lugares genera problemas porque las rutas de los nodos no llegan a su destino y las conexiones de las señales creadas en el editor se rompen.
To fix these problems, you must instantiate the sub-scenes without them requiring details about their environment. You need to be able to trust that the sub-scene will create itself without being picky about how it's used.
Una de las mayores cosas a considerar en POO es mantener clases enfocadas, de propósito simple, con vínculos no estrictos (loose coupling ) respecto a otras partes del proyecto. Esto mantiene pequeño el tamaño de los objetos (para facilitar el mantenimiento) y mejora la reusabilidad.
Estas buenas prácticas POO tienen muchas ramificaciones para buenas prácticas en estructura de escenas y uso de scripts.
If at all possible, you should design scenes to have no dependencies. That is, you should create scenes that keep everything they need within themselves.
Si una escena debe interactuar en un contexto externo, los desarrolladores experimentados recomiendan el uso de Inyección de dependencias. Esta técnica implica que una API de alto nivel proporcione las dependencias de la API de bajo nivel. ¿Por qué hacer esto? Porque las clases que dependen de su entorno externo pueden desencadenar errores y comportamientos inesperados sin darse cuenta.
To do this, you must expose data and then rely on a parent context to initialize it:
Conectando una señal. Es extremadamente seguro, pero solo debe ser utilizado como "respuesta" a un comportamiento, nunca para iniciarlo. Por convención, los nombres de las señales normalmente están en tiempo pasado, como por ejemplo "entered" (ingresó), "skill_activated" (se activó la habilidad) o "item_collected" (se recolectó el item).
# Parent $Child.signal_name.connect(method_on_the_object) # Child signal_name.emit() # Triggers parent-defined behavior.
// Parent GetNode("Child").Connect("SignalName", Callable.From(ObjectWithMethod.MethodOnTheObject)); // Child EmitSignal("SignalName"); // Triggers parent-defined behavior.
Llamar a un método. Usado para iniciar un comportamiento.
# Parent $Child.method_name = "do" # Child, assuming it has String property 'method_name' and method 'do'. call(method_name) # Call parent-defined method (which child must own).
// Parent GetNode("Child").Set("MethodName", "Do"); // Child Call(MethodName); // Call parent-defined method (which child must own).
Inicializar una propiedad Callable. Es más seguro que establecer un método como propiedad del método . Se utiliza para iniciar el comportamiento.
# Parent $Child.func_property = object_with_method.method_on_the_object # Child func_property.call() # Call parent-defined method (can come from anywhere).
// Parent GetNode("Child").Set("FuncProperty", Callable.From(ObjectWithMethod.MethodOnTheObject)); // Child FuncProperty.Call(); // Call parent-defined method (can come from anywhere).
Inicializa un Nodo u otra referencia de Objeto.
# Parent $Child.target = self # Child print(target) # Use parent-defined node.
// Parent GetNode("Child").Set("Target", this); // Child GD.Print(Target); // Use parent-defined node.
Inicializar un NodePath.
# Parent $Child.target_path = ".." # Child get_node(target_path) # Use parent-defined NodePath.
// Parent GetNode("Child").Set("TargetPath", NodePath("..")); // Child GetNode(TargetPath); // Use parent-defined NodePath.
These options hide the points of access from the child node. This in turn keeps the child loosely coupled to its environment. You can reuse it in another context without any extra changes to its API.
Nota
Although the examples above illustrate parent-child relationships, the same principles apply towards all object relations. Nodes which are siblings should only be aware of their own hierarchies while an ancestor mediates their communications and references.
# Parent
$Left.target = $Right.get_node("Receiver")
# Left
var target: Node
func execute():
# Do something with 'target'.
# Right
func _init():
var receiver = Receiver.new()
add_child(receiver)
// Parent
GetNode<Left>("Left").Target = GetNode("Right/Receiver");
public partial class Left : Node
{
public Node Target = null;
public void Execute()
{
// Do something with 'Target'.
}
}
public partial class Right : Node
{
public Node Receiver = null;
public Right()
{
Receiver = ResourceLoader.Load<Script>("Receiver.cs").New();
AddChild(Receiver);
}
}
The same principles also apply to non-Node objects that maintain dependencies on other objects. Whichever object owns the other objects should manage the relationships between them.
Advertencia
You should favor keeping data in-house (internal to a scene), though, as placing a dependency on an external context, even a loosely coupled one, still means that the node will expect something in its environment to be true. The project's design philosophies should prevent this from happening. If not, the code's inherent liabilities will force developers to use documentation to keep track of object relations on a microscopic scale; this is otherwise known as development hell. Writing code that relies on external documentation to use it safely is error-prone by default.
To avoid creating and maintaining such documentation, you convert the
dependent node ("child" above) into a tool script that implements
_get_configuration_warnings().
Returning a non-empty PackedStringArray from it will make the Scene dock generate a
warning icon with the string(s) as a tooltip by the node. This is the same icon
that appears for nodes such as the
Area2D node when it has no child
CollisionShape2D nodes defined. The editor
then self-documents the scene through the script code. No content duplication
via documentation is necessary.
Una GUI como esta puede informar mejor a los usuarios del proyecto sobre la existencia de información crítica sobre un Nodo. ¿Tiene dependencias externas? ¿se han satisfecho esas dependencias?. Otros programadores, y especialmente los diseñadores y escritores, necesitarán instrucciones claras en los mensajes que les indiquen qué hacer para configurarlo.
So, why does all this complex switcheroo work? Well, because scenes operate best when they operate alone. If unable to work alone, then working with others anonymously (with minimal hard dependencies, i.e. loose coupling) is the next best thing. Inevitably, changes may need to be made to a class, and if these changes cause it to interact with other scenes in unforeseen ways, then things will start to break down. The whole point of all this indirection is to avoid ending up in a situation where changing one class results in adversely affecting other classes dependent on it.
Tanto scripts y escenas, como clases de extensión del motor, deben apegarse a todos los principios POO. Ejemplos incluyen...
Eligiendo una estructura de árbol de nodos
You might start to work on a game but get overwhelmed by the vast possibilities before you. You might know what you want to do, what systems you want to have, but where do you put them all? How you go about making your game is always up to you. You can construct node trees in countless ways. If you are unsure, this guide can give you a sample of a decent structure to start with.
A game should always have an "entry point"; somewhere you can definitively track where things begin so that you can follow the logic as it continues elsewhere. It also serves as a bird's eye view of all other data and logic in the program. For traditional applications, this is normally a "main" function. In Godot, it's a Main node.
Nodo "Main" (main.gd)
The main.gd script will serve as the primary controller of your game.
Then you have an in-game "World" (a 2D or 3D one). This can be a child of Main. In addition, you will need a primary GUI for your game that manages the various menus and widgets the project needs.
- Nodo "Main" (main.gd)
Node2D/Node3D "World" (game_world.gd)
Control "GUI" (gui.gd)
When changing levels, you can then swap out the children of the "World" node. Changing scenes manually gives you full control over how your game world transitions.
The next step is to consider what gameplay systems your project requires. If you have a system that...
monitorea todos los datos internamente
debería ser accesible globalmente
debería existir de manera aislada
... then you should create an autoload 'singleton' node.
Nota
Para juegos pequeños, una alternativa simple con menor control podría ser tener un singleton "Game" que simplemente llame al método SceneTree.change_scene_to_file() para intercambiar el contenido de la escena principal. Esta estructura mantiene a "World" como un nodo principal del juego.
Any GUI would also need to be either a singleton, a transitory part of the "World", or manually added as a direct child of the root. Otherwise, the GUI nodes would also delete themselves during scene transitions.
If you have systems that modify other systems' data, you should define those as their own scripts or scenes, rather than autoloads. For more information, see Autoloads versus regular nodes.
Each subsystem within your game should have its own section within the SceneTree. You should use parent-child relationships only in cases where nodes are effectively elements of their parents. Does removing the parent reasonably mean that the children should also be removed? If not, then it should have its own place in the hierarchy as a sibling or some other relation.
Nota
In some cases, you need these separated nodes to also position themselves
relative to each other. You can use the
RemoteTransform /
RemoteTransform2D nodes for this purpose.
They will allow a target node to conditionally inherit selected transform
elements from the Remote* node. To assign the target
NodePath, use one of the following:
Un nodo externo confiable, como un nodo padre, para mediar en la asignación.
A group, to pull a reference to the desired node (assuming there will only ever be one of the targets).
When you should do this is subjective. The dilemma arises when you must micro-manage when a node must move around the SceneTree to preserve itself. For example...
Agregar un nodo "jugador" a un "escenario".
Need to change rooms, so you must delete the current room.
Before the room can be deleted, you must preserve and/or move the player.
If memory is not a concern, you can...
Create the new room.
Move the player to the new room.
Delete the old room.
If memory is a concern, instead you will need to...
Mover el jugador a algún lugar en el árbol de escenas.
Borrar el escenario.
Instanciar el escenario nuevo.
Re-add the player to the new room.
The issue is that the player here is a "special case" where the developers must know that they need to handle the player this way for the project. The only way to reliably share this information as a team is to document it. Keeping implementation details in documentation is dangerous. It's a maintenance burden, strains code readability, and unnecessarily bloats the intellectual content of a project.
In a more complex game with larger assets, it can be a better idea to keep the player somewhere else in the SceneTree entirely. This results in:
Mayor consistencia.
No hay "casos especiales" que deban ser documentados y mantenidos en algún lugar.
No hay oportunidad de que sucedan esos errores porque algún detalle no se tuvo en cuenta.
In contrast, if you ever need a child node that does not inherit the transform of its parent, you have the following options:
The declarative solution: place a Node in between them. Since it doesn't have a transform, they won't pass this information to its children.
La solución imperativa: Usa la propiedad
top_leveldel nodo CanvasItem or Node3D. Esto hará que el nodo ignore el transform heredado.
Nota
If building a networked game, keep in mind which nodes and gameplay systems are relevant to all players versus those just pertinent to the authoritative server. For example, users do not all need to have a copy of every players' "PlayerController" logic - they only need their own. Keeping them in a separate branch from the "world" can help simplify the management of game connections and the like.
La clave para la organización de la escena es considerar el Árbol de Escenas en términos relacionales más que espaciales. ¿Los nodos dependen de la existencia de sus padres? Si no es así, entonces pueden prosperar por sí mismos en otro lugar. Si son dependientes, entonces es lógico que sean hijos de ese padre (y probablemente parte de la escena de ese padre si no lo son ya).
Does this mean nodes themselves are components? Not at all. Godot's node trees form an aggregation relationship, not one of composition. But while you still have the flexibility to move nodes around, it is still best when such moves are unnecessary by default.