Skip to content

Commit d0d797c

Browse files
authored
Merge pull request #9837 from dementive/best-practices-cpp
Add GDExtension C++ snippets to tutorials/best_practices
2 parents a321c4e + de7b67d commit d0d797c

File tree

3 files changed

+221
-0
lines changed

3 files changed

+221
-0
lines changed

tutorials/best_practices/godot_notifications.rst

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,29 @@ implementing a Timer-timeout loop is another option.
102102
}
103103
}
104104

105+
.. code-tab:: cpp C++
106+
107+
using namespace godot;
108+
109+
class MyNode : public Node {
110+
GDCLASS(MyNode, Node)
111+
112+
public:
113+
// Allows for recurring operations that don't trigger script logic
114+
// every frame (or even every fixed frame).
115+
virtual void _ready() override {
116+
Timer *timer = memnew(Timer);
117+
timer->set_autostart(true);
118+
timer->set_wait_time(0.5);
119+
add_child(timer);
120+
timer->connect("timeout", callable_mp(this, &MyNode::run));
121+
}
122+
123+
void run() {
124+
UtilityFunctions::print("This block runs every 0.5 seconds.");
125+
}
126+
};
127+
105128
Use ``_physics_process()`` when one needs a framerate-independent delta time
106129
between frames. If code needs consistent updates over time, regardless
107130
of how fast or slow time advances, this is the right place.
@@ -160,6 +183,30 @@ delta time methods as needed.
160183

161184
}
162185

186+
.. code-tab:: cpp C++
187+
188+
using namespace godot;
189+
190+
class MyNode : public Node {
191+
GDCLASS(MyNode, Node)
192+
193+
public:
194+
// Called every frame, even when the engine detects no input.
195+
virtual void _process(double p_delta) override {
196+
if (Input::get_singleton->is_action_just_pressed("ui_select")) {
197+
UtilityFunctions::print(p_delta);
198+
}
199+
}
200+
201+
// Called during every input event. Equally true for _input().
202+
virtual void _unhandled_input(const Ref<InputEvent> &p_event) override {
203+
Ref<InputEventKey> key_event = event;
204+
if (key_event.is_valid() && Input::get_singleton->is_action_just_pressed("ui_accept")) {
205+
UtilityFunctions::print(get_process_delta_time());
206+
}
207+
}
208+
};
209+
163210
_init vs. initialization vs. export
164211
-----------------------------------
165212

@@ -223,6 +270,35 @@ values will set up according to the following sequence:
223270
// the setter, changing _test's value from "two!" to "three!".
224271
}
225272

273+
.. code-tab:: cpp C++
274+
275+
using namespace godot;
276+
277+
class MyNode : public Node {
278+
GDCLASS(MyNode, Node)
279+
280+
String test = "one";
281+
282+
protected:
283+
static void _bind_methods() {
284+
ClassDB::bind_method(D_METHOD("get_test"), &MyNode::get_test);
285+
ClassDB::bind_method(D_METHOD("set_test", "test"), &MyNode::set_test);
286+
ADD_PROPERTY(PropertyInfo(Variant::STRING, "test"), "set_test", "get_test");
287+
}
288+
289+
public:
290+
String get_test() { return test; }
291+
void set_test(String p_test) { return test = p_test; }
292+
293+
MyNode() {
294+
// Triggers the setter, changing _test's value from "one" to "two!".
295+
set_test("two");
296+
}
297+
298+
// If someone sets test to "three" in the Inspector, it would trigger
299+
// the setter, changing test's value from "two!" to "three!".
300+
};
301+
226302
As a result, instantiating a script versus a scene may affect both the
227303
initialization *and* the number of times the engine calls the setter.
228304

@@ -309,3 +385,38 @@ nodes that one might create at runtime.
309385
GD.Print("I'm reacting to my parent's interaction!");
310386
}
311387
}
388+
389+
.. code-tab:: cpp C++
390+
391+
using namespace godot;
392+
393+
class MyNode : public Node {
394+
GDCLASS(MyNode, Node)
395+
396+
Node *parent_cache = nullptr;
397+
398+
void on_parent_interacted_with() {
399+
UtilityFunctions::print("I'm reacting to my parent's interaction!");
400+
}
401+
402+
public:
403+
void connection_check() {
404+
return parent_cache->has_user_signal("interacted_with");
405+
}
406+
407+
void _notification(int p_what) {
408+
switch (p_what) {
409+
case NOTIFICATION_PARENTED:
410+
parent_cache = get_parent();
411+
if (connection_check()) {
412+
parent_cache->connect("interacted_with", callable_mp(this, &MyNode::on_parent_interacted_with));
413+
}
414+
break;
415+
case NOTIFICATION_UNPARENTED:
416+
if (connection_check()) {
417+
parent_cache->disconnect("interacted_with", callable_mp(this, &MyNode::on_parent_interacted_with));
418+
}
419+
break;
420+
}
421+
}
422+
};

tutorials/best_practices/logic_preferences.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,23 @@ either? Let's see an example:
100100
}
101101
}
102102

103+
.. code-tab:: cpp C++
104+
105+
using namespace godot;
106+
107+
class MyBuildings : public Node {
108+
GDCLASS(MyBuildings, Node)
109+
110+
public:
111+
const Ref<PackedScene> building = ResourceLoader::get_singleton()->load("res://building.tscn");
112+
Ref<PackedScene> a_building;
113+
114+
virtual void _ready() override {
115+
// Can assign the value during initialization.
116+
a_building = ResourceLoader::get_singleton()->load("res://office.tscn");
117+
}
118+
};
119+
103120
Preloading allows the script to handle all the loading the moment one loads the
104121
script. Preloading is useful, but there are also times when one doesn't wish
105122
for it. To distinguish these situations, there are a few things one can

tutorials/best_practices/scene_organization.rst

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,19 @@ initialize it:
6969
// Child
7070
EmitSignal("SignalName"); // Triggers parent-defined behavior.
7171

72+
.. code-tab:: cpp C++
73+
74+
// Parent
75+
Node *node = get_node<Node>("Child");
76+
if (node != nullptr) {
77+
// Note that get_node may return a nullptr, which would make calling the connect method crash the engine if "Child" does not exist!
78+
// So unless you are 1000% sure get_node will never return a nullptr, it's a good idea to always do a nullptr check.
79+
node->connect("signal_name", callable_mp(this, &ObjectWithMethod::method_on_the_object));
80+
}
81+
82+
// Child
83+
emit_signal("signal_name"); // Triggers parent-defined behavior.
84+
7285
2. Call a method. Used to start behavior.
7386

7487
.. tabs::
@@ -88,6 +101,17 @@ initialize it:
88101
// Child
89102
Call(MethodName); // Call parent-defined method (which child must own).
90103

104+
.. code-tab:: cpp C++
105+
106+
// Parent
107+
Node *node = get_node<Node>("Child");
108+
if (node != nullptr) {
109+
node->set("method_name", "do");
110+
}
111+
112+
// Child
113+
call(method_name); // Call parent-defined method (which child must own).
114+
91115
3. Initialize a :ref:`Callable <class_Callable>` property. Safer than a method
92116
as ownership of the method is unnecessary. Used to start behavior.
93117

@@ -108,6 +132,17 @@ initialize it:
108132
// Child
109133
FuncProperty.Call(); // Call parent-defined method (can come from anywhere).
110134

135+
.. code-tab:: cpp C++
136+
137+
// Parent
138+
Node *node = get_node<Node>("Child");
139+
if (node != nullptr) {
140+
node->set("func_property", Callable(&ObjectWithMethod::method_on_the_object));
141+
}
142+
143+
// Child
144+
func_property.call(); // Call parent-defined method (can come from anywhere).
145+
111146
4. Initialize a Node or other Object reference.
112147

113148
.. tabs::
@@ -127,6 +162,17 @@ initialize it:
127162
// Child
128163
GD.Print(Target); // Use parent-defined node.
129164

165+
.. code-tab:: cpp C++
166+
167+
// Parent
168+
Node *node = get_node<Node>("Child");
169+
if (node != nullptr) {
170+
node->set("target", this);
171+
}
172+
173+
// Child
174+
UtilityFunctions::print(target);
175+
130176
5. Initialize a NodePath.
131177

132178
.. tabs::
@@ -146,6 +192,17 @@ initialize it:
146192
// Child
147193
GetNode(TargetPath); // Use parent-defined NodePath.
148194

195+
.. code-tab:: cpp C++
196+
197+
// Parent
198+
Node *node = get_node<Node>("Child");
199+
if (node != nullptr) {
200+
node->set("target_path", NodePath(".."));
201+
}
202+
203+
// Child
204+
get_node<Node>(target_path); // Use parent-defined NodePath.
205+
149206
These options hide the points of access from the child node. This in turn
150207
keeps the child **loosely coupled** to its environment. You can reuse it
151208
in another context without any extra changes to its API.
@@ -199,6 +256,42 @@ in another context without any extra changes to its API.
199256
}
200257
}
201258

259+
.. code-tab:: cpp C++
260+
261+
// Parent
262+
get_node<Left>("Left")->target = get_node<Node>("Right/Receiver");
263+
264+
class Left : public Node {
265+
GDCLASS(Left, Node)
266+
267+
protected:
268+
static void _bind_methods() {}
269+
270+
public:
271+
Node *target = nullptr;
272+
273+
Left() {}
274+
275+
void execute() {
276+
// Do something with 'target'.
277+
}
278+
};
279+
280+
class Right : public Node {
281+
GDCLASS(Right, Node)
282+
283+
protected:
284+
static void _bind_methods() {}
285+
286+
public:
287+
Node *receiver = nullptr;
288+
289+
Right() {
290+
receiver = memnew(Node);
291+
add_child(receiver);
292+
}
293+
};
294+
202295
The same principles also apply to non-Node objects that maintain dependencies
203296
on other objects. Whichever object owns the other objects should manage
204297
the relationships between them.

0 commit comments

Comments
 (0)