Merge pull request #2175 from willnationsdev/scene-organization

Add a "best practices" section
This commit is contained in:
Nathan Lovato
2019-02-09 10:15:58 +09:00
committed by GitHub
12 changed files with 2221 additions and 0 deletions
@@ -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.
+1
View File
@@ -8,3 +8,4 @@ Project workflow
project_setup/index
assets/index
export/index
best_practices/index