Merge pull request #2183 from YeldhamDev/best_practices_fixup

Numerous fixes to the new "Best practices" section
This commit is contained in:
Rémi Verschelde
2019-02-10 12:11:15 +01:00
committed by GitHub
4 changed files with 138 additions and 134 deletions

View File

@@ -15,7 +15,7 @@ The rest of this tutorial outlines the various ways of doing all this.
Acquiring object references
---------------------------
For all :ref:`Object <class_Object>`s, the most basic way of referencing them
For all :ref:`Object <class_Object>`\s, the most basic way of referencing them
is to get a reference to an existing object from another acquired instance.
.. tabs::
@@ -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 <class_Resource_method_duplicate>` an existing reference or
:ref:`duplicate <class_Resource_method_duplicate>` an existing reference or
instantiate one from scratch with ``new()``.
Nodes likewise have an alternative access point: the SceneTree.
@@ -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 <class_CanvasItem_property_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");

View File

@@ -38,7 +38,7 @@ than Node alone:
- :ref:`Object::NOTIFICATION_PREDELETE <class_Object_constant_NOTIFICATION_PREDELETE>`:
a callback that triggers before the engine deletes an Object, i.e. a
'destructor'.
- :ref:`MainLoop::NOTIFICATION_WM_MOUSE_ENTER <class_MainLoop_constant_NOTIFICATION_WM_MOUSE_ENTER>`:
a callback that triggers when the mouse enters the window in the operating
system that displays the game content.
@@ -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 <class_Object_method__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!");
}

View File

@@ -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_Node>` class.
- Example: see the :ref:`Tree <class_Tree>` node. It supports a high level
- **Example:** See the :ref:`Tree <class_Tree>` node. It supports a high level
of customization for a table of content with an arbitrary number of
rows and columns. The data that it uses to generate its visualization
though is actually a tree of :ref:`TreeItem <class_TreeItem>` Objects.
- Advantages: Simplifying one's API to smaller scoped objects helps improve
- **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 <class_File>` object. It functions
- **Example:** See the :ref:`File <class_File>` object. It functions
just like a regular Object except that one need not delete it themselves.
- Advantages: same as the Object.
- **Advantages:** same as the Object.
3. :ref:`Resource <class_Resource>`: Only slightly more complex than Reference.
They have the innate ability to serialize/deserialize (i.e. save and load)
their object properties to/from Godot resource files.
- Example: Scripts, PackedScene (for scene files), and other types like
- **Example:** Scripts, PackedScene (for scene files), and other types like
each of the :ref:`AudioEffect <class_AudioEffect>` classes. Each of these
can be save and loaded, therefore they extend from Resource.
- Advantages: Much has
:ref:`already been said <http://docs.godotengine.org/en/latest/getting_started/step_by_step/resources.html#creating-your-own-resources>`
- **Advantages:** Much has
:ref:`already been said <doc_resources>`
on :ref:`Resource <class_Resource>`'s advantages over traditional data
storage methods. In the context of using Resources over Nodes though,
their main advantage is in Inspector-compatibility. While nearly as

View File

@@ -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 <https://en.wikipedia.org/wiki/Dependency_injection>`_.
This technique involves having a high-level API provide the dependencies of the
low-level API. Why do this? Because classes which rely on their external
@@ -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 <class_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() <class_Node_method__get_configuration_warning>`.
@@ -222,7 +222,7 @@ in another context without any extra changes to its API.
:ref:`CollisionShape2D <class_CollisionShape2D>` nodes defined. The editor
then self-documents the scene through the script code. No content duplication
via documentation is necessary.
A GUI like this can better inform project users of critical information about
a Node. Does it have external dependencies? Have those dependencies been
satisfied? Other programmers, and especially designers and writers, will need
@@ -290,13 +290,13 @@ If one has a system that...
:ref:`autoload 'singleton' node <doc_singletons_autoload>`.
.. note::
For smaller games, a simpler alternative with less control would be to have
a "Game" singleton that simply calls the
:ref:`SceneTree.change_scene() <class_SceneTree_method_change_scene>` method
to swap out the main scene's content. This structure more or less keeps
the "World" as the main game node.
Any GUI would need to also be a
singleton, be transitory parts of the "World", or be manually added as a
direct child of the root. Otherwise, the GUI nodes would also delete
@@ -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 <class_RemoteTransform>` /
:ref:`RemoteTransform <class_RemoteTransform>` /
:ref:`RemoteTransform2D <class_RemoteTransform2D>` nodes for this purpose.
They will allow a target node to conditionally inherit selected transform
elements from the Remote\* node. To assign the ``target``
@@ -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 <class_Node>` in between
them. As nodes with no transform, Nodes will not pass along such
information to their children.