diff --git a/tutorials/best_practices/godot_notifications.rst b/tutorials/best_practices/godot_notifications.rst index 75a0d237816..50363f3a564 100644 --- a/tutorials/best_practices/godot_notifications.rst +++ b/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 between frames. If code needs consistent updates over time, regardless 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 &p_event) override { + Ref 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 ----------------------------------- @@ -223,6 +270,35 @@ values will set up according to the following sequence: // 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 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!"); } } + + .. 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; + } + } + }; diff --git a/tutorials/best_practices/logic_preferences.rst b/tutorials/best_practices/logic_preferences.rst index 6f5901fd28c..a57abe74206 100644 --- a/tutorials/best_practices/logic_preferences.rst +++ b/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 building = ResourceLoader::get_singleton()->load("res://building.tscn"); + Ref 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 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 diff --git a/tutorials/best_practices/scene_organization.rst b/tutorials/best_practices/scene_organization.rst index c03914f9a18..15440fe8d3d 100644 --- a/tutorials/best_practices/scene_organization.rst +++ b/tutorials/best_practices/scene_organization.rst @@ -69,6 +69,19 @@ initialize it: // Child EmitSignal("SignalName"); // Triggers parent-defined behavior. + .. code-tab:: cpp C++ + + // Parent + Node *node = get_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. .. tabs:: @@ -88,6 +101,17 @@ initialize it: // Child Call(MethodName); // Call parent-defined method (which child must own). + .. code-tab:: cpp C++ + + // Parent + Node *node = get_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 ` property. Safer than a method as ownership of the method is unnecessary. Used to start behavior. @@ -108,6 +132,17 @@ initialize it: // Child FuncProperty.Call(); // Call parent-defined method (can come from anywhere). + .. code-tab:: cpp C++ + + // Parent + Node *node = get_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. .. tabs:: @@ -127,6 +162,17 @@ initialize it: // Child GD.Print(Target); // Use parent-defined node. + .. code-tab:: cpp C++ + + // Parent + Node *node = get_node("Child"); + if (node != nullptr) { + node->set("target", this); + } + + // Child + UtilityFunctions::print(target); + 5. Initialize a NodePath. .. tabs:: @@ -146,6 +192,17 @@ initialize it: // Child GetNode(TargetPath); // Use parent-defined NodePath. + .. code-tab:: cpp C++ + + // Parent + Node *node = get_node("Child"); + if (node != nullptr) { + node->set("target_path", NodePath("..")); + } + + // Child + get_node(target_path); // Use parent-defined NodePath. + 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 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")->target = get_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 on other objects. Whichever object owns the other objects should manage the relationships between them.