mirror of
https://github.com/stan220/godot-docs.git
synced 2026-09-08 21:29:07 +00:00
Merge pull request #2175 from willnationsdev/scene-organization
Add a "best practices" section
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
.. _doc_autoloads_versus_internal_nodes:
|
||||
|
||||
Autoloads versus internal nodes
|
||||
===============================
|
||||
|
||||
Other engines might encourage the use of creating "manager" classes that
|
||||
organize lots of functionality into a globally accessible entity. Godot
|
||||
thrives by supporting ways to cut down the size of such objects. Instead it
|
||||
shifts content into individual nodes as much as possible.
|
||||
|
||||
For example, what if a developer is building a platformer and they want to
|
||||
collect coins that play a sound effect? Well, there's a node for that:
|
||||
the :ref:`AudioStreamPlayer <class_AudioStreamPlayer>`. But they notice during
|
||||
their testing that if they "play" an AudioStreamPlayer while it is already playing
|
||||
the sound, then the new sound interrupts the first sound, terminating it
|
||||
before it can play.
|
||||
|
||||
Users tend to think the best solution is to make the whole system smarter by
|
||||
making a SoundManager autoload node. It generates a pool of AudioStreamPlayers
|
||||
that cycle through as each new request for sound effects comes in. They then
|
||||
make this SoundManager an autoload so that they can access it from anywhere with
|
||||
`SFX.play("coin_pickup.ogg")`. Little do they know, they've invited a great
|
||||
many complications into their code.
|
||||
|
||||
- **global state**: One object is now responsible for all objects' data. If
|
||||
SFX has errors or doesn't have an AudioStreamPlayer available, everything
|
||||
will break.
|
||||
|
||||
- **global access**: Now that any object can call `SFX.play(sound_path)`
|
||||
from anywhere, there's no longer an easy way to track where an SFX bug
|
||||
began.
|
||||
|
||||
- **global resource allocation**: If all objects' data and processing is
|
||||
centralized from the start, then one must either...
|
||||
|
||||
1. risk under-allocating resources which might lead to faulty behavior.
|
||||
|
||||
- Ex: Have too few AudioStreamPlayers in the object pool. Sound doesn't
|
||||
play or it interrupts another sound.
|
||||
|
||||
2. over-allocate resources and use more memory/processing than it needs.
|
||||
|
||||
- Ex: Have an arbitrarily large number of AudioStreamPlayers, with
|
||||
many of them idling away and not doing anything.
|
||||
|
||||
3. have each object that needs an AudioStreamPlayer register exactly how
|
||||
many it needs and for which sounds. This defeats the purpose of
|
||||
using a 3rd party though; it is now coupled to each object, just
|
||||
as a child node would have been. One has added an unnecessary
|
||||
middleman to the equation.
|
||||
|
||||
Contrast this with each scene keeping as many AudioStreamPlayer nodes as it
|
||||
needs within itself and all these problems go away.
|
||||
|
||||
- Each scene manages its own state information. If there is a problem with the
|
||||
data, it will only cause issues in that one scene.
|
||||
|
||||
- Each scene accesses only its own nodes. Now, if there is
|
||||
a bug, tracing which node is responsible (probably the root node of the
|
||||
scene), and where in the code it's making the problematic call (locate
|
||||
where the code references the given node) is going to be much easier.
|
||||
|
||||
- Each scene knows exactly how many resources it needs for the task it
|
||||
performs. No waste of memory or processing due to a lack of information.
|
||||
|
||||
The typical justifications for the Autoload include, "I have common Xs that
|
||||
involve many nodes across many scenes, and I want each scene to have X."
|
||||
|
||||
If X is a function, then the solution is to create a new type of
|
||||
:ref:`Node <class_Node>` that deals with providing that feature for an
|
||||
individual scene or node subtree.
|
||||
|
||||
If X is data, then the solution is either 1) to create a new type of
|
||||
:ref:`Resource <class_Resource>` to share the data, or 2) to store the data
|
||||
in an object to which each node has access (nodes within a scene can use
|
||||
`get_owner()` to fetch the scene root for example).
|
||||
|
||||
So when *should* one use an autoload?
|
||||
|
||||
- **Static Data**: if you need static data, i.e. data that should be
|
||||
associated with a class (so there is only ever one copy of the data), then
|
||||
autoloads are good opportunities for that. Static data doesn't exist in
|
||||
Godot's scripting API, so autoload singletons are the next best thing. If
|
||||
one creates a class as an autoload, and never creates another copy of that
|
||||
class within a scene, then it will function in place of a formal singleton
|
||||
API.
|
||||
|
||||
- **Convenience**: autoloaded nodes have a global variable for their name
|
||||
generated in GDScript. This can be very convenient for defining objects
|
||||
that should always exist, but which need object instance information.
|
||||
The alternative is to create a namespace script: a script that's purpose
|
||||
is only to load and create constants to access other Script or PackedScene
|
||||
resources, resulting in something like ``MyAutoload.MyNode.new()``.
|
||||
|
||||
- Note that the introduction of script classes in Godot 3.1 questions
|
||||
the validity of this reason. With them, one can access scripts using an
|
||||
explicit name from GDScript. Using an autoload to get to a namespace
|
||||
script becomes unnecessary, e.g. ``MyScriptClass.MyPreloadedScript.new()``.
|
||||
|
||||
If the singleton is managing its own information and not invading the data of
|
||||
other objects, then it's a great way to create a "system" class that handles
|
||||
a broad-scoped task. For example a targeting system, quest system, or dialogue
|
||||
system would be great use cases of singleton implementations.
|
||||
@@ -0,0 +1,354 @@
|
||||
.. _doc_data_preferences:
|
||||
|
||||
Data preferences
|
||||
================
|
||||
|
||||
Ever wondered whether one should approach problem X with data structure
|
||||
Y or Z? This article covers a variety of topics related to these dilemmas.
|
||||
|
||||
.. note::
|
||||
|
||||
This article makes references to "[something]-time" operations. This
|
||||
terminology comes from algorithm analysis'
|
||||
`Big O Notation <https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/>`_.
|
||||
|
||||
Long-story short, it describes the worst-case scenario of runtime length.
|
||||
In laymen's terms:
|
||||
|
||||
"As the size of a problem domain increases, the runtime length of the
|
||||
algorithm..."
|
||||
|
||||
- Constant-time, O(1): "...does not increase."
|
||||
- Logarithmic-time, O(log n): "...increases at a slow rate."
|
||||
- Linear-time, O(n): "...increases at the same rate."
|
||||
- Etc.
|
||||
|
||||
Imagine if one had to process 3 million data points within a single frame. It
|
||||
would be impossible to craft the feature with a linear-time algorithm since
|
||||
the sheer size of the data would increase the runtime far beyond the time alotted.
|
||||
In comparison, using a constant-time algorithm could handle the operation without
|
||||
issue.
|
||||
|
||||
By and large, developers want to avoid engaging in linear-time operations as
|
||||
much as possible. But, if one keeps the scale of a linear-time operation
|
||||
small, and if one does not need to perform the operation often, then it may
|
||||
be acceptable. Balancing these requirements and choosing the right
|
||||
algorithm / data structure for the job is part of what makes programmers'
|
||||
skills valuable.
|
||||
|
||||
Array vs. Dictionary vs. Object
|
||||
-------------------------------
|
||||
|
||||
Godot stores all variables in the scripting API in the
|
||||
`Variant <https://docs.godotengine.org/en/latest/development/cpp/variant_class.html>`_
|
||||
class. Variants can store Variant-compatible data structures such as
|
||||
:ref:`Array <class_Array>` and :ref:`Dictionary <class_Dictionary>` as well as
|
||||
:ref:`Object <class_Object>` s.
|
||||
|
||||
Godot implements Array as a Vector<Variant>. The engine stores the Array
|
||||
contents in a contiguous section of memory, i.e. they are in a row adjacent
|
||||
to each other.
|
||||
|
||||
.. note::
|
||||
For those unfamiliar with C++, a Vector is the name of the
|
||||
array object in traditional C++ libraries. It is a "templated"
|
||||
type, meaning that its records can only contain a particular type (denoted
|
||||
by angled brackets). So, for example, a
|
||||
:ref:`PoolStringArray <class_PoolStringArray>` would be something like
|
||||
a Vector<String>.
|
||||
|
||||
Contiguous memory stores imply the following operation performance:
|
||||
|
||||
- **Iterate:** Fastest. Great for loops.
|
||||
|
||||
- Op: All it does is increment a counter to get to the next record.
|
||||
|
||||
- **Insert, Erase, Move:** Position-dependent. Generally slow.
|
||||
|
||||
- Op: Adding/removing/moving content involves moving the adjacent records
|
||||
over (to make room / fill space).
|
||||
|
||||
- Fast add/remove *from the end*.
|
||||
|
||||
- Slow add/remove *from an arbitrary position*.
|
||||
|
||||
- Slowest add/remove *from the front*.
|
||||
|
||||
- If doing many inserts/removals *from the front*, then...
|
||||
|
||||
1. invert the array.
|
||||
|
||||
2. do a loop which executes the Array changes *at the end*.
|
||||
|
||||
3. re-invert the array.
|
||||
|
||||
This makes only 2 copies of the array (still constant time, but slow)
|
||||
versus copying roughly 1/2 of the array, on average, N times (linear time).
|
||||
|
||||
- **Get, Set:** Fastest *by position*. Ex. can request 0th, 2nd, 10th record, etc.
|
||||
but cannot specify which record you want.
|
||||
|
||||
- Op: 1 addition operation from array start position up to desired index.
|
||||
|
||||
- **Find:** Slowest. Identifies the index/position of a value.
|
||||
|
||||
- Op: Must iterate through array and compare values until one finds a match.
|
||||
|
||||
- Performance is also dependent on whether one needs an exhaustive
|
||||
search.
|
||||
|
||||
- If kept ordered, custom search operations can bring it to logarithmic
|
||||
time (relatively fast). Laymen users won't be comfortable with this
|
||||
though. Done by re-sorting the Array after every edit and writing an
|
||||
ordered-aware search algorithm.
|
||||
|
||||
Godot implements Dictionary as an OrderedHashMap<Variant, Variant>. The engine
|
||||
stores a giant array (initialized to 1000 records) of key-value pairs. When
|
||||
one attempts to access a value, they provide it a key. It then *hashes* the
|
||||
key, i.e. converts it into a number. The "hash" becomes the index into the
|
||||
array, giving the OHM a quick lookup for the value within the conceptual
|
||||
"table" of keys mapped to values.
|
||||
|
||||
Hashes are to reduce the chance of a key collision. If one occurs, the table
|
||||
must recalculate another index for the value that takes the previous position
|
||||
into account. In all, this results in constant-time access to all records at
|
||||
the expense of memory and some minor operational efficiency.
|
||||
|
||||
1. Hashing every key an arbitrary number of times.
|
||||
|
||||
- Hash operations are constant-time, so even if an algorithm must do more
|
||||
than one, as long as the number of hash calculations doesn't become
|
||||
too dependent on the density of the table, things will stay fast.
|
||||
Which leads to...
|
||||
|
||||
2. Maintaining a huge size for the table.
|
||||
|
||||
- The reason it starts with 1000 records, and the reason it forces
|
||||
large gaps of unused memory interspersed in the table is to
|
||||
minimize hash collisions and maintain the speed of the accesses.
|
||||
|
||||
As one might be able to tell, Dictionaries specialize in tasks that Arrays
|
||||
aren't. An overview of their operational details is as follows:
|
||||
|
||||
- **Iterate:** Fast.
|
||||
|
||||
- Op: Iterate over the map's internal vector of hashes. Return each key.
|
||||
Afterwards, users then use the key to jump to and return the desired
|
||||
value.
|
||||
|
||||
- **Insert, Erase, Move:** Fastest.
|
||||
|
||||
- Op: Hash the given key. Do 1 addition operation to look up the
|
||||
appropriate value (array start + offset). Move is two of these
|
||||
(one insert, one erase). The map must do some maintenance to preserve
|
||||
its capabilities:
|
||||
|
||||
- update ordered List of records.
|
||||
|
||||
- determine if table density mandates a need to expand table capacity.
|
||||
|
||||
- The Dictionary remembers in what
|
||||
order users inserted its keys. This enables it to execute reliable iterations.
|
||||
|
||||
- **Get, Set:** Fastest. Same as a lookup *by key*.
|
||||
|
||||
- Op: Same as insert/erase/move.
|
||||
|
||||
- **Find:** Slowest. Identifies the key of a value.
|
||||
|
||||
- Op: Must iterate through records and compare the value until a match is
|
||||
found.
|
||||
|
||||
- Note that Godot does not provide this feature out-of-the-box (because
|
||||
they aren't meant for this task).
|
||||
|
||||
Godot implements Objects as stupid, but dynamic containers of data content.
|
||||
Objects query data sources when posed questions. For example, to answer
|
||||
the question, "do you have a property called, 'position'?", it might ask
|
||||
its :ref:`script <class_Script>` or the :ref:`ClassDB <class_ClassDB>`.
|
||||
One can find more information about what objects are and how they work in
|
||||
the :ref:`what are godot classes <doc_what_are_godot_classes>` documentation.
|
||||
|
||||
The important detail here is the complexity of the Object's task. Every time
|
||||
it performs one of these multi-source queries, it runs through *several*
|
||||
iteration loops and HashMap lookups. What's more, the queries are linear-time
|
||||
operations dependent on the Object's inheritance hierarchy size. If the class
|
||||
the Object queries (it's current class) doesn't find anything, the request
|
||||
defers to the next base class, all the way up until the original Object class.
|
||||
While these are each fast operations in isolation, the fact that it must make
|
||||
so many checks is what makes them slower than both of the alternatives for
|
||||
looking up data.
|
||||
|
||||
.. note::
|
||||
|
||||
When developers mention how slow the scripting API is, it is this chain
|
||||
of queries they refer to. Compared to compiled C++ code where the
|
||||
application knows exactly where to go to find anything, it is inevitable
|
||||
that scripting API operations will take much longer. They must locate the
|
||||
source of any relevant data before they can attempt to access it.
|
||||
|
||||
The reason GDScript is slow is because every operation it performs passes
|
||||
through this system.
|
||||
|
||||
C# can process some content at higher speeds via more optimized bytecode.
|
||||
But, if the C# script calls into an engine class'
|
||||
content or if the script tries to access something external to it, it will
|
||||
go through this pipeline.
|
||||
|
||||
NativeScript C++ goes even further and keeps everything internal by default.
|
||||
Calls into external structures will go through the scripting API. In
|
||||
NativeScript C++, registering methods to expose them to the scripting API is
|
||||
a manual task. It is at this point that external, non-C++ classes will use
|
||||
the API to locate them.
|
||||
|
||||
So, assuming one extends from Reference to create a data structure, like
|
||||
an Array or Dictionary, why choose an Object over the other two options?
|
||||
|
||||
1. **Control:** With objects comes the ability to create more sophisticated
|
||||
structures. One can layer abstractions over the data to ensure the external
|
||||
API doesn't change in response to internal data structure changes. What's
|
||||
more, Objects can have signals, allowing for reactive behavior.
|
||||
|
||||
2. **Clarity:** Objects are a reliable data source when it comes to the data
|
||||
that scripts and engine classes define for them. Properties may not hold the
|
||||
values one expects, but one doesn't need to worry about whether the property
|
||||
exists in the first place.
|
||||
|
||||
3. **Convenience:** If one already has a similar data structure in mind, then
|
||||
extending from an existing class makes the task of building the data
|
||||
structure much easier. In comparison, Arrays and Dictionaries don't
|
||||
fulfill all use cases one might have.
|
||||
|
||||
Objects also give users the opportunity to create even more specialized data
|
||||
structures. With it, one can design their own List, Binary Search Tree, Heap,
|
||||
Splay Tree, Graph, Disjoint Set, and any host of other options.
|
||||
|
||||
"Why not use Node for tree structures?" one might ask. Well, the Node
|
||||
class contains things that won't be relevant to one's custom data structure.
|
||||
As such, it can be helpful to construct one's own node type when building
|
||||
tree structures.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Object
|
||||
class_name TreeNode
|
||||
|
||||
var _parent : TreeNode = null
|
||||
var _children : = [] setget
|
||||
|
||||
func _notification(p_what):
|
||||
match p_what:
|
||||
NOTIFICATION_PREDELETE:
|
||||
# destructor
|
||||
for a_child in _children:
|
||||
a_child.free()
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
// Can decide whether to expose getters/setters for properties later
|
||||
public class TreeNode : public Object {
|
||||
|
||||
private TreeNode _parent = null;
|
||||
|
||||
private object[] _children = new object[0];
|
||||
|
||||
// Not sure if this should be a destructor or a notification
|
||||
public void ~TreeNode() {
|
||||
foreach (object child in _children) {
|
||||
TreeNode node = child as TreeNode;
|
||||
if (node != null) {
|
||||
node.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
From here, one can then create their own structures with specific features,
|
||||
limited only by their imagination.
|
||||
|
||||
Enumerations: int vs. string
|
||||
----------------------------
|
||||
|
||||
Most languages offer an enumeration type option. GDScript is no different,
|
||||
but unlike most other languages, it allows one to use either integers or
|
||||
strings for the enum values. The question then arises, "which should one
|
||||
use?"
|
||||
|
||||
The short answer is, "whichever you are more comfortable with." This
|
||||
is a feature specific to GDScript and not Godot scripting in general;
|
||||
The languages prioritizes usability over performance.
|
||||
|
||||
On a technical level, integer comparisons (constant-time) will happen
|
||||
faster than string comparisons (linear-time). If one wants to keep
|
||||
up other languages' conventions though, then one should use integers.
|
||||
|
||||
The primary issue with using integers comes up when one wants to *print*
|
||||
an enum value. As integers, attempting to print MY_ENUM will print
|
||||
``5`` or what-have-you, rather than something like ``"MyEnum"``. To
|
||||
print an integer enum, one would have to write a Dictionary that maps the
|
||||
corresponding string value for each enum.
|
||||
|
||||
If the primary purpose of using an enum is for printing values and one wishes
|
||||
to group them together as related concepts, then it makes sense to use them as
|
||||
strings. That way, a separate data structure to execute on the printing is
|
||||
unnecessary.
|
||||
|
||||
AnimatedTexture vs. AnimatedSprite vs. AnimationPlayer vs. AnimationTree
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Under what circumstances should one use each of Godot's animation classes?
|
||||
The answer may not be immediately clear to new Godot users.
|
||||
|
||||
:ref:`AnimatedTexture <class_AnimatedTexture>` is a texture that
|
||||
the engine draws as an animated loop rather than a static image.
|
||||
Users can manipulate...
|
||||
|
||||
1. the rate at which it moves across each section of the texture (fps).
|
||||
|
||||
2. the number of regions contained within the texture (frames).
|
||||
|
||||
Godot's :ref:`VisualServer <class_VisualServer>` then draws
|
||||
the regions in sequence at the prescribed rate. The good news is that this
|
||||
involves no extra logic on the part of the engine. The bad news is
|
||||
that users have very little control.
|
||||
|
||||
Also note that AnimatedTexture is a :ref:`Resource <class_Resource>` unlike
|
||||
the other :ref:`Node <class_Node>` objects discussed here. One might create
|
||||
a :ref:`Sprite <class_Sprite>` node that uses AnimatedTexture as its texture.
|
||||
Or (something the others can't do) one could add AnimatedTextures as tiles
|
||||
in a :ref:`TileSet <class_TileSet>` and integrate it with a
|
||||
:ref:`TileMap <class_TileMap>` for many auto-animating backgrounds that
|
||||
all render in a single batched draw call.
|
||||
|
||||
The AnimatedSprite node, in combination with the
|
||||
:ref:`SpriteFrames <class_SpriteFrames>` resource, allows one to create a
|
||||
variety of animation sequences through spritesheets, flip between animations,
|
||||
and control their speed, regional offset, and orientation. This makes them
|
||||
well-suited to controlling 2D frame-based animations.
|
||||
|
||||
If one needs trigger other effects in relation to animation changes (for
|
||||
example, create particle effects, call functions, or manipulate other
|
||||
peripheral elements besides the frame-based animation), then will need to use
|
||||
an :ref:`AnimationPlayer <class_AnimationPlayer>` node in conjunction with
|
||||
the AnimatedSprite.
|
||||
|
||||
AnimationPlayers are also the tool one will need to use if they wish to design
|
||||
more complex 2D animation systems, such as...
|
||||
|
||||
1. **Cut-Out animations:** editing sprites' transforms at runtime.
|
||||
|
||||
2. **2D Mesh animations:** defining a region for the sprite's texture and
|
||||
rigging a skeleton to it. Then one animates the bones which
|
||||
stretch and bend the texture in proportion to the bones' relationships to
|
||||
each other.
|
||||
|
||||
3. A mix of the above.
|
||||
|
||||
While one needs an AnimationPlayer to design each of the individual
|
||||
animation sequences for a game, it can also be useful to combine animations
|
||||
for blending, i.e. enabling smooth transitions between these animations. There
|
||||
may also be a hierarchical structure between animations that one plans out for
|
||||
their object. These are the cases where the :ref:`AnimationTree <class_AnimationTree>`
|
||||
shines. One can find an in-depth guide on using the AnimationTree
|
||||
:ref:`here <doc_animation_tree>`.
|
||||
@@ -0,0 +1,503 @@
|
||||
.. _doc_godot_interfaces:
|
||||
|
||||
Godot Interfaces
|
||||
================
|
||||
|
||||
Often one needs scripts that rely on other objects for features. There
|
||||
are 2 parts to this process:
|
||||
|
||||
1. Acquiring a reference to the object that presumably has the features.
|
||||
|
||||
2. Accessing the data or logic from the object.
|
||||
|
||||
The rest of this tutorial outlines the various ways of doing all this.
|
||||
|
||||
Acquiring object references
|
||||
---------------------------
|
||||
|
||||
For all :ref:`Object <class_Object>`s, the most basic way of referencing them
|
||||
is to get a reference to an existing object from another acquired instance.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
var obj = node.object # Property access
|
||||
var obj = node.get_object() # Method access
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
Object obj = node.Object; // Property access
|
||||
Object obj = node.GetObject(); // Method access
|
||||
|
||||
The same principle applies for :ref:`Reference <class_Reference>` objects.
|
||||
While users often access :ref:`Node <class_Node>` and
|
||||
:ref:`Resource <class_Resource>` this way, alternative measures are available.
|
||||
|
||||
Instead of property or method access, one can get Resources by load
|
||||
access.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
var preres = preload(path) # Load resource during scene load
|
||||
var res = load(path) # Load resource when program reaches statement
|
||||
|
||||
# Note that users load scenes and scripts, by convention, with PascalCase
|
||||
# names (like typenames), often into constants.
|
||||
const MyScene : = preload("my_scene.tscn") as PackedScene # Static load
|
||||
const MyScript : = preload("my_script.gd") as Script
|
||||
|
||||
# This type's value varies, i.e. it is a variable, so it uses snake_case
|
||||
export(Script) var script_type: Script
|
||||
|
||||
# If need an "export const var" (which doesn't exist), use a conditional
|
||||
# setter for a tool script that checks if it's executing in the editor.
|
||||
tool # must place at top of file
|
||||
|
||||
# Must configure from the editor, defaults to null
|
||||
export(Script) var const_script setget set_const_script
|
||||
func set_const_script(value):
|
||||
if Engine.is_editor_hint():
|
||||
const_script = value
|
||||
|
||||
# Warn users if the value hasn't been set
|
||||
func _get_configuration_warning():
|
||||
if not const_script:
|
||||
return "Must initialize property 'const_script'."
|
||||
return ""
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
// Tool script added for the sake of the "const [Export]" example
|
||||
[Tool]
|
||||
public MyType : extends Object
|
||||
{
|
||||
// Property initializations load during Script instancing, i.e. .new().
|
||||
// No "preload" loads during scene load exists in C#.
|
||||
|
||||
// Initialize with a value. Editable at runtime.
|
||||
public Script MyScript = GD.Load("MyScript.cs");
|
||||
|
||||
// Initialize with same value. Value locked.
|
||||
public const Script MyConstScript = GD.Load("MyScript.cs");
|
||||
|
||||
// Like 'const' due to inaccessible setter.
|
||||
// But, value can be set during constructor, i.e. MyType().
|
||||
public Script Library { get; } = GD.Load("res://addons/plugin/library.gd") as Script;
|
||||
|
||||
// If need a "const [Export]" (which doesn't exist), use a
|
||||
// conditional setter for a tool script that checks if it's executing
|
||||
// in the editor.
|
||||
[Export]
|
||||
public PackedScene EnemyScn
|
||||
{
|
||||
get;
|
||||
|
||||
set
|
||||
{
|
||||
if (Engine.IsEditorHint())
|
||||
{
|
||||
EnemyScn = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Warn users if the value hasn't been set
|
||||
public String _GetConfigurationWarning()
|
||||
{
|
||||
if (EnemyScn == null)
|
||||
return "Must initialize property 'const_script'.";
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Note the following:
|
||||
|
||||
1. There are many ways in which a language can load such resources.
|
||||
|
||||
2. When designing how objects will access data, don't forget
|
||||
that one can pass resources around as references as well.
|
||||
|
||||
3. Keep in mind that loading a resource fetches the cached resource
|
||||
instance maintained by the engine. To get a new object, one must
|
||||
:ref:`duplicate <class_Resource_method_duplicate>` an existing reference or
|
||||
instantiate one from scratch with ``new()``.
|
||||
|
||||
Nodes likewise have an alternative access point: the SceneTree.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
extends Node
|
||||
|
||||
# Slow
|
||||
func dynamic_lookup_with_dynamic_nodepath():
|
||||
print(get_node("Child"))
|
||||
|
||||
# Faster. GDScript only
|
||||
func dynamic_lookup_with_cached_nodepath():
|
||||
print($Child)
|
||||
|
||||
# Fastest. Doesn't break if node moves later.
|
||||
# Note that `onready` keyword is GDScript only.
|
||||
# Other languages must do...
|
||||
# var child
|
||||
# func _ready():
|
||||
# child = get_node("Child")
|
||||
onready var child = $Child
|
||||
func lookup_and_cache_for_future_access():
|
||||
print(child)
|
||||
|
||||
# Delegate reference assignment to an external source
|
||||
# Con: need to perform a validation check
|
||||
# Pro: node makes no requirements of its external structure.
|
||||
# 'prop' can come from anywhere.
|
||||
var prop
|
||||
func call_me_after_prop_is_initialized_by_parent():
|
||||
# validate prop in one of three ways
|
||||
|
||||
# fail with no notification
|
||||
if not prop:
|
||||
return
|
||||
|
||||
# fail with an error message
|
||||
if not prop:
|
||||
printerr("'prop' wasn't initialized")
|
||||
return
|
||||
|
||||
# fail and terminate
|
||||
# Compiled scripts in final binary do not include assert statements
|
||||
assert prop
|
||||
|
||||
# Use an autoload.
|
||||
# Dangerous for typical nodes, but useful for true singleton nodes
|
||||
# that manage their own data and don't interfere with other objects.
|
||||
func reference_a_global_autoloaded_variable():
|
||||
print(globals)
|
||||
print(globals.prop)
|
||||
print(globals.my_getter())
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public class MyNode
|
||||
{
|
||||
// Slow, dynamic lookup with dynamic NodePath.
|
||||
public void Method1()
|
||||
{
|
||||
GD.Print(GetNode(NodePath("Child")));
|
||||
}
|
||||
|
||||
// Fastest. Lookup node and cache for future access.
|
||||
// Doesn't break if node moves later.
|
||||
public Node Child;
|
||||
public void _Ready()
|
||||
{
|
||||
Child = GetNode(NodePath("Child"));
|
||||
}
|
||||
public void Method2()
|
||||
{
|
||||
GD.Print(Child);
|
||||
}
|
||||
|
||||
// Delegate reference assignment to an external source
|
||||
// Con: need to perform a validation check
|
||||
// Pro: node makes no requirements of its external structure.
|
||||
// 'prop' can come from anywhere.
|
||||
public object Prop;
|
||||
public void CallMeAfterPropIsInitializedByParent() {
|
||||
// Validate prop in one of three ways
|
||||
|
||||
// Fail with no notification
|
||||
if (prop == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Fail with an error message
|
||||
if (prop == null)
|
||||
{
|
||||
GD.PrintErr("'Prop' wasn't initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fail and terminate
|
||||
Debug.Assert(Prop, "'Prop' wasn't initialized");
|
||||
}
|
||||
|
||||
// Use an autoload.
|
||||
// Dangerous for typical nodes, but useful for true singleton nodes
|
||||
// that manage their own data and don't interfere with other objects.
|
||||
public void ReferenceAGlobalAutoloadedVariable()
|
||||
{
|
||||
Node globals = GetNode(NodePath("/root/Globals"));
|
||||
GD.Print(globals);
|
||||
GD.Print(globals.prop);
|
||||
GD.Print(globals.my_getter());
|
||||
}
|
||||
};
|
||||
|
||||
Accessing data or logic from an object
|
||||
--------------------------------------
|
||||
|
||||
Godot's scripting API is duck-typed. This means that if a script executes an
|
||||
operation, Godot doesn't validate that it supports the operation by **type**.
|
||||
It instead checks that the object **implements** the individual method.
|
||||
|
||||
For example, the :ref:`CanvasItem <class_CanvasItem>` class has a ``visible``
|
||||
property. All properties exposed to the scripting API are in fact a setter and
|
||||
getter pair bound to a name. If one tried to access
|
||||
:ref:`CanvasItem.visible <class_CanvasItem_property_visible>`, then Godot would do the
|
||||
following checks, in order:
|
||||
|
||||
- If the object has a script attached, it will attempt to set the property
|
||||
through the script. This leaves open the opportunity for scripts to override
|
||||
a property defined on a base object by overriding the setter method for the
|
||||
property.
|
||||
|
||||
- If the script does not have the property, it performs a HashMap lookup in
|
||||
the ClassDB for the "visible" property against the CanvasItem class and all
|
||||
of its inherited types. If found, it will call the bound setter or getter.
|
||||
For more information about HashMaps, see the
|
||||
:ref:`data preferences <doc_data_preferences>` docs.
|
||||
|
||||
- If not found, it does an explicit check to see if the user wants to access
|
||||
the "script" or "meta" properties.
|
||||
|
||||
- If not, it checks for a ``_set``/``_get`` implementation (depending on type
|
||||
of access) in the CanvasItem and its inherited types. These methods can
|
||||
execute logic that gives the impression that the Object has a property. This
|
||||
is also the case with the ``_get_property_list`` method.
|
||||
|
||||
- Note that this happens even for non-legal symbol names such as in the
|
||||
case of :ref:`TileSet <class_TileSet>`'s "1/tile_name" property. This
|
||||
refers to the name of the tile with ID 1, i.e.
|
||||
:ref:`TileSet.tile_get_name(1) <class_TileSet_method_tile_get_name>`.
|
||||
|
||||
As a result, this duck-typed system can locate a property either in the script,
|
||||
the object's class, or any class that object inherits, but only for things
|
||||
which extend Object.
|
||||
|
||||
Godot provides a variety of options for performing runtime checks on these
|
||||
accesses:
|
||||
|
||||
- A duck-typed property access. These will property check (as described above).
|
||||
If the operation isn't supported by the object, execution will halt.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
# All Objects have duck-typed get, set, and call wrapper methods
|
||||
get_parent().set("visible", false)
|
||||
|
||||
# Using a symbol accessor, rather than a string in the method call,
|
||||
# will implicitly call the `set` method which, in turn, calls the
|
||||
# setter method bound to the property through the property lookup
|
||||
# sequence.
|
||||
get_parent().visible = false
|
||||
|
||||
# Note that if one defines a _set and _get that describe a property's
|
||||
# existence, but the property isn't recognized in any _get_property_list
|
||||
# method, then the set() and get() methods will work, but the symbol
|
||||
# access will claim it can't find the property.
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
// All Objects have duck-typed Get, Set, and Call wrapper methods
|
||||
GetParent().Set("visible", false);
|
||||
|
||||
// C# is a static language, so it has no dynamic symbol access, e.g.
|
||||
// `GetParent().Visible = false` won't work
|
||||
|
||||
- A method check. In the case of
|
||||
:ref:`CanvasItem.visible <class_CanvasItem_property_visible>`, one can
|
||||
access the methods, ``set_visible`` and ``is_visible`` like any other method.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
var child = GetChild(0)
|
||||
|
||||
# Dynamic lookup
|
||||
child.call("set_visible", false)
|
||||
|
||||
# Symbol-based dynamic lookup
|
||||
# GDScript aliases this into a 'call' method behind the scenes.
|
||||
child.set_visible(false)
|
||||
|
||||
# Dynamic lookup, checks for method existence first
|
||||
if child.has("set_visible"):
|
||||
child.set_visible(false)
|
||||
|
||||
# Cast check, followed by dynamic lookup
|
||||
# Useful when you make multiple "safe" calls knowing that the class
|
||||
# implements them all. No need for repeated checks.
|
||||
# Tricky if one executes a cast check for a user-defined type as it
|
||||
# forces more dependencies.
|
||||
if child is CanvasItem:
|
||||
child.set_visible(false)
|
||||
child.show_on_top = true
|
||||
|
||||
# If one does not wish to fail these checks without notifying users, one
|
||||
# can use an assert instead. These will trigger runtime errors
|
||||
# immediately if not true.
|
||||
assert child.has("set_visible")
|
||||
assert child.is_in_group("offer")
|
||||
assert child is CanvasItem
|
||||
|
||||
# Can also use object labels to imply an interface, i.e. assume it implements certain methods.
|
||||
# There are two types, both of which only exist for Nodes: Names and Groups
|
||||
|
||||
# Assuming...
|
||||
# A "Quest" object exists and 1) that it can "complete" or "fail" and
|
||||
# that it will have text available before and after each state...
|
||||
|
||||
# 1. Use a name
|
||||
var quest = $Quest
|
||||
print(quest.text)
|
||||
quest.complete() # or quest.fail()
|
||||
print(quest.text) # implied new text content
|
||||
|
||||
# 2. Use a group
|
||||
for a_child in get_children():
|
||||
if a_child.is_in_group("quest"):
|
||||
print(quest.text)
|
||||
quest.complete() # or quest.fail()
|
||||
print(quest.text) # implied new text content
|
||||
|
||||
# Note that these interfaces are project-specific conventions the team
|
||||
# defines (which means documentation! But maybe worth it?).
|
||||
# Any script that conforms to the documented "interface" of the name/group can fill in for it.
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
Node child = GetChild(0);
|
||||
|
||||
// Dynamic lookup
|
||||
child.Call("SetVisible", false);
|
||||
|
||||
// Dynamic lookup, checks for method existence first
|
||||
if (child.HasMethod("SetVisible"))
|
||||
{
|
||||
child.Call("SetVisible", false);
|
||||
}
|
||||
|
||||
// Use a group as if it were an "interface", i.e. assume it implements certain methods
|
||||
// requires good documentation for the project to keep it reliable (unless you make
|
||||
// editor tools to enforce it at editor time.
|
||||
// Note, this is generally not as good as using an actual interface in C#,
|
||||
// but you can't set C# interfaces from the editor since they are
|
||||
// language-level features.
|
||||
if (child.IsInGroup("Offer"))
|
||||
{
|
||||
child.Call("Accept");
|
||||
child.Call("Reject");
|
||||
}
|
||||
|
||||
// Cast check, followed by static lookup
|
||||
CanvasItem ci = GetParent() as CanvasItem;
|
||||
if (ci != null)
|
||||
{
|
||||
ci.SetVisible(false);
|
||||
|
||||
// useful when you need to make multiple safe calls to the class
|
||||
ci.ShowOnTop = true;
|
||||
}
|
||||
|
||||
// If one does not wish to fail these checks without notifying users, one
|
||||
// can use an assert instead. These will trigger runtime errors
|
||||
// immediately if not true.
|
||||
Debug.Assert(child.HasMethod("set_visible"));
|
||||
Debug.Assert(child.IsInGroup("offer"));
|
||||
Debug.Assert(CanvasItem.InstanceHas(child));
|
||||
|
||||
// Can also use object labels to imply an interface, i.e. assume it implements certain methods.
|
||||
// There are two types, both of which only exist for Nodes: Names and Groups
|
||||
|
||||
// Assuming...
|
||||
// A "Quest" object exists and 1) that it can "Complete" or "Fail" and
|
||||
// that it will have Text available before and after each state...
|
||||
|
||||
// 1. Use a name
|
||||
Node quest = GetNode("Quest");
|
||||
GD.Print(quest.Get("Text"));
|
||||
quest.Call("Complete"); // or "Fail"
|
||||
GD.Print(quest.Get("Text")); // implied new text content
|
||||
|
||||
// 2. Use a group
|
||||
foreach (Node AChild in GetChildren())
|
||||
{
|
||||
if (AChild.IsInGroup("quest"))
|
||||
{
|
||||
GD.Print(quest.Get("Text"));
|
||||
quest.Call("Complete"); // or "Fail"
|
||||
GD.Print(quest.Get("Text")); // implied new text content
|
||||
}
|
||||
}
|
||||
|
||||
// Note that these interfaces are project-specific conventions the team
|
||||
// defines (which means documentation! But maybe worth it?)..
|
||||
// Any script that conforms to the documented "interface" of the
|
||||
// name/group can fill in for it. Also note that in C#, these methods
|
||||
// will be slower than static accesses with traditional interfaces.
|
||||
|
||||
- Outsource the access to a :ref:`FuncRef <class_FuncRef>`. These may be useful
|
||||
in cases where one needs the max level of freedom from dependencies. In
|
||||
this case, one relies on an external context to setup the method.
|
||||
|
||||
..tabs::
|
||||
..code-tab:: gdscript GDScript
|
||||
|
||||
# child.gd
|
||||
extends Node
|
||||
var fn = null
|
||||
|
||||
func my_method():
|
||||
if fn:
|
||||
fn.call_func()
|
||||
|
||||
# parent.gd
|
||||
extends Node
|
||||
|
||||
onready var child = $Child
|
||||
|
||||
func _ready():
|
||||
child.fn = funcref(self, "print_me")
|
||||
child.my_method()
|
||||
|
||||
func print_me():
|
||||
print(name)
|
||||
|
||||
..code-tab:: csharp
|
||||
|
||||
// Child.cs
|
||||
public class Child extends Node
|
||||
{
|
||||
public FuncRef FN = null;
|
||||
|
||||
public void MyMethod()
|
||||
{
|
||||
Debug.Assert(FN != null);
|
||||
FN.CallFunc();
|
||||
}
|
||||
}
|
||||
|
||||
// Parent.cs
|
||||
public class Parent extends Node
|
||||
{
|
||||
public Node Child;
|
||||
|
||||
public void _Ready()
|
||||
{
|
||||
Child = GetNode("Child");
|
||||
Child.Set("FN", GD.FuncRef(this, "PrintMe"));
|
||||
Child.MyMethod();
|
||||
}
|
||||
|
||||
public void PrintMe() {
|
||||
{
|
||||
GD.Print(GetClass());
|
||||
}
|
||||
}
|
||||
|
||||
These strategies contribute to Godot's flexible design. Between them, users
|
||||
have a breadth of tools to meet their specific needs.
|
||||
@@ -0,0 +1,288 @@
|
||||
.. _doc_godot_notifications:
|
||||
|
||||
Godot notifications
|
||||
===================
|
||||
|
||||
Every Object in Godot implements a
|
||||
:ref:`_notification <class_Object_method__notification>` method. Its purpose is to
|
||||
allow the Object to respond to a variety of engine-level callbacks that may
|
||||
relate to it. For example, if the engine tells a
|
||||
:ref:`CanvasItem <class_CanvasItem>` to "draw", it will call
|
||||
``_notification(NOTIFICATION_DRAW)``.
|
||||
|
||||
Some of these notifications, like draw, are useful to override in scripts. So
|
||||
much so that Godot exposes many of them with dedicated functions:
|
||||
|
||||
- ``_ready()`` : NOTIFICATION_READY
|
||||
|
||||
- ``_enter_tree()`` : NOTIFICATION_ENTER_TREE
|
||||
|
||||
- ``_exit_tree()`` : NOTIFICATION_EXIT_TREE
|
||||
|
||||
- ``_process(delta)`` : NOTIFICATION_PROCESS
|
||||
|
||||
- ``_physics_process(delta)`` : NOTIFICATION_PHYSICS_PROCESS
|
||||
|
||||
- ``_input()`` : NOTIFICATION_INPUT
|
||||
|
||||
- ``_unhandled_input()`` : NOTIFICATION_UNHANDLED_INPUT
|
||||
|
||||
- ``_draw()`` : NOTIFICATION_DRAW
|
||||
|
||||
What users might *not* realize is that notifications exist for types other
|
||||
than Node alone:
|
||||
|
||||
- :ref:`Object::NOTIFICATION_POSTINITIALIZE <class_Object_constant_NOTIFICATION_POSTINITIALIZE>`:
|
||||
a callback that triggers during object initialization. Not accessible to scripts.
|
||||
|
||||
- :ref:`Object::NOTIFICATION_PREDELETE <class_Object_constant_NOTIFICATION_PREDELETE>`:
|
||||
a callback that triggers before the engine deletes an Object, i.e. a
|
||||
'destructor'.
|
||||
|
||||
- :ref:`MainLoop::NOTIFICATION_WM_MOUSE_ENTER <class_MainLoop_constant_NOTIFICATION_WM_MOUSE_ENTER>`:
|
||||
a callback that triggers when the mouse enters the window in the operating
|
||||
system that displays the game content.
|
||||
|
||||
And many of the callbacks that *do* exist in Nodes don't have any dedicated
|
||||
methods, but are still quite useful.
|
||||
|
||||
- :ref:`Node::NOTIFICATION_PARENTED <class_Node_constant_NOTIFICATION_PARENTED>`:
|
||||
a callback that triggers anytime one adds a child node to another node.
|
||||
|
||||
- :ref:`Node::NOTIFICATION_UNPARENTED <class_Node_constant_NOTIFICATION_UNPARENTED>`:
|
||||
a callback that triggers anytime one removes a child node from another
|
||||
node.
|
||||
|
||||
- :ref:`Popup::NOTIFICATION_POST_POPUP <class_Popup_constant_NOTIFICATION_POST_POPUP>`:
|
||||
a callback that triggers after a Popup node completes any ``popup*`` method.
|
||||
Note the difference from its ``about_to_show`` signal which triggers
|
||||
*before* its appearance.
|
||||
|
||||
One can access all these custom notifications from the universal
|
||||
``_notification`` method.
|
||||
|
||||
..note::
|
||||
Methods in the documentation labeled as "virtual" are also intended to be
|
||||
overridden by scripts.
|
||||
|
||||
A classic example is the
|
||||
:ref:`_init <class_Object_method__init>` method in Object. While it has no
|
||||
NOTIFICATION_* equivalent, the engine still calls the method. Most languages
|
||||
(except C#) rely on it as a constructor.
|
||||
|
||||
So, in which situation should one use each of these notifications or
|
||||
virtual functions?
|
||||
|
||||
_process vs. _physics_process vs. *_input
|
||||
-----------------------------------------
|
||||
|
||||
Use ``_process`` when one needs a framerate-dependent deltatime between
|
||||
frames. If code that updates object data needs to update as often as
|
||||
possible, this is the right place. Recurring logic checks and data caching
|
||||
often execute here, but it comes down to the frequency at which one needs
|
||||
the evaluations to update. If they don't need to execute every frame, then
|
||||
implementing a Timer-yield-timeout loop is another option.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab::
|
||||
|
||||
# Infinitely loop, but only execute whenever the Timer fires.
|
||||
# Allows for recurring operations that don't trigger script logic
|
||||
# every frame (or even every fixed frame).
|
||||
while true:
|
||||
my_method()
|
||||
$Timer.start()
|
||||
yield($Timer, "timeout")
|
||||
|
||||
Use ``_physics_process`` when one needs a framerate-independent deltatime
|
||||
between frames. If code needs consistent updates over time, regardless
|
||||
of how fast or slow time advances, this is the right place.
|
||||
Recurring kinematic and object transform operations should execute here.
|
||||
|
||||
While it is possible, to achieve the best performance, one should avoid
|
||||
making input checks during these callbacks. ``_process`` and
|
||||
``_physics_process`` will trigger at every opportunity (they do not "rest" by
|
||||
default). In contrast, ``*_input`` callbacks will trigger only on frames in
|
||||
which the engine has actually detected the input.
|
||||
|
||||
One can check for input actions within the input callbacks just the same.
|
||||
If one wants to use delta time, one can fetch it from the
|
||||
:ref:`Performance <class_Performance>` singleton as needed.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
# Called every frame, even when the engine detects no input
|
||||
func _process(delta):
|
||||
if Input.action_just_pressed("ui_select"):
|
||||
print(delta)
|
||||
|
||||
# Called during every input event
|
||||
func _unhandled_input(event):
|
||||
match event.get_class():
|
||||
"InputEventKey":
|
||||
if Input.is_action_just_pressed("ui_accept"):
|
||||
print(Performance.get_monitor(Performance.TIME_PROCESS))
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
public class MyNode : public Node {
|
||||
|
||||
// Called every frame, even when the engine detects no input
|
||||
public void _Process(float delta) {
|
||||
if (GD.Input.ActionJustPressed("UiSelect")) {
|
||||
GD.Print(string(delta));
|
||||
}
|
||||
}
|
||||
|
||||
// Called during every input event. Equally true for _input().
|
||||
public void _UnhandledInput(InputEvent event) {
|
||||
switch (event.GetClass()) {
|
||||
case "InputEventAction":
|
||||
GD.Print(string(GD.Performance.GetMonitor(GD.Performance.TIME_PROCESS)));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_init vs. initialization vs. export
|
||||
-----------------------------------
|
||||
|
||||
If the script initializes its own node subtree, without a scene,
|
||||
that code should execute here. Other property or SceneTree-independent
|
||||
initializations should also run here. This triggers before ``_ready`` or
|
||||
``_enter_tree``, but after a script creates and initializes its properties.
|
||||
|
||||
Scripts have three types of property assignments that can occur during
|
||||
instantiation:
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
# "one" is an "initialized value". These DO NOT trigger the setter.
|
||||
# If someone set the value as "two" from the Inspector, this would be an
|
||||
# "exported value". These DO trigger the setter.
|
||||
export(String) var test = "one" setget set_test
|
||||
|
||||
func _init():
|
||||
# "three" is an "init assignment value".
|
||||
# These DO NOT trigger the setter, but...
|
||||
test = "three"
|
||||
# These DO trigger the setter. Note the `self` prefix.
|
||||
self.test = "three"
|
||||
|
||||
func set_test(value):
|
||||
test = value
|
||||
print("Setting: ", test)
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
// "one" is an "initialized value". These DO NOT trigger the setter.
|
||||
// If one set the value as "two" from the Inspector, this would be an
|
||||
// "exported value". These DO trigger the setter.
|
||||
[Export]
|
||||
public string Test = "one"
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
Test = value;
|
||||
GD.Print("Setting: " + Test);
|
||||
}
|
||||
}
|
||||
|
||||
public void _Init() {
|
||||
// "three" is an "init assignment value".
|
||||
// These DO (NOT?) trigger the setter.
|
||||
Test = "three";
|
||||
}
|
||||
|
||||
When instantiating a scene, property values will set up according to the
|
||||
following sequence:
|
||||
|
||||
1. **Initial value assignment:** instantiation will assign either the
|
||||
initialization value or the init assignment value. Init assignments take
|
||||
priority over initialization values.
|
||||
|
||||
2. **Exported value assignment:** If instancing from a scene rather than
|
||||
a script, Godot will assign the exported value to replace the initial
|
||||
value defined in the script.
|
||||
|
||||
As a result, instantiating a script versus a scene will affect both the
|
||||
initialization *and* the number of times the engine calls the setter.
|
||||
|
||||
_ready vs. _enter_tree vs. NOTIFICATION_PARENTED
|
||||
------------------------------------------------
|
||||
|
||||
When instantiating a scene connected to the first executed scene, Godot will
|
||||
instantiate nodes down the tree (making ``_init`` calls) and build the tree
|
||||
going downwards from the root. This causes ``_enter_tree`` calls to cascade
|
||||
down the tree. Once the tree is complete, leaf nodes call ``_ready``. A node
|
||||
will call this method once all child nodes have finished calling theirs. This
|
||||
then causes a reverse cascade going up back to the tree's root.
|
||||
|
||||
When instantiating a script or a standalone scene, nodes are not
|
||||
added to the SceneTree upon creation, so no ``_enter_tree`` callbacks
|
||||
trigger. Instead, only the ``_init`` and later ``_ready`` calls occur.
|
||||
|
||||
If one needs to trigger behavior that occurs as nodes parent to another,
|
||||
regardless of whether it occurs as part of the main/active scene or not, one
|
||||
can use the :ref:`PARENTED <class_Node_constant_NOTIFICATION_PARENTED>` notification.
|
||||
For example, here is a snippet that connects a node's method to
|
||||
a custom signal on the parent node without failing. Useful on data-centric
|
||||
nodes that one might create at runtime.
|
||||
|
||||
..tabs::
|
||||
..code-tab:: gdscript GDScript
|
||||
|
||||
extends Node
|
||||
|
||||
var parent_cache
|
||||
|
||||
func connection_check():
|
||||
return parent.has_user_signal("interacted_with")
|
||||
|
||||
func _notification(what):
|
||||
match what:
|
||||
NOTIFICATION_PARENTED:
|
||||
parent_cache = get_parent()
|
||||
if connection_check():
|
||||
parent_cache.connect("interacted_with", self, "_on_parent_interacted_with")
|
||||
NOTIFICATION_UNPARENTED:
|
||||
if connection_check():
|
||||
parent_cache.disconnect("interacted_with", self, "_on_parent_interacted_with")
|
||||
|
||||
func _on_parent_interacted_with():
|
||||
print("I'm reacting to my parent's interaction!")
|
||||
|
||||
..code-tab:: csharp
|
||||
|
||||
public class MyNode extends Node {
|
||||
|
||||
public Node ParentCache = null;
|
||||
|
||||
public void ConnectionCheck() {
|
||||
return ParentCache.HasUserSignal("InteractedWith");
|
||||
}
|
||||
|
||||
public void _Notification(int What) {
|
||||
switch (What) {
|
||||
case NOTIFICATION_PARENTED:
|
||||
ParentCache = GetParent();
|
||||
if (ConnectionCheck())
|
||||
ParentCache.Connect("InteractedWith", this, "OnParentInteractedWith");
|
||||
break;
|
||||
case NOTIFICATION_UNPARENTED:
|
||||
if (ConnectionCheck())
|
||||
ParentCache.Disconnect("InteractedWith", this, "OnParentInteractedWith");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnParentInteractedWith() {
|
||||
GD.Print("I'm reacting to my parent's interaction!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
Scene workflow
|
||||
===============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:name: toc-best-practices
|
||||
|
||||
introduction_best_practices
|
||||
what_are_godot_classes
|
||||
scene_organization
|
||||
scenes_versus_scripts
|
||||
autoloads_versus_internal_nodes
|
||||
node_alternatives
|
||||
godot_interfaces
|
||||
godot_notifications
|
||||
data_preferences
|
||||
logic_preferences
|
||||
@@ -0,0 +1,24 @@
|
||||
.. _doc_introduction_best_practices:
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
This tutorial series aims to illustrate intended Godot workflows, i.e. the
|
||||
"best practices" of defining one's game API. Godot allows for a great
|
||||
amount of flexibility in how one designs a project's code and scene
|
||||
structure. Each method will have its own advantages and disadvantages,
|
||||
so outlining the best practices should help users feel less confused
|
||||
as they further explore Godot's depth.
|
||||
|
||||
This series is also designed to help get users thinking about the best way
|
||||
to reason about best practices. It would be impossible to give a comprehensive
|
||||
overview of what one should do for any arbitrary design decision. As such,
|
||||
each article breaks problems down into the fundamental questions they pose,
|
||||
suggests the available solutions, analyzes the advantages and disadvantages
|
||||
of each option, and then highlights the best course of action depending on the
|
||||
circumstances.
|
||||
|
||||
While the articles in this series are largely topic-directed, it is
|
||||
recommended that users begin with the "What are godot classes" article.
|
||||
It is from there that the "best practices" for the rest of the engine
|
||||
become more clear, based on established OOP practices.
|
||||
@@ -0,0 +1,162 @@
|
||||
.. _doc_logic_preferences:
|
||||
|
||||
Logic preferences
|
||||
=================
|
||||
|
||||
Ever wondered whether one should approach problem X with strategy Y or Z?
|
||||
This article covers a variety of topics related to these dilemmas.
|
||||
|
||||
Loading vs. preloading
|
||||
----------------------
|
||||
|
||||
In GDScript, there exists the global
|
||||
:ref:`preload <class_@GDScript_method_preload>` method. It loads resources as
|
||||
early as possible to front-load the "loading" operations and avoid loading
|
||||
resources while in the middle of performance-sensitive code.
|
||||
|
||||
Its counterpart, the :ref:`load <class_@GDScript_method_load>` method, loads a
|
||||
resource only when it reaches the load statement. That is, it will load a
|
||||
resource in-place which can cause slowdowns then it occurs in the middle of
|
||||
sensitive processes. The ``load`` function is also an alias for
|
||||
:ref:`ResourceLoader.load(path) <class_ResourceLoader_method_load>` which is
|
||||
accessible to *all* scripting languages.
|
||||
|
||||
So, when exactly does preloading occur versus loading, and when should one use
|
||||
either? Let's see an example:
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
# my_buildings.gd
|
||||
extends Node
|
||||
|
||||
# (note how constant scripts/scenes have a diferent naming scheme than
|
||||
# their property variants).
|
||||
|
||||
# This value is a constant, so it spawns when the Script object loads.
|
||||
# The script is preloading the value. The advantage here is that the editor
|
||||
# can offer autocompletion since it must be a static path.
|
||||
const BuildingScn = preload("building.tscn")
|
||||
|
||||
# 1. The script preloads the value, so it will load as a dependency
|
||||
# of the 'my_buildings.gd' script file. But, because this is a
|
||||
# property rather than a constant, the object won't copy the preloaded
|
||||
# PackedScene resource into the property until the script instantiates
|
||||
# with .new().
|
||||
#
|
||||
# 2. The preloaded value is inaccessible from the Script object alone. As
|
||||
# such, preloading the value here actually does not benefit anyone.
|
||||
#
|
||||
# 3. Because the user exports the value, if this script stored on
|
||||
# a node in a scene file, the scene instantation code will overwrite the
|
||||
# preloaded initial value anyway (wasting it). It's usually better to
|
||||
# provide null, empty, or otherwise invalid default values for exports.
|
||||
#
|
||||
# 4. It is when one instantiates this script on its own with .new() that
|
||||
# one will load "office.tscn" rather than the exported value.
|
||||
export(PackedScene) var a_building = preload("office.tscn")
|
||||
|
||||
# Uh oh! This results in an error!
|
||||
# One must assign constant values to constants. Because `load` performs a
|
||||
# runtime lookup by its very nature, one cannot use it to initialize a
|
||||
# constant.
|
||||
const OfficeScn = load("office.tscn")
|
||||
|
||||
# Successfully loads and only when one instantiates the script! Yay!
|
||||
var office_scn = load("office.tscn")
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using System;
|
||||
using GD;
|
||||
|
||||
public class MyBuildings : public Node {
|
||||
|
||||
public const PackedScene Building { get; set; }
|
||||
|
||||
public PackedScene ABuilding = GD.load("office.tscn") as PackedScene;
|
||||
|
||||
public void MyBuildings() {
|
||||
// Can assign the value during initialization or during a constructor.
|
||||
Building = ResourceLoader.load("building.tscn") as PackedScene;
|
||||
|
||||
// C# and other languages have no concept of "preloading".
|
||||
}
|
||||
}
|
||||
|
||||
Preloading allows the script to handle all the loading the moment one loads the
|
||||
script. Preloading is useful, but there are also times when one doesn't wish
|
||||
for it. To distinguish these situations, there are a few things one can
|
||||
consider:
|
||||
|
||||
1. If one cannot determine when the script might load, then preloading a
|
||||
resource, especially a scene or script, could result in further loads one
|
||||
does not expect. This could lead to unintentional, variable-length
|
||||
load times on top of the original script's load operations.
|
||||
|
||||
2. If something else could replace the value (like a scene's exported
|
||||
initialization), then preloading the value has no meaning. This point isn't
|
||||
a significant factor if one intends to always create the script on its own.
|
||||
|
||||
3. If one wishes only to 'import' another class resource (script or scene),
|
||||
then using a preloaded constant is often the best course of action. However,
|
||||
in exceptional cases, one my wish not to do this:
|
||||
|
||||
1. If the 'imported' class is liable to change, then it should be a property
|
||||
instead, initialized either using an ``export`` or a ``load`` (and
|
||||
perhaps not even initialized until later).
|
||||
|
||||
2. If the script requires a great many dependencies, and one does not wish
|
||||
to consume so much memory, then one may wish to, load and unload various
|
||||
dependencies at runtime as circumstances change. If one preloads
|
||||
resources into constants, then the only way to unload these resources
|
||||
would be to unload the entire script. If they are instead loaded
|
||||
properties, then one can set them to ``null`` and remove all references
|
||||
to the resource entirely (which, as a
|
||||
:ref:`Reference <class_Reference>`-extending type, will cause the
|
||||
resources to delete themselves from memory).
|
||||
|
||||
Large levels: static vs. dynamic
|
||||
--------------------------------
|
||||
|
||||
If one is creating a large level, which circumstances are most appropriate?
|
||||
Should they create the level as one static space? Or should they load the
|
||||
level in pieces and shift the world's content as needed?
|
||||
|
||||
Well, the simple answer is , "when the performance requires it." The
|
||||
dilemma associated with the two options is one of the age-old programming
|
||||
choices: does one optimize memory over speed, or vice versa?
|
||||
|
||||
The naive answer is to use a static level that loads everything at once.
|
||||
But, depending on the project, this could consume a large amount of
|
||||
memory. Wasting users' RAM leads to programs running slow or outright
|
||||
crashing from everything else the computer tries to do at the same time.
|
||||
|
||||
No matter what, one should break larger scenes into smaller ones (to aid
|
||||
in reusability of assets). Developers can then design a node that manages the
|
||||
creation/loading and deletion/unloading of resources and nodes in real-time.
|
||||
Games with large and varied environments or procedurally generated
|
||||
elements often implement these strategies to avoid wasting memory.
|
||||
|
||||
On the flip side, coding a dynamic system is more complex, i.e. uses more
|
||||
programmed logic, which results in opportunities for errors and bugs. If one
|
||||
isn't careful, they can develop a system that bloats the technical debt of
|
||||
the application.
|
||||
|
||||
As such, the best options would be...
|
||||
|
||||
1. To use a static level for smaller games.
|
||||
|
||||
2. If one has the time/resources on a medium/large game, create a library or
|
||||
plugin that can code the management of nodes and resources. If refined
|
||||
over time, so as to improve usability and stability, then it could evolve
|
||||
into a reliable tool across projects.
|
||||
|
||||
3. Code the dynamic logic for a medium/large game because one has the coding
|
||||
skills, but not the time or resources to refine the code (game's
|
||||
gotta get done). Could potentially refactor later to outsource the code
|
||||
into a plugin.
|
||||
|
||||
For an example of the various ways one can swap scenes around at runtime,
|
||||
please see the :ref:`"Change scenes manually" <doc_change_scenes_manually>`
|
||||
documentation.
|
||||
@@ -0,0 +1,62 @@
|
||||
.. _doc_node_alternatives:
|
||||
|
||||
When and how to avoid using nodes for everything
|
||||
================================================
|
||||
|
||||
|
||||
Nodes are cheap to produce, but even they have their limits. A project may
|
||||
have tens of thousands of nodes all doing things. The more complex their
|
||||
behavior though, the larger the strain each one adds to a project's
|
||||
performance.
|
||||
|
||||
Godot provides more lightweight objects for creating APIs which nodes use.
|
||||
Be sure to keep these in mind as options when designing how you wish to build
|
||||
your project's features.
|
||||
|
||||
1. :ref:`Object <class_Object>`: The ultimate lightweight object, the original
|
||||
Object must use manual memory management. With that said, it isn't too
|
||||
difficult to create one's own custom data structures, even node structures,
|
||||
that are also lighter than the :ref:`Node <class_Node>` class.
|
||||
|
||||
- Example: see the :ref:`Tree <class_Tree>` node. It supports a high level
|
||||
of customization for a table of content with an arbitrary number of
|
||||
rows and columns. The data that it uses to generate its visualization
|
||||
though is actually a tree of :ref:`TreeItem <class_TreeItem>` Objects.
|
||||
|
||||
- Advantages: Simplifying one's API to smaller scoped objects helps improve
|
||||
its accessibility improve iteration time. Rather than working with the
|
||||
entire Node library, one creates an abbreviated set of Objects from which
|
||||
a node can generate and manage the appropriate sub-nodes.
|
||||
|
||||
- Note: One should be careful when handling them. One can store an Object
|
||||
into a variable, but these references can become invalid without warning.
|
||||
For example, if the object's creator decides to delete it out of nowhere,
|
||||
this would trigger an error state when one next accesses it.
|
||||
|
||||
2. :ref:`Reference <class_Reference>`: Only a little more complex than Object.
|
||||
They track references to themselves, only deleting loaded memory when no
|
||||
further references to themselves exist. These are useful in the majority of
|
||||
cases where one needs data in a custom class.
|
||||
|
||||
- Example: see the :ref:`File <class_File>` object. It functions
|
||||
just like a regular Object except that one need not delete it themselves.
|
||||
|
||||
- Advantages: same as the Object.
|
||||
|
||||
3. :ref:`Resource <class_Resource>`: Only slightly more complex than Reference.
|
||||
They have the innate ability to serialize/deserialize (i.e. save and load)
|
||||
their object properties to/from Godot resource files.
|
||||
|
||||
- Example: Scripts, PackedScene (for scene files), and other types like
|
||||
each of the :ref:`AudioEffect <class_AudioEffect>` classes. Each of these
|
||||
can be save and loaded, therefore they extend from Resource.
|
||||
|
||||
- Advantages: Much has
|
||||
:ref:`already been said <http://docs.godotengine.org/en/latest/getting_started/step_by_step/resources.html#creating-your-own-resources>`
|
||||
on :ref:`Resource <class_Resource>`'s advantages over traditional data
|
||||
storage methods. In the context of using Resources over Nodes though,
|
||||
their main advantage is in Inspector-compatibility. While nearly as
|
||||
lightweight as Object/Reference, they can still display and export
|
||||
properties in the Inspector. This allows them to fulfill a purpose much
|
||||
like sub-Nodes on the usability front, but also improve performance if
|
||||
one plans to have many such Resources/Nodes in their scenes.
|
||||
@@ -0,0 +1,391 @@
|
||||
.. _doc_scene_organization:
|
||||
|
||||
Scene organization
|
||||
==================
|
||||
|
||||
This article covers topics related to the effective organization of
|
||||
scene content. Which nodes should one use? Where should one place them?
|
||||
How should they interact?
|
||||
|
||||
How to build relationships effectively
|
||||
--------------------------------------
|
||||
|
||||
When Godot users begin crafting their own scenes, they often run into the
|
||||
following problem:
|
||||
|
||||
They create their first scene and fill it with content before the creeping
|
||||
sense that they need to split it up into re-usable pieces haunts them. They
|
||||
save branches of their scene into their own scene. However, they then notice
|
||||
that the hard references they were able to rely on before are no longer
|
||||
possible. Re-using the scene in multiple places creates issues because the
|
||||
node paths do not find their targets. 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.
|
||||
|
||||
One of the biggest things to consider in OOP is maintaining
|
||||
focused, singular-purpose classes with
|
||||
`loose coupling <https://en.wikipedia.org/wiki/Loose_coupling>`_
|
||||
to other parts of the codebase. This keeps the size of objects small (for
|
||||
maintainability) and improves their reusability so that re-writing completed
|
||||
logic is unnecessary.
|
||||
|
||||
These OOP best practices have *several* ramifications for the 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
|
||||
themselves.
|
||||
|
||||
If a scene must interact with an external context, experienced developers
|
||||
recommend the use of
|
||||
`Dependency Injection <https://en.wikipedia.org/wiki/Dependency_injection>`_.
|
||||
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 inadvertantly trigger bugs and unexpected behavior.
|
||||
|
||||
To do this, one must expose data and then rely on a parent context to
|
||||
initialize it:
|
||||
|
||||
1. Connect to a signal. Extremely safe, but should use only to "respond" to
|
||||
behavior, not start it. Note that signal names are usually past-tense verbs
|
||||
like "entered", "skill_activated", or "item_collected".
|
||||
|
||||
..tabs::
|
||||
..code-tab:: gdscript GDScript
|
||||
|
||||
# Parent
|
||||
$Child.connect("signal_name", object_with_method, "method_on_the_object")
|
||||
|
||||
# Child
|
||||
emit_signal("signal_name") # Triggers parent-defined behavior
|
||||
|
||||
..code-tab:: csharp
|
||||
|
||||
// Parent
|
||||
GetNode("Child").Connect("SignalName", ObjectWithMethod, "MethodOnTheObject");
|
||||
|
||||
// Child
|
||||
EmitSignal("SignalName"); // Triggers parent-defined behavior
|
||||
|
||||
2. Call a method. Used to start behavior.
|
||||
|
||||
..tabs::
|
||||
..code-tab:: gdscript GDScript
|
||||
|
||||
# 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)
|
||||
|
||||
..code-tab:: csharp
|
||||
|
||||
// Parent
|
||||
GetNode("Child").Set("MethodName", "Do");
|
||||
|
||||
// Child
|
||||
Call(MethodName); // Call parent-defined method (which child must own)
|
||||
|
||||
3. Initialize a :ref:`FuncRef <class_FuncRef>` property. Safer than a method
|
||||
as ownership of the method is unnecessary. Used to start behavior.
|
||||
|
||||
..tabs::
|
||||
..code-tab:: gdscript GDScript
|
||||
|
||||
# Parent
|
||||
$Child.func_property = funcref(object_with_method, "method_on_the_object")
|
||||
|
||||
# Child
|
||||
func_property.call_func() # Call parent-defined method (can come from anywhere)
|
||||
|
||||
..code-tab:: csharp
|
||||
|
||||
// Parent
|
||||
GetNode("Child").Set("FuncProperty", GD.FuncRef(ObjectWithMethod, "MethodOnTheObject"));
|
||||
|
||||
// Child
|
||||
FuncProperty.CallFunc(); // Call parent-defined method (can come from anywhere)
|
||||
|
||||
4. Initialize a Node or other Object reference.
|
||||
|
||||
..tabs::
|
||||
..code-tab:: gdscript GDScript
|
||||
|
||||
# Parent
|
||||
$Child.target = self
|
||||
|
||||
# Child
|
||||
print(target) # Use parent-defined node
|
||||
|
||||
..code-tab:: csharp
|
||||
|
||||
// Parent
|
||||
GetNode("Child").Set("Target", this);
|
||||
|
||||
// Child
|
||||
GD.Print(Target); // Use parent-defined node
|
||||
|
||||
5. Initialize a NodePath.
|
||||
|
||||
..tabs::
|
||||
..code-tab:: gdscript GDScript
|
||||
|
||||
# Parent
|
||||
$Child.target_path = ".."
|
||||
|
||||
# Child
|
||||
get_node(target_path) # Use parent-defined NodePath
|
||||
|
||||
..code-tab:: csharp
|
||||
|
||||
// Parent
|
||||
GetNode("Child").Set("TargetPath", NodePath(".."));
|
||||
|
||||
// Child
|
||||
GetNode(TargetPath); // Use parent-defined NodePath
|
||||
|
||||
These options hide the source of accesses from the child node. This in turn
|
||||
keeps the child **loosely coupled** to its environment. One can re-use 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
|
||||
mediates their communications and references.
|
||||
|
||||
..tabs::
|
||||
..code-tab:: gdscript GDScript
|
||||
|
||||
# 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)
|
||||
|
||||
..code-tab:: csharp
|
||||
|
||||
// Parent
|
||||
GetNode("Left").Target = GetNode("Right/Receiver");
|
||||
|
||||
// Left
|
||||
public Node Target = null;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
// Do something with 'Target'
|
||||
}
|
||||
|
||||
// Right
|
||||
public Node Receiver = null;
|
||||
|
||||
public Right()
|
||||
{
|
||||
Receiver = ResourceLoader.load("Receiver.cs").new();
|
||||
AddChild(Receiver);
|
||||
}
|
||||
|
||||
The same principles also apply to non-Node objects that maintain dependencies
|
||||
on other objects. Whichever object actually owns the objects should manage
|
||||
the relationships between them.
|
||||
|
||||
.. warning::
|
||||
|
||||
One 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.
|
||||
|
||||
To avoid creating and maintaining such documentation, one converts the
|
||||
dependent node ("child" above) into a tool script that implements
|
||||
:ref:`_get_configuration_warning() <class_Node_method__get_configuration_warning>`.
|
||||
Returning a non-empty string from it will make the Scene dock generate a
|
||||
warning icon with the string as a tooltip by the node. This is the same icon
|
||||
that appears for nodes such as the
|
||||
:ref:`Area2D <class_Area2D>` node when it has no child
|
||||
:ref:`CollisionShape2D <class_CollisionShape2D>` nodes defined. The editor
|
||||
then self-documents the scene through the script code. No content duplication
|
||||
via documentation is necessary.
|
||||
|
||||
A GUI like this can better inform project users of critical information about
|
||||
a Node. Does it have external dependencies? Have those dependencies been
|
||||
satisfied? Other programmers, and especially designers and writers, will need
|
||||
clear instructions in the messages telling them what to do to configure it.
|
||||
|
||||
So, why do all this complex switcharoo 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).
|
||||
If the inevitable changes made to a class cause it to interact with other
|
||||
scenes in unforeseen ways, then things break down. A change to one class could
|
||||
result in damaging effects to other classes.
|
||||
|
||||
Scripts and scenes, as extensions of engine classes should abide
|
||||
by *all* OOP principles. Examples include...
|
||||
|
||||
- `SOLID <https://en.wikipedia.org/wiki/SOLID>`_
|
||||
|
||||
- `DRY <https://en.wikipedia.org/wiki/Don%27t_repeat_yourself>`_
|
||||
|
||||
- `KISS <https://en.wikipedia.org/wiki/KISS_principle>`_
|
||||
|
||||
- `YAGNI <https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it>`_
|
||||
|
||||
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 a myriad of ways.
|
||||
But, for those who are unsure, this helpful guide can give them 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 to 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.
|
||||
|
||||
- Node "Main" (main.gd)
|
||||
|
||||
The ``main.gd`` script would then serve as the primary controller of one's
|
||||
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
|
||||
the various menus and widgets the project needs.
|
||||
|
||||
- Node "Main" (main.gd)
|
||||
- Node2D/Spatial "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.
|
||||
|
||||
The next step is to consider what gameplay systems one's project requires.
|
||||
If one has 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>`.
|
||||
|
||||
.. note::
|
||||
|
||||
For smaller games, a simpler alternative with less control would be to have
|
||||
a "Game" singleton that simply calls the
|
||||
:ref:`SceneTree.change_scene() <class_SceneTree_method_change_scene>` method
|
||||
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 transitory parts 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.
|
||||
|
||||
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 vs. Internal Nodes' <doc_autoloads_versus_internal_nodes>`
|
||||
documentation.
|
||||
|
||||
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
|
||||
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
|
||||
own place in the hierachy 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
|
||||
:ref:`RemoteTransform <class_RemoteTransform>` /
|
||||
:ref:`RemoteTransform2D <class_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``
|
||||
: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
|
||||
will only ever be one of the targets).
|
||||
|
||||
When should one do this? Well, it's up to them to decide. The dilemma
|
||||
arises when one 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.
|
||||
Is memory a concern?
|
||||
- If not, one can just create the two rooms, move the player
|
||||
and delete the old one. No problem.
|
||||
- If so, one will need to...
|
||||
- Move the player somewhere else in the tree.
|
||||
- Delete the room.
|
||||
- Instantiate and add the new room.
|
||||
- Re-add the player.
|
||||
|
||||
The issue is that the player here is a "special case", one 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.
|
||||
|
||||
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 involves...
|
||||
|
||||
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:
|
||||
|
||||
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.
|
||||
2. The **imperative** solution: Use the ``set_as_toplevel`` setter for the
|
||||
:ref:`CanvasItem <class_CanvasItem_method_set_as_toplevel>` or
|
||||
:ref:`Spatial <class_Spatial_method_set_as_toplevel>` node. This will make
|
||||
the node ignore its inherited transform.
|
||||
|
||||
.. note::
|
||||
|
||||
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.
|
||||
|
||||
The key to scene organization is to consider the SceneTree in relational terms
|
||||
rather than spatial terms. Do the nodes need to be dependent on their parent's
|
||||
existence? If not, then they can thrive all by themselves somewhere else.
|
||||
If so, then it stands to reason they should be children of 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
|
||||
when such moves are unnecessary by default.
|
||||
@@ -0,0 +1,179 @@
|
||||
.. _doc_scenes_versus_scripts:
|
||||
|
||||
When to use scenes versus scripts
|
||||
=================================
|
||||
|
||||
We've already covered how scenes and scripts are different. Scripts
|
||||
define an engine class extension with imperative code, scenes with
|
||||
declarative code.
|
||||
|
||||
Each system's capabilities are different as a result.
|
||||
Scenes can define how an extended class initializes, but not what its
|
||||
behavior actually is. Scenes are often used in conjunction with a script so
|
||||
that the scene acts as an extension of the scripts declarative code.
|
||||
|
||||
Anonymous types
|
||||
---------------
|
||||
|
||||
It *is* possible to completely define a scenes' contents using a script alone.
|
||||
This is, in essence, what the Godot Editor does, only in the C++ constructor
|
||||
of its objects.
|
||||
|
||||
But, choosing which one to use can be a dilemma. Creating script instances
|
||||
is identical to creating in-engine classes whereas handling scenes requires
|
||||
a change in API:
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
const MyNode = preload("my_node.gd")
|
||||
const MyScene = preload("my_scene.tscn")
|
||||
var node = Node.new()
|
||||
var my_node = MyNode.new() # Same method call
|
||||
var my_scene = MyScene.instance() # Different method call
|
||||
var my_inherited_scene = MyScene.instance(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
using System;
|
||||
using Godot;
|
||||
|
||||
public class Game : Node
|
||||
{
|
||||
public const Script MyNodeScr = ResourceLoader.load("MyNode.cs") as Script;
|
||||
public const PackedScene MySceneScn= ResourceLoader.load("MyScene.tscn") as PackedScene;
|
||||
public Node ANode;
|
||||
public Node MyNode;
|
||||
public Node MyScene;
|
||||
public Node MyInheritedScene;
|
||||
|
||||
public Game()
|
||||
{
|
||||
ANode = new Node();
|
||||
MyNode = new MyNode(); // Same syntax
|
||||
MyScene = MySceneScn.instance(); // different syntax
|
||||
MyInheritedScene = MySceneScn.instance(PackedScene.GEN_EDIT_STATE_MAIN); // Create scene inheriting from MyScene
|
||||
}
|
||||
}
|
||||
|
||||
Also, scripts will operate a little slower than scenes due to the
|
||||
speed differences between engine and script code. The larger and more complex
|
||||
the node, the more reason there is to build it as a scene.
|
||||
|
||||
Named types
|
||||
-----------
|
||||
|
||||
In some cases, a user can register a script as a new type within the editor
|
||||
itself. This displays it as a new type in the node or resource creation dialog
|
||||
with an optional icon. In these cases, the user's ability to use the script
|
||||
is much more streamlined. Rather than having to...
|
||||
|
||||
1. Know the base type of the script they would like to use.
|
||||
|
||||
2. Create an instance of that base type.
|
||||
|
||||
3. Add the script to the node.
|
||||
|
||||
1. (Drag-n-drop method)
|
||||
|
||||
1. Find the script in the FileSystem dock.
|
||||
|
||||
2. Drag and drop the script onto the node in the Scene dock.
|
||||
|
||||
2. (Property method)
|
||||
|
||||
1. Scroll down to the bottom of the Inspector to find the ``script`` property and select it.
|
||||
|
||||
2. Select "Load" from the dropdown.
|
||||
|
||||
3. Select the script from the file dialog.
|
||||
|
||||
With a registered script, the scripted type instead becomes a creation option
|
||||
like the other nodes and resources in the system. One need not do any of the
|
||||
above work. The creation dialog even has a search bar to look up the type by
|
||||
name.
|
||||
|
||||
There are two systems for registering types...
|
||||
|
||||
- `Custom Types <doc_making_plugins>`
|
||||
|
||||
- Editor-only. Typenames are not accessible at runtime.
|
||||
|
||||
- Does not support inherited custom types.
|
||||
|
||||
- An initializer tool. Creates the node with the script. Nothing more.
|
||||
|
||||
- Editor has no type-awareness of the script or its relationship
|
||||
to other engine types or scripts.
|
||||
|
||||
- Allows users to define an icon.
|
||||
|
||||
- Works for all scripting languages because it deals with Script resources in abstract.
|
||||
|
||||
- Set up using :ref:`EditorPlugin.add_custom_type <class_EditorPlugin_method_add_custom_type>`.
|
||||
|
||||
- `Script Classes <https://godot.readthedocs.io/en/latest/getting_started/step_by_step/scripting_continued.html#register-scripts-as-classes>`_
|
||||
|
||||
- Editor and runtime accessible.
|
||||
|
||||
- Displays inheritance relationships in full.
|
||||
|
||||
- Creates the node with the script, but can also change types
|
||||
or extend the type from the editor.
|
||||
|
||||
- Editor is aware of inheritance relationships between scripts,
|
||||
script classes, and engine C++ classes.
|
||||
|
||||
- Allows users to define an icon.
|
||||
|
||||
- Engine developers must add support for languages manually (both name exposure and
|
||||
runtime accessibility).
|
||||
|
||||
- Godot 3.1+ only.
|
||||
|
||||
- The Editor scans project folders and registers any exposed names for all
|
||||
scripting languages. Each scripting language must implement its own
|
||||
support for exposing this information.
|
||||
|
||||
Both methodologies add names to the creation dialog, but script classes, in
|
||||
particular, also allow for users to access the typename without loading the
|
||||
script resource. Creating instances and accessing constants or static methods
|
||||
is viable from anywhere.
|
||||
|
||||
With features like these, one may wish their type to be a script without a
|
||||
scene due to the ease of use it grants users. Those developing plugins or
|
||||
creating in-house tools for designers to use will find an easier time of things
|
||||
this way.
|
||||
|
||||
On the downside, it also means having to use largely imperative programming.
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
In the end, the best approach is to consider the following:
|
||||
|
||||
- If one wishes to create a basic tool that is going to be re-used in several
|
||||
different projects and which people of all skill levels will likely use
|
||||
(including those who don't label themselves as "programmers"), then chances
|
||||
are that it should probably be a script, likely one with a custom name/icon.
|
||||
|
||||
- If one wishes to create a concept that is particular to their game, then it
|
||||
should always be a scene. Scenes are easier to track/edit and provide more
|
||||
security than scripts.
|
||||
|
||||
- If one would like to give a name to a scene, then they can still sort of do
|
||||
this in 3.1 by declaring a script class and giving it a scene as a constant.
|
||||
The script becomes, in effect, a namespace:
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
# game.gd
|
||||
extends Reference
|
||||
class_name Game # extends Reference, so it won't show up in the node creation dialog
|
||||
const MyScene = preload("my_scene.tscn")
|
||||
|
||||
# main.gd
|
||||
extends Node
|
||||
func _ready():
|
||||
add_child(Game.MyScene.instance())
|
||||
@@ -0,0 +1,137 @@
|
||||
.. _doc_what_are_godot_classes:
|
||||
|
||||
What are Godot classes really?
|
||||
==============================
|
||||
|
||||
Godot offers two main means of creating types: scripts and scenes.
|
||||
Both of these represent a "class" since Godot revolves around
|
||||
Object-Oriented design. *How* they do this may not be clear to beginner
|
||||
or intermediate users though.
|
||||
|
||||
Godot Engine provides classes out-of-the-box (like
|
||||
:ref:`Node <class_Node>`), but user-created types are not actually classes.
|
||||
Instead they are resources that tell the engine a sequence of initializations
|
||||
to perform on an engine class.
|
||||
|
||||
Godot's internal classes have methods that register a class's data with
|
||||
a :ref:`ClassDB <class_ClassDB>`. This database provides runtime access to
|
||||
class information (also called "reflection"). Things stored in the ClassDB
|
||||
include, among other things...
|
||||
|
||||
- properties
|
||||
|
||||
- methods
|
||||
|
||||
- constants
|
||||
|
||||
- signals
|
||||
|
||||
Furthermore, this ClassDB is what Objects actually check against when
|
||||
performing any operation. Access a property? Call a method? Emit a signal?
|
||||
It will check the database's records (and the records of the Object's base
|
||||
types) to see if the Object supports the operation. Every C++ Object defines
|
||||
a static `_bind_methods()` function that describes what C++ content it
|
||||
registers to the database and how.
|
||||
|
||||
So, if the engine provides all of this data at startup, then how does
|
||||
a user define their own data? It'd be nice if users could define a custom
|
||||
set of data to be appended to an object's data. That way, users could inject
|
||||
their own properties and methods into the engine's Object query requests.
|
||||
|
||||
*This* is what a :ref:`Script <class_Script>` is. Objects check their attached
|
||||
script before the database, so scripts can even override methods.
|
||||
If a script defines a `_get_property_list()` method, that data is appended to
|
||||
the list of properties the Object fetches from the ClassDB. The same holds
|
||||
true for other declarative code.
|
||||
|
||||
This can lead to some users' confusion when they see a script as being
|
||||
a class unto itself. In reality, the engine just auto-instantiates the
|
||||
base engine class and then adds the script to that object. This then allows
|
||||
the Object to defer to the Script's content where the engine logic deems
|
||||
appropriate.
|
||||
|
||||
A problem does present itself though. As the size of Objects increases,
|
||||
the scripts' necessary size to create them grows much, much larger.
|
||||
Creating node hierarchies demonstrates this. Each individual Node's logic
|
||||
could be several hundred lines of code in length.
|
||||
|
||||
let's see a simple example of creating a single Node as a child.
|
||||
|
||||
.. tabs::
|
||||
.. code-tab:: gdscript GDScript
|
||||
|
||||
# main.gd
|
||||
extends Node
|
||||
|
||||
var child # define a variable to store a reference to the child
|
||||
|
||||
func _init():
|
||||
child = Node.new() # Construct the child.
|
||||
child.name = "Child" # Change its name.
|
||||
child.script = preload("child.gd") # Give it custom features.
|
||||
child.owner = self # Serialize this node if self is saved.
|
||||
add_child(child) # Add "Child" as a child of self.
|
||||
|
||||
.. code-tab:: csharp
|
||||
|
||||
// Main.cs
|
||||
using System;
|
||||
using Godot;
|
||||
|
||||
namespace ExampleProject {
|
||||
public class Main : Resource
|
||||
{
|
||||
public Node Child { get; set; }
|
||||
|
||||
public Main()
|
||||
{
|
||||
Child = new Node(); // Construct the child.
|
||||
Child.Name = "Child"; // Change its name.
|
||||
Child.Script = ResourceLoader.load("child.gd") as Script; // Give it custom features.
|
||||
Child.Owner = this; // Serialize this node if this is saved.
|
||||
AddChild(Child); // Add "Child" as a child of this.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Notice that only two pieces of declarative code are involved in
|
||||
the creation of this child node: the variable declaration and
|
||||
the constructor declaration. Everything else about the child
|
||||
must be setup using imperative code. However, script code is
|
||||
much slower than engine C++ code. Each change must make a separate
|
||||
call to the scripting API which means a lot of C++ "lookups" within
|
||||
data structures to find the corresponding logic to execute.
|
||||
|
||||
To help offload the work, it would be convenient if one could batch up
|
||||
all operations involved in creating and setting up node hierarchies. The
|
||||
engine could then handle the construction using its fast C++ code, and the
|
||||
script code would be free from the perils of imperative code.
|
||||
|
||||
*This* is what a scene (:ref:`PackedScene <class_PackedScene>`) is: a
|
||||
resource that provides an advanced "constructor" serialization which is
|
||||
offloaded to the engine for batch processing.
|
||||
|
||||
Now, why is any of this important to scene organization? Because one must
|
||||
understand that scenes *are* objects. One often pairs a scene with
|
||||
a scripted root node that makes use of the sub-nodes. This means that the
|
||||
scene is often an extension of the script's declarative code.
|
||||
|
||||
It helps to define...
|
||||
|
||||
- what objects are available to the script?
|
||||
|
||||
- how are they organized?
|
||||
|
||||
- how are they initialized?
|
||||
|
||||
- what connections to each other do they have, if any?
|
||||
|
||||
As such, many Object-Oriented principles which apply to "programming", i.e.
|
||||
scripts, *also* apply to scenes. Some scripts are designed to only work
|
||||
in one scene (which are often bundled into the scene itself). Other scripts
|
||||
are meant to be re-used between scenes.
|
||||
|
||||
**Regardless, the scene is always an extension of the root script, and can
|
||||
therefore be interpreted as a part of the class.**
|
||||
Most of the points covered in this series will build on this point, so
|
||||
keep it in mind.
|
||||
@@ -8,3 +8,4 @@ Project workflow
|
||||
project_setup/index
|
||||
assets/index
|
||||
export/index
|
||||
best_practices/index
|
||||
Reference in New Issue
Block a user