|
@@ -4,7 +4,7 @@ Scene organization
|
|
==================
|
|
==================
|
|
|
|
|
|
This article covers topics related to the effective organization of
|
|
This article covers topics related to the effective organization of
|
|
-scene content. Which nodes should one use? Where should one place them?
|
|
|
|
|
|
+scene content. Which nodes should you use? Where should you place them?
|
|
How should they interact?
|
|
How should they interact?
|
|
|
|
|
|
How to build relationships effectively
|
|
How to build relationships effectively
|
|
@@ -21,10 +21,9 @@ possible. Re-using the scene in multiple places creates issues because the
|
|
node paths do not find their targets and signal connections established in the
|
|
node paths do not find their targets and signal connections established in the
|
|
editor break.
|
|
editor break.
|
|
|
|
|
|
-To fix these problems, one must instantiate the sub-scenes without them
|
|
|
|
-requiring details about their environment. One needs to be able to trust
|
|
|
|
-that the sub-scene will create itself without being picky about how one uses
|
|
|
|
-it.
|
|
|
|
|
|
+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.
|
|
|
|
|
|
One of the biggest things to consider in OOP is maintaining
|
|
One of the biggest things to consider in OOP is maintaining
|
|
focused, singular-purpose classes with
|
|
focused, singular-purpose classes with
|
|
@@ -35,8 +34,8 @@ maintainability) and improves their reusability.
|
|
These OOP best practices have *several* implications for best practices
|
|
These OOP best practices have *several* implications for best practices
|
|
in scene structure and script usage.
|
|
in scene structure and script usage.
|
|
|
|
|
|
-**If at all possible, one should design scenes to have no dependencies.**
|
|
|
|
-That is, one should create scenes that keep everything they need within
|
|
|
|
|
|
+**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.
|
|
themselves.
|
|
|
|
|
|
If a scene must interact with an external context, experienced developers
|
|
If a scene must interact with an external context, experienced developers
|
|
@@ -46,7 +45,7 @@ This technique involves having a high-level API provide the dependencies of the
|
|
low-level API. Why do this? Because classes which rely on their external
|
|
low-level API. Why do this? Because classes which rely on their external
|
|
environment can inadvertently trigger bugs and unexpected behavior.
|
|
environment can inadvertently trigger bugs and unexpected behavior.
|
|
|
|
|
|
-To do this, one must expose data and then rely on a parent context to
|
|
|
|
|
|
+To do this, you must expose data and then rely on a parent context to
|
|
initialize it:
|
|
initialize it:
|
|
|
|
|
|
1. Connect to a signal. Extremely safe, but should be used only to "respond" to
|
|
1. Connect to a signal. Extremely safe, but should be used only to "respond" to
|
|
@@ -148,14 +147,14 @@ initialize it:
|
|
GetNode(TargetPath); // Use parent-defined NodePath.
|
|
GetNode(TargetPath); // Use parent-defined NodePath.
|
|
|
|
|
|
These options hide the points of access from the child node. This in turn
|
|
These options hide the points of access from the child node. This in turn
|
|
-keeps the child **loosely coupled** to its environment. One can reuse it
|
|
|
|
|
|
+keeps the child **loosely coupled** to its environment. You can reuse it
|
|
in another context without any extra changes to its API.
|
|
in another context without any extra changes to its API.
|
|
|
|
|
|
.. note::
|
|
.. note::
|
|
|
|
|
|
Although the examples above illustrate parent-child relationships,
|
|
Although the examples above illustrate parent-child relationships,
|
|
the same principles apply towards all object relations. Nodes which
|
|
the same principles apply towards all object relations. Nodes which
|
|
- are siblings should only be aware of their hierarchies while an ancestor
|
|
|
|
|
|
+ are siblings should only be aware of their own hierarchies while an ancestor
|
|
mediates their communications and references.
|
|
mediates their communications and references.
|
|
|
|
|
|
.. tabs::
|
|
.. tabs::
|
|
@@ -201,21 +200,21 @@ in another context without any extra changes to its API.
|
|
}
|
|
}
|
|
|
|
|
|
The same principles also apply to non-Node objects that maintain dependencies
|
|
The same principles also apply to non-Node objects that maintain dependencies
|
|
- on other objects. Whichever object actually owns the objects should manage
|
|
|
|
|
|
+ on other objects. Whichever object owns the other objects should manage
|
|
the relationships between them.
|
|
the relationships between them.
|
|
|
|
|
|
.. warning::
|
|
.. warning::
|
|
|
|
|
|
- One should favor keeping data in-house (internal to a scene) though as
|
|
|
|
|
|
+ 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,
|
|
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
|
|
still means that the node will expect something in its environment to be
|
|
true. The project's design philosophies should prevent this from happening.
|
|
true. The project's design philosophies should prevent this from happening.
|
|
If not, the code's inherent liabilities will force developers to use
|
|
If not, the code's inherent liabilities will force developers to use
|
|
documentation to keep track of object relations on a microscopic scale; this
|
|
documentation to keep track of object relations on a microscopic scale; this
|
|
is otherwise known as development hell. Writing code that relies on external
|
|
is otherwise known as development hell. Writing code that relies on external
|
|
- documentation for one to use it safely is error-prone by default.
|
|
|
|
|
|
+ documentation to use it safely is error-prone by default.
|
|
|
|
|
|
- To avoid creating and maintaining such documentation, one converts the
|
|
|
|
|
|
+ To avoid creating and maintaining such documentation, you convert the
|
|
dependent node ("child" above) into a tool script that implements
|
|
dependent node ("child" above) into a tool script that implements
|
|
``_get_configuration_warnings()``.
|
|
``_get_configuration_warnings()``.
|
|
Returning a non-empty PackedStringArray from it will make the Scene dock generate a
|
|
Returning a non-empty PackedStringArray from it will make the Scene dock generate a
|
|
@@ -234,7 +233,7 @@ in another context without any extra changes to its API.
|
|
So, why does all this complex switcheroo work? Well, because scenes operate
|
|
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
|
|
best when they operate alone. If unable to work alone, then working with
|
|
others anonymously (with minimal hard dependencies, i.e. loose coupling)
|
|
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
|
|
|
|
|
|
+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,
|
|
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
|
|
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
|
|
is to avoid ending up in a situation where changing one class results in
|
|
@@ -251,44 +250,43 @@ by *all* OOP principles. Examples include...
|
|
Choosing a node tree structure
|
|
Choosing a node tree structure
|
|
------------------------------
|
|
------------------------------
|
|
|
|
|
|
-So, a developer starts work on a game only to stop at the vast possibilities
|
|
|
|
-before them. They might know what they want to do, what systems they want to
|
|
|
|
-have, but *where* to put them all? Well, how one goes about making their game
|
|
|
|
-is always up to them. One can construct node trees in countless ways.
|
|
|
|
-But, for those who are unsure, this helpful guide can give them a sample of
|
|
|
|
-a decent structure to start with.
|
|
|
|
|
|
+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 a sort of "entry point"; somewhere the developer can
|
|
|
|
-definitively track where things begin so that they can follow the logic as it
|
|
|
|
-continues elsewhere. This place also serves as a bird's eye view of all of the
|
|
|
|
-other data and logic in the program. For traditional applications, this would
|
|
|
|
-be the "main" function. In this case, it would be a Main node.
|
|
|
|
|
|
+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.
|
|
|
|
|
|
- Node "Main" (main.gd)
|
|
- Node "Main" (main.gd)
|
|
|
|
|
|
-The ``main.gd`` script would then serve as the primary controller of one's
|
|
|
|
-game.
|
|
|
|
|
|
+The ``main.gd`` script will serve as the primary controller of your game.
|
|
|
|
|
|
-Then one has their actual in-game "World" (a 2D or 3D one). This can be a child
|
|
|
|
-of Main. In addition, one will need a primary GUI for their game that manages
|
|
|
|
|
|
+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.
|
|
the various menus and widgets the project needs.
|
|
|
|
|
|
- Node "Main" (main.gd)
|
|
- Node "Main" (main.gd)
|
|
- Node2D/Node3D "World" (game_world.gd)
|
|
- Node2D/Node3D "World" (game_world.gd)
|
|
- Control "GUI" (gui.gd)
|
|
- Control "GUI" (gui.gd)
|
|
|
|
|
|
-When changing levels, one can then swap out the children of the "World" node.
|
|
|
|
-:ref:`Changing scenes manually <doc_change_scenes_manually>` gives users full
|
|
|
|
-control over how their game world transitions.
|
|
|
|
|
|
+When changing levels, you can then swap out the children of the "World" node.
|
|
|
|
+:ref:`Changing scenes manually <doc_change_scenes_manually>` gives you full
|
|
|
|
+control over how your game world transitions.
|
|
|
|
|
|
-The next step is to consider what gameplay systems one's project requires.
|
|
|
|
-If one has a system that...
|
|
|
|
|
|
+The next step is to consider what gameplay systems your project requires.
|
|
|
|
+If you have a system that...
|
|
|
|
|
|
1. tracks all of its data internally
|
|
1. tracks all of its data internally
|
|
2. should be globally accessible
|
|
2. should be globally accessible
|
|
3. should exist in isolation
|
|
3. should exist in isolation
|
|
|
|
|
|
-... then one should create an :ref:`autoload 'singleton' node <doc_singletons_autoload>`.
|
|
|
|
|
|
+... then you should create an :ref:`autoload 'singleton' node <doc_singletons_autoload>`.
|
|
|
|
|
|
.. note::
|
|
.. note::
|
|
|
|
|
|
@@ -298,27 +296,24 @@ If one has a system that...
|
|
to swap out the main scene's content. This structure more or less keeps
|
|
to swap out the main scene's content. This structure more or less keeps
|
|
the "World" as the main game node.
|
|
the "World" as the main game node.
|
|
|
|
|
|
- Any GUI would need to also be a
|
|
|
|
- singleton; be a transitory part of the "World"; or be manually added as a
|
|
|
|
- direct child of the root. Otherwise, the GUI nodes would also delete
|
|
|
|
- themselves during scene transitions.
|
|
|
|
|
|
+ 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 one has systems that modify other systems' data, one should define those as
|
|
|
|
-their own scripts or scenes rather than autoloads. For more information on the
|
|
|
|
-reasons, please see the
|
|
|
|
-:ref:`Autoloads versus regular nodes <doc_autoloads_versus_internal_nodes>`
|
|
|
|
-documentation.
|
|
|
|
|
|
+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
|
|
|
|
+:ref:`Autoloads versus regular nodes <doc_autoloads_versus_internal_nodes>`.
|
|
|
|
|
|
-Each subsystem within one's game should have its own section within the
|
|
|
|
-SceneTree. One should use parent-child relationships only in cases where 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
|
|
are effectively elements of their parents. Does removing the parent reasonably
|
|
-mean that one should also remove the children? If not, then it should have its
|
|
|
|
|
|
+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.
|
|
own place in the hierarchy as a sibling or some other relation.
|
|
|
|
|
|
.. note::
|
|
.. note::
|
|
|
|
|
|
- In some cases, one needs these separated nodes to *also* position themselves
|
|
|
|
- relative to each other. One can use the
|
|
|
|
|
|
+ In some cases, you need these separated nodes to *also* position themselves
|
|
|
|
+ relative to each other. You can use the
|
|
:ref:`RemoteTransform <class_RemoteTransform3D>` /
|
|
:ref:`RemoteTransform <class_RemoteTransform3D>` /
|
|
:ref:`RemoteTransform2D <class_RemoteTransform2D>` nodes for this purpose.
|
|
:ref:`RemoteTransform2D <class_RemoteTransform2D>` nodes for this purpose.
|
|
They will allow a target node to conditionally inherit selected transform
|
|
They will allow a target node to conditionally inherit selected transform
|
|
@@ -326,50 +321,51 @@ own place in the hierarchy as a sibling or some other relation.
|
|
:ref:`NodePath <class_NodePath>`, use one of the following:
|
|
:ref:`NodePath <class_NodePath>`, use one of the following:
|
|
|
|
|
|
1. A reliable third party, likely a parent node, to mediate the assignment.
|
|
1. A reliable third party, likely a parent node, to mediate the assignment.
|
|
- 2. A group, to easily pull a reference to the desired node (assuming there
|
|
|
|
|
|
+ 2. A group, to pull a reference to the desired node (assuming there
|
|
will only ever be one of the targets).
|
|
will only ever be one of the targets).
|
|
|
|
|
|
- When should one do this? Well, this is subjective. The dilemma arises when
|
|
|
|
- one must micro-manage when a node must move around the SceneTree to preserve
|
|
|
|
|
|
+ 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...
|
|
itself. For example...
|
|
|
|
|
|
- Add a "player" node to a "room".
|
|
- Add a "player" node to a "room".
|
|
- - Need to change rooms, so one must delete the current room.
|
|
|
|
- - Before the room can be deleted, one must preserve and/or move the player.
|
|
|
|
|
|
+ - 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.
|
|
|
|
|
|
- Is memory a concern?
|
|
|
|
|
|
+ If memory is not a concern, you can...
|
|
|
|
|
|
- - If not, one can just create the two rooms, move the player
|
|
|
|
- and delete the old one. No problem.
|
|
|
|
|
|
+ - Create the new room.
|
|
|
|
+ - Move the player to the new room.
|
|
|
|
+ - Delete the old room.
|
|
|
|
|
|
- If so, one will need to...
|
|
|
|
|
|
+ If memory is a concern, instead you will need to...
|
|
|
|
|
|
- Move the player somewhere else in the tree.
|
|
- Move the player somewhere else in the tree.
|
|
- Delete the room.
|
|
- Delete the room.
|
|
- Instantiate and add the new room.
|
|
- Instantiate and add the new room.
|
|
- - Re-add the player.
|
|
|
|
|
|
+ - Re-add the player to the new room.
|
|
|
|
|
|
- The issue is that the player here is a "special case"; one where the
|
|
|
|
|
|
+ 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
|
|
developers must *know* that they need to handle the player this way for the
|
|
- project. As such, the only way to reliably share this information as a team
|
|
|
|
- is to *document* it. Keeping implementation details in documentation however
|
|
|
|
- is dangerous. It's a maintenance burden, strains code readability, and bloats
|
|
|
|
- the intellectual content of a project unnecessarily.
|
|
|
|
|
|
+ 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 simply
|
|
|
|
- keep the player somewhere else in the SceneTree entirely. This results in:
|
|
|
|
|
|
+ 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:
|
|
|
|
|
|
1. More consistency.
|
|
1. More consistency.
|
|
2. No "special cases" that must be documented and maintained somewhere.
|
|
2. No "special cases" that must be documented and maintained somewhere.
|
|
3. No opportunity for errors to occur because these details are not accounted
|
|
3. No opportunity for errors to occur because these details are not accounted
|
|
for.
|
|
for.
|
|
|
|
|
|
- In contrast, if one ever needs to have a child node that does *not* inherit
|
|
|
|
- the transform of their parent, one has the following options:
|
|
|
|
|
|
+ In contrast, if you ever need a child node that does *not* inherit
|
|
|
|
+ the transform of its parent, you have the following options:
|
|
|
|
|
|
1. The **declarative** solution: place a :ref:`Node <class_Node>` in between
|
|
1. The **declarative** solution: place a :ref:`Node <class_Node>` in between
|
|
- them. As nodes with no transform, Nodes will not pass along such
|
|
|
|
- information to their children.
|
|
|
|
|
|
+ them. Since it doesn't have a transform, they won't pass this information
|
|
|
|
+ to its children.
|
|
2. The **imperative** solution: Use the ``top_level`` property for the
|
|
2. The **imperative** solution: Use the ``top_level`` property for the
|
|
:ref:`CanvasItem <class_CanvasItem_property_top_level>` or
|
|
:ref:`CanvasItem <class_CanvasItem_property_top_level>` or
|
|
:ref:`Node3D <class_Node3D_property_top_level>` node. This will make
|
|
:ref:`Node3D <class_Node3D_property_top_level>` node. This will make
|
|
@@ -380,9 +376,9 @@ own place in the hierarchy as a sibling or some other relation.
|
|
If building a networked game, keep in mind which nodes and gameplay systems
|
|
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
|
|
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'
|
|
server. For example, users do not all need to have a copy of every players'
|
|
- "PlayerController" logic. Instead, they need only their own. As such, keeping
|
|
|
|
- these in a separate branch from the "world" can help simplify the management
|
|
|
|
- of game connections and the like.
|
|
|
|
|
|
+ "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.
|
|
|
|
|
|
The key to scene organization is to consider the SceneTree in relational terms
|
|
The key to scene organization is to consider the SceneTree in relational terms
|
|
rather than spatial terms. Are the nodes dependent on their parent's existence?
|
|
rather than spatial terms. Are the nodes dependent on their parent's existence?
|
|
@@ -392,5 +388,5 @@ that parent (and likely part of that parent's scene if they aren't already).
|
|
|
|
|
|
Does this mean nodes themselves are components? Not at all.
|
|
Does this mean nodes themselves are components? Not at all.
|
|
Godot's node trees form an aggregation relationship, not one of composition.
|
|
Godot's node trees form an aggregation relationship, not one of composition.
|
|
-But while one still has the flexibility to move nodes around, it is still best
|
|
|
|
|
|
+But while you still have the flexibility to move nodes around, it is still best
|
|
when such moves are unnecessary by default.
|
|
when such moves are unnecessary by default.
|