Browse Source

Merge pull request #9837 from dementive/best-practices-cpp

Add GDExtension C++ snippets to tutorials/best_practices
Max Hilbrunner 8 months ago
parent
commit
d0d797ccbe

+ 111 - 0
tutorials/best_practices/godot_notifications.rst

@@ -102,6 +102,29 @@ implementing a Timer-timeout loop is another option.
         }
         }
     }
     }
 
 
+ .. code-tab:: cpp C++
+
+    using namespace godot;
+
+    class MyNode : public Node {
+        GDCLASS(MyNode, Node)
+
+    public:
+        // Allows for recurring operations that don't trigger script logic
+        // every frame (or even every fixed frame).
+        virtual void _ready() override {
+            Timer *timer = memnew(Timer);
+            timer->set_autostart(true);
+            timer->set_wait_time(0.5);
+            add_child(timer);
+            timer->connect("timeout", callable_mp(this, &MyNode::run));
+        }
+
+        void run() {
+            UtilityFunctions::print("This block runs every 0.5 seconds.");
+        }
+    };
+
 Use ``_physics_process()`` when one needs a framerate-independent delta time
 Use ``_physics_process()`` when one needs a framerate-independent delta time
 between frames. If code needs consistent updates over time, regardless
 between frames. If code needs consistent updates over time, regardless
 of how fast or slow time advances, this is the right place.
 of how fast or slow time advances, this is the right place.
@@ -160,6 +183,30 @@ delta time methods as needed.
 
 
     }
     }
 
 
+  .. code-tab:: cpp C++
+
+    using namespace godot;
+
+    class MyNode : public Node {
+        GDCLASS(MyNode, Node)
+
+    public:
+        // Called every frame, even when the engine detects no input.
+        virtual void _process(double p_delta) override {
+            if (Input::get_singleton->is_action_just_pressed("ui_select")) {
+                UtilityFunctions::print(p_delta);
+            }
+        }
+
+        // Called during every input event. Equally true for _input().
+        virtual void _unhandled_input(const Ref<InputEvent> &p_event) override {
+            Ref<InputEventKey> key_event = event;
+            if (key_event.is_valid() && Input::get_singleton->is_action_just_pressed("ui_accept")) {
+                UtilityFunctions::print(get_process_delta_time());
+            }
+        }
+    };
+
 _init vs. initialization vs. export
 _init vs. initialization vs. export
 -----------------------------------
 -----------------------------------
 
 
@@ -223,6 +270,35 @@ values will set up according to the following sequence:
         // the setter, changing _test's value from "two!" to "three!".
         // the setter, changing _test's value from "two!" to "three!".
     }
     }
 
 
+  .. code-tab:: cpp C++
+
+    using namespace godot;
+
+    class MyNode : public Node {
+        GDCLASS(MyNode, Node)
+
+        String test = "one";
+
+    protected:
+        static void _bind_methods() {
+            ClassDB::bind_method(D_METHOD("get_test"), &MyNode::get_test);
+            ClassDB::bind_method(D_METHOD("set_test", "test"), &MyNode::set_test);
+            ADD_PROPERTY(PropertyInfo(Variant::STRING, "test"), "set_test", "get_test");
+        }
+
+    public:
+        String get_test() { return test; }
+        void set_test(String p_test) { return test = p_test; }
+
+        MyNode() {
+            // Triggers the setter, changing _test's value from "one" to "two!".
+            set_test("two");
+        }
+
+        // If someone sets test to "three" in the Inspector, it would trigger
+        // the setter, changing test's value from "two!" to "three!".
+    };
+
 As a result, instantiating a script versus a scene may affect both the
 As a result, instantiating a script versus a scene may affect both the
 initialization *and* the number of times the engine calls the setter.
 initialization *and* the number of times the engine calls the setter.
 
 
@@ -309,3 +385,38 @@ nodes that one might create at runtime.
             GD.Print("I'm reacting to my parent's interaction!");
             GD.Print("I'm reacting to my parent's interaction!");
         }
         }
     }
     }
+
+  .. code-tab:: cpp C++
+
+    using namespace godot;
+
+    class MyNode : public Node {
+        GDCLASS(MyNode, Node)
+
+        Node *parent_cache = nullptr;
+
+        void on_parent_interacted_with() {
+            UtilityFunctions::print("I'm reacting to my parent's interaction!");
+        }
+
+    public:
+        void connection_check() {
+            return parent_cache->has_user_signal("interacted_with");
+        }
+
+        void _notification(int p_what) {
+            switch (p_what) {
+                case NOTIFICATION_PARENTED:
+                    parent_cache = get_parent();
+                    if (connection_check()) {
+                        parent_cache->connect("interacted_with", callable_mp(this, &MyNode::on_parent_interacted_with));
+                    }
+                    break;
+                case NOTIFICATION_UNPARENTED:
+                    if (connection_check()) {
+                        parent_cache->disconnect("interacted_with", callable_mp(this, &MyNode::on_parent_interacted_with));
+                    }
+                    break;
+            }
+        }
+    };

+ 17 - 0
tutorials/best_practices/logic_preferences.rst

@@ -100,6 +100,23 @@ either? Let's see an example:
         }
         }
     }
     }
 
 
+  .. code-tab:: cpp C++
+
+    using namespace godot;
+
+    class MyBuildings : public Node {
+        GDCLASS(MyBuildings, Node)
+
+    public:
+        const Ref<PackedScene> building = ResourceLoader::get_singleton()->load("res://building.tscn");
+        Ref<PackedScene> a_building;
+
+        virtual void _ready() override {
+            // Can assign the value during initialization.
+            a_building = ResourceLoader::get_singleton()->load("res://office.tscn");
+        }
+    };
+
 Preloading allows the script to handle all the loading the moment one loads the
 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
 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
 for it. To distinguish these situations, there are a few things one can

+ 93 - 0
tutorials/best_practices/scene_organization.rst

@@ -69,6 +69,19 @@ initialize it:
        // Child
        // Child
        EmitSignal("SignalName"); // Triggers parent-defined behavior.
        EmitSignal("SignalName"); // Triggers parent-defined behavior.
 
 
+     .. code-tab:: cpp C++
+
+       // Parent
+       Node *node = get_node<Node>("Child");
+       if (node != nullptr) {
+           // Note that get_node may return a nullptr, which would make calling the connect method crash the engine if "Child" does not exist!
+           // So unless you are 1000% sure get_node will never return a nullptr, it's a good idea to always do a nullptr check.
+           node->connect("signal_name", callable_mp(this, &ObjectWithMethod::method_on_the_object));
+       }
+
+       // Child
+       emit_signal("signal_name"); // Triggers parent-defined behavior.
+
 2. Call a method. Used to start behavior.
 2. Call a method. Used to start behavior.
 
 
    .. tabs::
    .. tabs::
@@ -88,6 +101,17 @@ initialize it:
        // Child
        // Child
        Call(MethodName); // Call parent-defined method (which child must own).
        Call(MethodName); // Call parent-defined method (which child must own).
 
 
+     .. code-tab:: cpp C++
+
+       // Parent
+       Node *node = get_node<Node>("Child");
+       if (node != nullptr) {
+           node->set("method_name", "do");
+       }
+
+       // Child
+       call(method_name); // Call parent-defined method (which child must own).
+
 3. Initialize a :ref:`Callable <class_Callable>` property. Safer than a method
 3. Initialize a :ref:`Callable <class_Callable>` property. Safer than a method
    as ownership of the method is unnecessary. Used to start behavior.
    as ownership of the method is unnecessary. Used to start behavior.
 
 
@@ -108,6 +132,17 @@ initialize it:
        // Child
        // Child
        FuncProperty.Call(); // Call parent-defined method (can come from anywhere).
        FuncProperty.Call(); // Call parent-defined method (can come from anywhere).
 
 
+     .. code-tab:: cpp C++
+
+       // Parent
+       Node *node = get_node<Node>("Child");
+       if (node != nullptr) {
+           node->set("func_property", Callable(&ObjectWithMethod::method_on_the_object));
+       }
+
+       // Child
+       func_property.call(); // Call parent-defined method (can come from anywhere).
+
 4. Initialize a Node or other Object reference.
 4. Initialize a Node or other Object reference.
 
 
    .. tabs::
    .. tabs::
@@ -127,6 +162,17 @@ initialize it:
        // Child
        // Child
        GD.Print(Target); // Use parent-defined node.
        GD.Print(Target); // Use parent-defined node.
 
 
+     .. code-tab:: cpp C++
+
+       // Parent
+       Node *node = get_node<Node>("Child");
+       if (node != nullptr) {
+           node->set("target", this);
+       }
+
+       // Child
+       UtilityFunctions::print(target);
+
 5. Initialize a NodePath.
 5. Initialize a NodePath.
 
 
    .. tabs::
    .. tabs::
@@ -146,6 +192,17 @@ initialize it:
        // Child
        // Child
        GetNode(TargetPath); // Use parent-defined NodePath.
        GetNode(TargetPath); // Use parent-defined NodePath.
 
 
+     .. code-tab:: cpp C++
+
+       // Parent
+       Node *node = get_node<Node>("Child");
+       if (node != nullptr) {
+           node->set("target_path", NodePath(".."));
+       }
+
+       // Child
+       get_node<Node>(target_path); // Use parent-defined NodePath.
+
 These options hide the points of access from the child node. This in turn
 These options hide the points of access from the child node. This in turn
 keeps the child **loosely coupled** to its environment. You can reuse it
 keeps the child **loosely coupled** to its environment. You can reuse it
 in another context without any extra changes to its API.
 in another context without any extra changes to its API.
@@ -199,6 +256,42 @@ in another context without any extra changes to its API.
           }
           }
       }
       }
 
 
+    .. code-tab:: cpp C++
+
+      // Parent
+      get_node<Left>("Left")->target = get_node<Node>("Right/Receiver");
+
+      class Left : public Node {
+          GDCLASS(Left, Node)
+
+          protected:
+              static void _bind_methods() {} 
+
+          public:
+              Node *target = nullptr;
+
+              Left() {}
+
+              void execute() {
+                  // Do something with 'target'.
+              }
+      };
+
+      class Right : public Node {
+          GDCLASS(Right, Node)
+
+          protected:
+              static void _bind_methods() {}
+
+          public:
+              Node *receiver = nullptr;
+
+              Right() {
+                  receiver = memnew(Node);
+                  add_child(receiver);
+              }
+      };
+
   The same principles also apply to non-Node objects that maintain dependencies
   The same principles also apply to non-Node objects that maintain dependencies
   on other objects. Whichever object owns the other objects should manage
   on other objects. Whichever object owns the other objects should manage
   the relationships between them.
   the relationships between them.