From d99cd76c62d0b85584782ecfa122ffb2c2abcddd Mon Sep 17 00:00:00 2001 From: Michael Alexsander Silva Dias Date: Sat, 9 Feb 2019 22:02:49 -0200 Subject: [PATCH] Numerous fixes to the new "Best practices" section --- .../best_practices/godot_interfaces.rst | 94 +++++++------- .../best_practices/godot_notifications.rst | 42 +++--- .../best_practices/node_alternatives.rst | 16 +-- .../best_practices/scene_organization.rst | 120 +++++++++--------- 4 files changed, 138 insertions(+), 134 deletions(-) diff --git a/getting_started/workflow/best_practices/godot_interfaces.rst b/getting_started/workflow/best_practices/godot_interfaces.rst index eadf03d42..2f9093ef3 100644 --- a/getting_started/workflow/best_practices/godot_interfaces.rst +++ b/getting_started/workflow/best_practices/godot_interfaces.rst @@ -15,7 +15,7 @@ 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 +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:: @@ -47,20 +47,20 @@ access. 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 + # 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 + # 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 + tool # Must place at top of file. - # Must configure from the editor, defaults to null + # 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 + # Warn users if the value hasn't been set. func _get_configuration_warning(): if not const_script: return "Must initialize property 'const_script'." @@ -68,7 +68,7 @@ access. .. code-tab:: csharp - // Tool script added for the sake of the "const [Export]" example + // Tool script added for the sake of the "const [Export]" example. [Tool] public MyType : extends Object { @@ -85,14 +85,14 @@ access. // 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 + // 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()) @@ -102,7 +102,7 @@ access. } }; - // Warn users if the value hasn't been set + // Warn users if the value hasn't been set. public String _GetConfigurationWarning() { if (EnemyScn == null) @@ -120,7 +120,7 @@ Note the following: 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 + :ref:`duplicate ` an existing reference or instantiate one from scratch with ``new()``. Nodes likewise have an alternative access point: the SceneTree. @@ -130,11 +130,11 @@ Nodes likewise have an alternative access point: the SceneTree. extends Node - # Slow + # Slow. func dynamic_lookup_with_dynamic_nodepath(): print(get_node("Child")) - # Faster. GDScript only + # Faster. GDScript only. func dynamic_lookup_with_cached_nodepath(): print($Child) @@ -147,27 +147,27 @@ Nodes likewise have an alternative access point: the SceneTree. 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 + # Validate prop in one of three ways. - # fail with no notification + # Fail with no notification. if not prop: return - - # fail with an error message + + # Fail with an error message. if not prop: printerr("'prop' wasn't initialized") return - - # fail and terminate + + # Fail and terminate. # Compiled scripts in final binary do not include assert statements - assert prop + assert prop. # Use an autoload. # Dangerous for typical nodes, but useful for true singleton nodes @@ -212,14 +212,14 @@ Nodes likewise have an alternative access point: the SceneTree. { 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"); } @@ -285,7 +285,7 @@ accesses: .. tabs:: .. code-tab:: gdscript GDScript - + # All Objects have duck-typed get, set, and call wrapper methods get_parent().set("visible", false) @@ -302,13 +302,13 @@ accesses: .. code-tab:: csharp - // All Objects have duck-typed Get, Set, and Call wrapper methods + // 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 + // `GetParent().Visible = false` won't work. -- A method check. In the case of +- A method check. In the case of :ref:`CanvasItem.visible `, one can access the methods, ``set_visible`` and ``is_visible`` like any other method. @@ -316,18 +316,18 @@ accesses: .. code-tab:: gdscript GDScript var child = GetChild(0) - - # Dynamic lookup + + # Dynamic lookup. child.call("set_visible", false) - # Symbol-based dynamic lookup + # 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 + # 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. @@ -336,7 +336,7 @@ accesses: 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. @@ -363,7 +363,7 @@ accesses: 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. @@ -373,7 +373,7 @@ accesses: Node child = GetChild(0); // Dynamic lookup - child.Call("SetVisible", false); + child.Call("SetVisible", false); // Dynamic lookup, checks for method existence first if (child.HasMethod("SetVisible")) @@ -420,8 +420,8 @@ accesses: // 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 + quest.Call("Complete"); // or "Fail". + GD.Print(quest.Get("Text")); // Implied new text content. // 2. Use a group foreach (Node AChild in GetChildren()) @@ -429,11 +429,11 @@ accesses: if (AChild.IsInGroup("quest")) { GD.Print(quest.Get("Text")); - quest.Call("Complete"); // or "Fail" - GD.Print(quest.Get("Text")); // implied new text content + 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 @@ -444,9 +444,9 @@ accesses: 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 - +.. tabs:: + .. code-tab:: gdscript GDScript + # child.gd extends Node var fn = null @@ -454,7 +454,7 @@ accesses: func my_method(): if fn: fn.call_func() - + # parent.gd extends Node @@ -467,8 +467,8 @@ accesses: func print_me(): print(name) - ..code-tab:: csharp - + .. code-tab:: csharp + // Child.cs public class Child extends Node { @@ -485,7 +485,7 @@ accesses: public class Parent extends Node { public Node Child; - + public void _Ready() { Child = GetNode("Child"); diff --git a/getting_started/workflow/best_practices/godot_notifications.rst b/getting_started/workflow/best_practices/godot_notifications.rst index b330e456c..bd5d36207 100644 --- a/getting_started/workflow/best_practices/godot_notifications.rst +++ b/getting_started/workflow/best_practices/godot_notifications.rst @@ -38,7 +38,7 @@ than Node alone: - :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. @@ -59,12 +59,12 @@ methods, but are still quite useful. *before* its appearance. One can access all these custom notifications from the universal -``_notification`` method. +``_notification`` method. -..note:: +.. 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 @@ -73,8 +73,8 @@ One can access all these custom notifications from the universal So, in which situation should one use each of these notifications or virtual functions? -_process vs. _physics_process vs. *_input ------------------------------------------ +_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 @@ -84,8 +84,8 @@ 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:: - + .. code-tab:: gdscript GDScript + # 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). @@ -97,7 +97,7 @@ implementing a Timer-yield-timeout loop is another option. 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. +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 @@ -112,12 +112,12 @@ If one wants to use delta time, one can fetch it from the .. tabs:: .. code-tab:: gdscript GDScript - # Called every frame, even when the engine detects no input + # 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 + # Called during every input event. func _unhandled_input(event): match event.get_class(): "InputEventKey": @@ -128,7 +128,7 @@ If one wants to use delta time, one can fetch it from the public class MyNode : public Node { - // Called every frame, even when the engine detects no input + // Called every frame, even when the engine detects no input. public void _Process(float delta) { if (GD.Input.ActionJustPressed("UiSelect")) { GD.Print(string(delta)); @@ -161,7 +161,7 @@ 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. @@ -173,7 +173,7 @@ instantiation: test = "three" # These DO trigger the setter. Note the `self` prefix. self.test = "three" - + func set_test(value): test = value print("Setting: ", test) @@ -213,7 +213,7 @@ following sequence: 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 ------------------------------------------------ @@ -235,8 +235,8 @@ 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 +.. tabs:: + .. code-tab:: gdscript GDScript extends Node @@ -244,7 +244,7 @@ nodes that one might create at runtime. func connection_check(): return parent.has_user_signal("interacted_with") - + func _notification(what): match what: NOTIFICATION_PARENTED: @@ -254,11 +254,11 @@ nodes that one might create at runtime. 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 + .. code-tab:: csharp public class MyNode extends Node { @@ -281,7 +281,7 @@ nodes that one might create at runtime. break; } } - + public void OnParentInteractedWith() { GD.Print("I'm reacting to my parent's interaction!"); } diff --git a/getting_started/workflow/best_practices/node_alternatives.rst b/getting_started/workflow/best_practices/node_alternatives.rst index aa55b77bd..5b5176a36 100644 --- a/getting_started/workflow/best_practices/node_alternatives.rst +++ b/getting_started/workflow/best_practices/node_alternatives.rst @@ -18,17 +18,17 @@ your project's features. 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 + - **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 + - **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 + .. 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. @@ -38,21 +38,21 @@ your project's features. 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 + - **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. + - **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 + - **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 ` + - **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 diff --git a/getting_started/workflow/best_practices/scene_organization.rst b/getting_started/workflow/best_practices/scene_organization.rst index 463714bed..f9a7b939a 100644 --- a/getting_started/workflow/best_practices/scene_organization.rst +++ b/getting_started/workflow/best_practices/scene_organization.rst @@ -41,7 +41,7 @@ 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 +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 @@ -54,99 +54,99 @@ initialize it: 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 - + .. 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 + emit_signal("signal_name") # Triggers parent-defined behavior. - ..code-tab:: csharp + .. code-tab:: csharp // Parent GetNode("Child").Connect("SignalName", ObjectWithMethod, "MethodOnTheObject"); // Child - EmitSignal("SignalName"); // Triggers parent-defined behavior + EmitSignal("SignalName"); // Triggers parent-defined behavior. 2. Call a method. Used to start behavior. - - ..tabs:: - ..code-tab:: gdscript GDScript - + + .. 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) + # 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 + .. code-tab:: csharp // Parent GetNode("Child").Set("MethodName", "Do"); // Child - Call(MethodName); // Call parent-defined method (which child must own) + 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 - + + .. 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) + func_property.call_func() # Call parent-defined method (can come from anywhere). - ..code-tab:: csharp + .. code-tab:: csharp // Parent GetNode("Child").Set("FuncProperty", GD.FuncRef(ObjectWithMethod, "MethodOnTheObject")); // Child - FuncProperty.CallFunc(); // Call parent-defined method (can come from anywhere) + FuncProperty.CallFunc(); // Call parent-defined method (can come from anywhere). 4. Initialize a Node or other Object reference. - - ..tabs:: - ..code-tab:: gdscript GDScript - + + .. tabs:: + .. code-tab:: gdscript GDScript + # Parent $Child.target = self # Child - print(target) # Use parent-defined node + print(target) # Use parent-defined node. - ..code-tab:: csharp + .. code-tab:: csharp // Parent GetNode("Child").Set("Target", this); // Child - GD.Print(Target); // Use parent-defined node + GD.Print(Target); // Use parent-defined node. 5. Initialize a NodePath. - - ..tabs:: - ..code-tab:: gdscript GDScript - + + .. tabs:: + .. code-tab:: gdscript GDScript + # Parent $Child.target_path = ".." # Child - get_node(target_path) # Use parent-defined NodePath + get_node(target_path) # Use parent-defined NodePath. - ..code-tab:: csharp + .. code-tab:: csharp // Parent GetNode("Child").Set("TargetPath", NodePath("..")); // Child - GetNode(TargetPath); // Use parent-defined NodePath + 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 @@ -159,23 +159,23 @@ in another context without any extra changes to its API. are siblings should only be aware of their hierarchies while an ancestor mediates their communications and references. - ..tabs:: - ..code-tab:: gdscript GDScript - + .. tabs:: + .. code-tab:: gdscript GDScript + # Parent $Left.target = $Right.get_node("Receiver") # Left var target: Node func execute(): - # Do something with 'target' + # Do something with 'target'. # Right func _init(): var receiver = Receiver.new() add_child(receiver) - - ..code-tab:: csharp + + .. code-tab:: csharp // Parent GetNode("Left").Target = GetNode("Right/Receiver"); @@ -185,7 +185,7 @@ in another context without any extra changes to its API. public void Execute() { - // Do something with 'Target' + // Do something with 'Target'. } // Right @@ -196,7 +196,7 @@ in another context without any extra changes to its API. 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. @@ -211,7 +211,7 @@ in another context without any extra changes to its API. 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() `. @@ -222,7 +222,7 @@ in another context without any extra changes to its API. :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 @@ -290,13 +290,13 @@ If one has a system that... :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 @@ -318,7 +318,7 @@ own place in the hierachy as a sibling or some other relation. In some cases, one needs these separated nodes to *also* position themselves relative to each other. One can use the - :ref:`RemoteTransform ` / + :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`` @@ -335,15 +335,19 @@ own place in the hierachy as a sibling or some other relation. - 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. - + + 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 @@ -353,15 +357,15 @@ own place in the hierachy as a sibling or some other relation. 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.