Skip to content

Add GDExtension C++ snippets to tutorials/best_practices #9837

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions tutorials/best_practices/godot_notifications.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
-----------------------------------

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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;
}
}
};
17 changes: 17 additions & 0 deletions tutorials/best_practices/logic_preferences.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
Expand Down
93 changes: 93 additions & 0 deletions tutorials/best_practices/scene_organization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ initialize it:
// Child
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.

.. tabs::
Expand All @@ -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<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
as ownership of the method is unnecessary. Used to start behavior.

Expand All @@ -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<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::
Expand All @@ -127,6 +162,17 @@ initialize it:
// Child
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.

.. tabs::
Expand All @@ -146,6 +192,17 @@ initialize it:
// Child
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
keeps the child **loosely coupled** to its environment. You can reuse it
in another context without any extra changes to its API.
Expand Down Expand Up @@ -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
on other objects. Whichever object owns the other objects should manage
the relationships between them.
Expand Down