mirror of
https://github.com/stan220/godot-docs.git
synced 2026-09-08 17:28:58 +00:00
Merge pull request #9642 from Incognito357/Incognito357-update-docs-scene_organization
Update scene_organization.rst
This commit is contained in:
@@ -4,7 +4,7 @@ Scene organization
|
||||
==================
|
||||
|
||||
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 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
|
||||
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
|
||||
focused, singular-purpose classes with
|
||||
@@ -35,8 +34,8 @@ maintainability) and improves their reusability.
|
||||
These OOP best practices have *several* implications for best practices
|
||||
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.
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
.. note::
|
||||
|
||||
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 hierarchies while an ancestor
|
||||
are siblings should only be aware of their own hierarchies while an ancestor
|
||||
mediates their communications and references.
|
||||
|
||||
.. 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
|
||||
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.
|
||||
|
||||
.. 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,
|
||||
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 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
|
||||
``_get_configuration_warnings()``.
|
||||
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
|
||||
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
|
||||
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
|
||||
@@ -251,44 +250,43 @@ by *all* OOP principles. Examples include...
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
- Node "Main" (main.gd)
|
||||
- Node2D/Node3D "World" (game_world.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
|
||||
2. should be globally accessible
|
||||
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::
|
||||
|
||||
@@ -298,27 +296,24 @@ If one has a system that...
|
||||
to swap out the main scene's content. This structure more or less keeps
|
||||
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
|
||||
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.
|
||||
|
||||
.. 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:`RemoteTransform2D <class_RemoteTransform2D>` nodes for this purpose.
|
||||
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:
|
||||
|
||||
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).
|
||||
|
||||
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...
|
||||
|
||||
- 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.
|
||||
- Delete the 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
|
||||
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.
|
||||
2. No "special cases" that must be documented and maintained somewhere.
|
||||
3. No opportunity for errors to occur because these details are not accounted
|
||||
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
|
||||
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
|
||||
:ref:`CanvasItem <class_CanvasItem_property_top_level>` or
|
||||
: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
|
||||
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. 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
|
||||
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.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user