From e8289b93f334c533890ae1e19cd345473ba2fac3 Mon Sep 17 00:00:00 2001 From: Will Nations Date: Fri, 7 Dec 2018 13:55:00 -0600 Subject: [PATCH] Add "best practices" documentation. --- .../autoloads_versus_internal_nodes.rst | 103 ++++ .../best_practices/data_preferences.rst | 354 ++++++++++++ .../best_practices/godot_interfaces.rst | 503 ++++++++++++++++++ .../best_practices/godot_notifications.rst | 288 ++++++++++ .../workflow/best_practices/index.rst | 17 + .../introduction_best_practices.rst | 24 + .../best_practices/logic_preferences.rst | 162 ++++++ .../best_practices/node_alternatives.rst | 62 +++ .../best_practices/scene_organization.rst | 391 ++++++++++++++ .../best_practices/scenes_versus_scripts.rst | 179 +++++++ .../best_practices/what_are_godot_classes.rst | 137 +++++ getting_started/workflow/index.rst | 1 + 12 files changed, 2221 insertions(+) create mode 100644 getting_started/workflow/best_practices/autoloads_versus_internal_nodes.rst create mode 100644 getting_started/workflow/best_practices/data_preferences.rst create mode 100644 getting_started/workflow/best_practices/godot_interfaces.rst create mode 100644 getting_started/workflow/best_practices/godot_notifications.rst create mode 100644 getting_started/workflow/best_practices/index.rst create mode 100644 getting_started/workflow/best_practices/introduction_best_practices.rst create mode 100644 getting_started/workflow/best_practices/logic_preferences.rst create mode 100644 getting_started/workflow/best_practices/node_alternatives.rst create mode 100644 getting_started/workflow/best_practices/scene_organization.rst create mode 100644 getting_started/workflow/best_practices/scenes_versus_scripts.rst create mode 100644 getting_started/workflow/best_practices/what_are_godot_classes.rst diff --git a/getting_started/workflow/best_practices/autoloads_versus_internal_nodes.rst b/getting_started/workflow/best_practices/autoloads_versus_internal_nodes.rst new file mode 100644 index 000000000..55aee9c31 --- /dev/null +++ b/getting_started/workflow/best_practices/autoloads_versus_internal_nodes.rst @@ -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 `. 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 ` 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 ` 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. diff --git a/getting_started/workflow/best_practices/data_preferences.rst b/getting_started/workflow/best_practices/data_preferences.rst new file mode 100644 index 000000000..6dddc1360 --- /dev/null +++ b/getting_started/workflow/best_practices/data_preferences.rst @@ -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 `_. + + 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 `_ +class. Variants can store Variant-compatible data structures such as +:ref:`Array ` and :ref:`Dictionary ` as well as +:ref:`Object ` s. + +Godot implements Array as a Vector. 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 ` would be something like + a Vector. + +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. 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 ` or the :ref:`ClassDB `. +One can find more information about what objects are and how they work in +the :ref:`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 ` 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 ` 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 ` unlike +the other :ref:`Node ` objects discussed here. One might create +a :ref:`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 ` and integrate it with a +:ref:`TileMap ` for many auto-animating backgrounds that +all render in a single batched draw call. + +The AnimatedSprite node, in combination with the +:ref:`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 ` 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 ` +shines. One can find an in-depth guide on using the AnimationTree +:ref:`here `. \ No newline at end of file diff --git a/getting_started/workflow/best_practices/godot_interfaces.rst b/getting_started/workflow/best_practices/godot_interfaces.rst new file mode 100644 index 000000000..eadf03d42 --- /dev/null +++ b/getting_started/workflow/best_practices/godot_interfaces.rst @@ -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 `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 ` objects. +While users often access :ref:`Node ` and +:ref:`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 ` 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 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 `, 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 ` 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 `'s "1/tile_name" property. This + refers to the name of the tile with ID 1, i.e. + :ref:`TileSet.tile_get_name(1) `. + +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 `, 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 `. 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. \ No newline at end of file diff --git a/getting_started/workflow/best_practices/godot_notifications.rst b/getting_started/workflow/best_practices/godot_notifications.rst new file mode 100644 index 000000000..b330e456c --- /dev/null +++ b/getting_started/workflow/best_practices/godot_notifications.rst @@ -0,0 +1,288 @@ +.. _doc_godot_notifications: + +Godot notifications +=================== + +Every Object in Godot implements a +:ref:`_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 ` 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 `: + a callback that triggers during object initialization. Not accessible to scripts. + +- :ref:`Object::NOTIFICATION_PREDELETE `: + a callback that triggers before the engine deletes an Object, i.e. a + 'destructor'. + +- :ref:`MainLoop::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 `: + a callback that triggers anytime one adds a child node to another node. + +- :ref:`Node::NOTIFICATION_UNPARENTED `: + a callback that triggers anytime one removes a child node from another + node. + +- :ref:`Popup::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 ` 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 ` 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 ` 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!"); + } + } \ No newline at end of file diff --git a/getting_started/workflow/best_practices/index.rst b/getting_started/workflow/best_practices/index.rst new file mode 100644 index 000000000..cc061c8ce --- /dev/null +++ b/getting_started/workflow/best_practices/index.rst @@ -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 \ No newline at end of file diff --git a/getting_started/workflow/best_practices/introduction_best_practices.rst b/getting_started/workflow/best_practices/introduction_best_practices.rst new file mode 100644 index 000000000..cd4bc839d --- /dev/null +++ b/getting_started/workflow/best_practices/introduction_best_practices.rst @@ -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. \ No newline at end of file diff --git a/getting_started/workflow/best_practices/logic_preferences.rst b/getting_started/workflow/best_practices/logic_preferences.rst new file mode 100644 index 000000000..4f7ad56ec --- /dev/null +++ b/getting_started/workflow/best_practices/logic_preferences.rst @@ -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 ` 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 ` 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) ` 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 `-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" ` +documentation. \ No newline at end of file diff --git a/getting_started/workflow/best_practices/node_alternatives.rst b/getting_started/workflow/best_practices/node_alternatives.rst new file mode 100644 index 000000000..aa55b77bd --- /dev/null +++ b/getting_started/workflow/best_practices/node_alternatives.rst @@ -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 `: 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. + + - Example: see the :ref:`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 ` 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 `: 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 ` object. It functions + just like a regular Object except that one need not delete it themselves. + + - Advantages: same as the Object. + +3. :ref:`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 ` classes. Each of these + can be save and loaded, therefore they extend from Resource. + + - Advantages: Much has + :ref:`already been said ` + on :ref:`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. diff --git a/getting_started/workflow/best_practices/scene_organization.rst b/getting_started/workflow/best_practices/scene_organization.rst new file mode 100644 index 000000000..463714bed --- /dev/null +++ b/getting_started/workflow/best_practices/scene_organization.rst @@ -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 `_ +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 `_. +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 ` 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() `. + 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 ` node when it has no child + :ref:`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 `_ + +- `DRY `_ + +- `KISS `_ + +- `YAGNI `_ + +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 ` 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 `. + +.. 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() ` 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' ` +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 ` / + :ref:`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 `, 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 ` 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 ` or + :ref:`Spatial ` 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. diff --git a/getting_started/workflow/best_practices/scenes_versus_scripts.rst b/getting_started/workflow/best_practices/scenes_versus_scripts.rst new file mode 100644 index 000000000..0e2c21175 --- /dev/null +++ b/getting_started/workflow/best_practices/scenes_versus_scripts.rst @@ -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 ` + + - 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 `. + +- `Script 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()) diff --git a/getting_started/workflow/best_practices/what_are_godot_classes.rst b/getting_started/workflow/best_practices/what_are_godot_classes.rst new file mode 100644 index 000000000..1b07a767b --- /dev/null +++ b/getting_started/workflow/best_practices/what_are_godot_classes.rst @@ -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 `), 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 `. 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 ` 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 `) 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. diff --git a/getting_started/workflow/index.rst b/getting_started/workflow/index.rst index d6414d0c5..a93ca1fe8 100644 --- a/getting_started/workflow/index.rst +++ b/getting_started/workflow/index.rst @@ -8,3 +8,4 @@ Project workflow project_setup/index assets/index export/index + best_practices/index \ No newline at end of file