gdextension_cpp_example.rst 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. .. _doc_gdextension_cpp_example:
  2. GDExtension C++ example
  3. =======================
  4. Introduction
  5. ------------
  6. The C++ bindings for GDExtension are built on top of the C GDExtension API
  7. and provide a nicer way to "extend" nodes and other built-in classes in Godot using C++.
  8. This new system allows the extension of Godot to nearly the same
  9. level as statically linked C++ modules.
  10. You can download the included example in the test folder of the godot-cpp
  11. repository `on GitHub <https://github.com/godotengine/godot-cpp>`__.
  12. Setting up the project
  13. ----------------------
  14. There are a few prerequisites you'll need:
  15. - a Godot 4 executable,
  16. - a C++ compiler,
  17. - SCons as a build tool,
  18. - a copy of the `godot-cpp
  19. repository <https://github.com/godotengine/godot-cpp>`__.
  20. See also :ref:`Compiling <toc-devel-compiling>` as the build tools are identical
  21. to the ones you need to compile Godot from source.
  22. You can download this repository from GitHub or let Git do the work for you.
  23. Note that this repository has different branches for different versions
  24. of Godot. GDExtensions will not work in older versions of Godot (only Godot 4 and up) and vice versa, so make sure you download the correct branch.
  25. .. note::
  26. To use `GDExtension <https://godotengine.org/article/introducing-gd-extensions>`__
  27. you need to use the ``master`` branch of godot-cpp,
  28. which is only compatible with Godot 4.0 and follow this example.
  29. If you are versioning your project using Git, it is a good idea to add it as
  30. a Git submodule:
  31. .. code-block:: none
  32. mkdir gdextension_cpp_example
  33. cd gdextension_cpp_example
  34. git init
  35. git submodule add -b master https://github.com/godotengine/godot-cpp
  36. cd godot-cpp
  37. git submodule update --init
  38. If you decide to just download the repositories or clone them into your project
  39. folder, make sure to keep the folder layout identical to the one described here,
  40. as much of the code we'll be showcasing here assumes the project follows this
  41. layout.
  42. Do make sure you clone recursively to pull in both repositories:
  43. .. code-block:: none
  44. mkdir gdextension_cpp_example
  45. cd gdextension_cpp_example
  46. git clone -b master https://github.com/godotengine/godot-cpp
  47. .. note::
  48. If you decide to download the repository or clone it into your folder,
  49. make sure to keep the folder layout the same as we've setup here. Much of
  50. the code we'll be showcasing here assumes the project has this layout.
  51. If you cloned the example from the link specified in the introduction, the
  52. submodules are not automatically initialized. You will need to execute the
  53. following commands:
  54. .. code-block:: none
  55. cd gdextension_cpp_example
  56. git submodule update --init
  57. This will initialize the repository in your project folder.
  58. Building the C++ bindings
  59. -------------------------
  60. Now that we've downloaded our prerequisites, it is time to build the C++
  61. bindings.
  62. The repository contains a copy of the metadata for the current Godot release,
  63. but if you need to build these bindings for a newer version of Godot, simply
  64. call the Godot executable:
  65. .. code-block:: none
  66. godot --dump-extension-api extension_api.json
  67. Place the resulting ``extension_api.json`` file in the project folder and add
  68. ``custom_api_file=<PATH_TO_FILE>`` to the scons command
  69. below.
  70. To generate and compile the bindings, use this command (replacing ``<platform>``
  71. with ``windows``, ``linux`` or ``macos`` depending on your OS):
  72. To speed up compilation, add `-jN` at the end of the SCons command line where `N`
  73. is the number of CPU threads you have on your system. The example below uses 4 threads.
  74. .. code-block:: none
  75. cd godot-cpp
  76. scons platform=<platform> -j4 custom_api_file=<PATH_TO_FILE>
  77. cd ..
  78. This step will take a while. When it is completed, you should have static
  79. libraries that can be compiled into your project stored in ``godot-cpp/bin/``.
  80. .. note::
  81. You may need to add ``bits=64`` to the command on Windows or Linux.
  82. Creating a simple plugin
  83. ------------------------
  84. Now it's time to build an actual plugin. We'll start by creating an empty Godot
  85. project in which we'll place a few files.
  86. Open Godot and create a new project. For this example, we will place it in a
  87. folder called ``demo`` inside our GDExtension's folder structure.
  88. In our demo project, we'll create a scene containing a Node called "Main" and
  89. we'll save it as ``main.tscn``. We'll come back to that later.
  90. Back in the top-level GDExtension module folder, we're also going to create a
  91. subfolder called ``src`` in which we'll place our source files.
  92. You should now have ``demo``, ``godot-cpp``, and ``src``
  93. directories in your GDExtension module.
  94. Your folder structure should now look like this:
  95. .. code-block:: none
  96. gdextension_cpp_example/
  97. |
  98. +--demo/ # game example/demo to test the extension
  99. |
  100. +--godot-cpp/ # C++ bindings
  101. |
  102. +--src/ # source code of the extension we are building
  103. In the ``src`` folder, we'll start with creating our header file for the
  104. GDExtension node we'll be creating. We will name it ``gdexample.h``:
  105. .. code-block:: C++
  106. #ifndef GDEXAMPLE_H
  107. #define GDEXAMPLE_H
  108. #include <godot_cpp/classes/sprite2d.hpp>
  109. namespace godot {
  110. class GDExample : public Sprite2D {
  111. GDCLASS(GDExample, Sprite2D)
  112. private:
  113. float time_passed;
  114. protected:
  115. static void _bind_methods();
  116. public:
  117. GDExample();
  118. ~GDExample();
  119. void _process(float delta);
  120. };
  121. }
  122. #endif
  123. There are a few things of note to the above. We include ``sprite2d.hpp`` which
  124. contains bindings to the Sprite2D class. We'll be extending this class in our
  125. module.
  126. We're using the namespace ``godot``, since everything in GDExtension is defined
  127. within this namespace.
  128. Then we have our class definition, which inherits from our Sprite2D through a
  129. container class. We'll see a few side effects of this later on. The
  130. ``GDCLASS`` macro sets up a few internal things for us.
  131. After that, we declare a single member variable called ``time_passed``.
  132. In the next block we're defining our methods, we have our constructor
  133. and destructor defined, but there are two other functions that will likely look
  134. familiar to some, and one new method.
  135. The first is ``_bind_methods``, which is a static function that Godot will
  136. call to find out which methods can be called and which properties it exposes.
  137. The second is our ``_process`` function, which will work exactly the same
  138. as the ``_process`` function you're used to in GDScript.
  139. Let's implement our functions by creating our ``gdexample.cpp`` file:
  140. .. code-block:: C++
  141. #include "gdexample.h"
  142. #include <godot_cpp/core/class_db.hpp>
  143. using namespace godot;
  144. void GDExample::_bind_methods() {
  145. }
  146. GDExample::GDExample() {
  147. // initialize any variables here
  148. time_passed = 0.0;
  149. }
  150. GDExample::~GDExample() {
  151. // add your cleanup here
  152. }
  153. void GDExample::_process(float delta) {
  154. time_passed += delta;
  155. Vector2 new_position = Vector2(10.0 + (10.0 * sin(time_passed * 2.0)), 10.0 + (10.0 * cos(time_passed * 1.5)));
  156. set_position(new_position);
  157. }
  158. This one should be straightforward. We're implementing each method of our class
  159. that we defined in our header file.
  160. Note our ``_process`` function, which keeps track of how much time has passed
  161. and calculates a new position for our sprite using a sine and cosine function.
  162. There is one more C++ file we need; we'll name it ``register_types.cpp``. Our
  163. GDExtension plugin can contain multiple classes, each with their own header
  164. and source file like we've implemented ``GDExample`` up above. What we need now
  165. is a small bit of code that tells Godot about all the classes in our
  166. GDExtension plugin.
  167. .. code-block:: C++
  168. #include "register_types.h"
  169. #include "gdexample.h"
  170. #include <gdextension_interface.h>
  171. #include <godot_cpp/core/defs.hpp>
  172. #include <godot_cpp/core/class_db.hpp>
  173. #include <godot_cpp/godot.hpp>
  174. using namespace godot;
  175. void initialize_example_module(ModuleInitializationLevel p_level) {
  176. if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
  177. return;
  178. }
  179. ClassDB::register_class<GDExample>();
  180. }
  181. void uninitialize_example_module(ModuleInitializationLevel p_level) {
  182. if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
  183. return;
  184. }
  185. }
  186. extern "C" {
  187. // Initialization.
  188. GDExtensionBool GDE_EXPORT example_library_init(const GDExtensionInterface *p_interface, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
  189. godot::GDExtensionBinding::InitObject init_obj(p_interface, p_library, r_initialization);
  190. init_obj.register_initializer(initialize_example_module);
  191. init_obj.register_terminator(uninitialize_example_module);
  192. init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
  193. return init_obj.init();
  194. }
  195. }
  196. The ``initialize_example_module`` and ``uninitialize_example_module`` functions get
  197. called respectively when Godot loads our plugin and when it unloads it. All
  198. we're doing here is parse through the functions in our bindings module to
  199. initialize them, but you might have to set up more things depending on your
  200. needs. We call the function ``register_class`` for each of our classes in our library.
  201. The important function is the third function called ``example_library_init``.
  202. We first call a function in our bindings library that creates an initilization object.
  203. This object registrates the initialization and termination functions of the GDExtension.
  204. Furthermore, it sets the level of initilization (core, servers, scene, editor, level).
  205. At last, we need the header file for the ``register_types.cpp`` named
  206. ``register_types.h``.
  207. .. code-block:: C++
  208. #ifndef GDEXAMPLE_REGISTER_TYPES_H
  209. #define GDEXAMPLE_REGISTER_TYPES_H
  210. void initialize_example_module();
  211. void uninitialize_example_module();
  212. #endif // GDEXAMPLE_REGISTER_TYPES_H
  213. Compiling the plugin
  214. --------------------
  215. We cannot easily write by hand a ``SConstruct`` file that SCons would use for
  216. building. For the purpose of this example, just use
  217. :download:`this hardcoded SConstruct file <files/cpp_example/SConstruct>` we've
  218. prepared. We'll cover a more customizable, detailed example on how to use these
  219. build files in a subsequent tutorial.
  220. .. note::
  221. This ``SConstruct`` file was written to be used with the latest ``godot-cpp``
  222. master, you may need to make small changes using it with older versions or
  223. refer to the ``SConstruct`` file in the Godot 4.0 documentation.
  224. Once you've downloaded the ``SConstruct`` file, place it in your GDExtension folder
  225. structure alongside ``godot-cpp``, ``src`` and ``demo``, then run:
  226. .. code-block:: bash
  227. scons platform=<platform>
  228. You should now be able to find the module in ``demo/bin/<platform>``.
  229. .. note::
  230. Here, we've compiled both godot-cpp and our gdexample library as debug
  231. builds. For optimized builds, you should compile them using the
  232. ``target=template_release`` switch.
  233. Using the GDExtension module
  234. ----------------------------
  235. Before we jump back into Godot, we need to create one more file in
  236. ``demo/bin/``.
  237. This file lets Godot know what dynamic libraries should be
  238. loaded for each platform and the entry function for the module. It is called ``gdexample.gdextension``.
  239. .. code-block:: none
  240. [configuration]
  241. entry_symbol = "example_library_init"
  242. [libraries]
  243. linux.64="res://bin/libgdexample.linux.64.so"
  244. windows.x86_64="res://bin/libgdexample.windows.x86_64.dll"
  245. macos="res://bin/libgdexample.macos.framework"
  246. This file contains a ``configuration`` section that controls the entry function of the module.
  247. The ``libraries`` section is the important bit: it tells Godot the location of the
  248. dynamic library in the project's filesystem for each supported platform. It will
  249. also result in *just* that file being exported when you export the project,
  250. which means the data pack won't contain libraries that are incompatible with the
  251. target platform.
  252. Finally, the ``dependencies`` section allows you to name additional dynamic
  253. libraries that should be included as well. This is important when your GDExtension
  254. plugin implements someone else's library and requires you to supply a
  255. third-party dynamic library with your project.
  256. Here is another overview to check the correct file structure:
  257. .. code-block:: none
  258. gdextension_cpp_example/
  259. |
  260. +--demo/ # game example/demo to test the extension
  261. | |
  262. | +--main.tscn
  263. | |
  264. | +--bin/
  265. | |
  266. | +--gdexample.gdextension
  267. |
  268. +--godot-cpp/ # C++ bindings
  269. |
  270. +--src/ # source code of the extension we are building
  271. | |
  272. | +--register_types.cpp
  273. | +--register_types.h
  274. | +--gdexample.cpp
  275. | +--gdexample.h
  276. Time to jump back into Godot. We load up the main scene we created way back in
  277. the beginning and now add a newly available GDExample node to the scene:
  278. .. image:: img/gdextension_cpp_nodes.webp
  279. We're going to assign the Godot logo to this node as our texture, disable the
  280. ``centered`` property:
  281. .. image:: img/gdextension_cpp_sprite.webp
  282. We're finally ready to run the project:
  283. .. image:: img/gdextension_cpp_animated.gif
  284. Adding properties
  285. -----------------
  286. GDScript allows you to add properties to your script using the ``export``
  287. keyword. In GDExtension you have to register the properties with a getter and
  288. setter function or directly implement the ``_get_property_list``, ``_get`` and
  289. ``_set`` methods of an object (but that goes far beyond the scope of this
  290. tutorial.
  291. Lets add a property that allows us to control the amplitude of our wave.
  292. In our ``gdexample.h`` file we need to add a member variable and getter and setter
  293. functions:
  294. .. code-block:: C++
  295. ...
  296. private:
  297. float time_passed;
  298. float amplitude;
  299. public:
  300. void set_amplitude(const float amplitude);
  301. float get_amplitude() const;
  302. ...
  303. In our ``gdexample.cpp`` file we need to make a number of changes, we will only
  304. show the methods we end up changing, don't remove the lines we're omitting:
  305. .. code-block:: C++
  306. void GDExample::_bind_methods() {
  307. ClassDB::bind_method(D_METHOD("get_amplitude"), &GDExample::get_amplitude);
  308. ClassDB::bind_method(D_METHOD("set_amplitude", "p_amplitude"), &GDExample::set_amplitude);
  309. ClassDB::add_property("GDExample", PropertyInfo(Variant::FLOAT, "amplitude"), "set_amplitude", "get_amplitude");
  310. }
  311. void GDExample::GDExample() {
  312. // initialize any variables here
  313. time_passed = 0.0;
  314. amplitude = 10.0;
  315. }
  316. void GDExample::_process(float delta) {
  317. time_passed += delta;
  318. Vector2 new_position = Vector2(
  319. amplitude + (amplitude * sin(time_passed * 2.0)),
  320. amplitude + (amplitude * cos(time_passed * 1.5))
  321. );
  322. set_position(new_position);
  323. }
  324. void GDExample::set_amplitude(const float p_amplitude) {
  325. amplitude = p_amplitude;
  326. }
  327. float GDExample::get_amplitude() const {
  328. return amplitude;
  329. }
  330. Once you compile the module with these changes in place, you will see that a
  331. property has been added to our interface. You can now change this property and
  332. when you run your project, you will see that our Godot icon travels along a
  333. larger figure.
  334. Let's do the same but for the speed of our animation and use a setter and getter
  335. function. Our ``gdexample.h`` header file again only needs a few more lines of
  336. code:
  337. .. code-block:: C++
  338. ...
  339. float amplitude;
  340. float speed;
  341. ...
  342. void _process(float delta) override;
  343. void set_speed(float p_speed);
  344. float get_speed();
  345. ...
  346. This requires a few more changes to our ``gdexample.cpp`` file, again we're only
  347. showing the methods that have changed so don't remove anything we're omitting:
  348. .. code-block:: C++
  349. void GDExample::_bind_methods() {
  350. ...
  351. ClassDB::bind_method(D_METHOD("get_speed"), &GDExample::get_speed);
  352. ClassDB::bind_method(D_METHOD("set_speed", "p_speed"), &GDExample::set_speed);
  353. ClassDB::add_property("GDExample", PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_speed", "get_speed");
  354. }
  355. void GDExample::GDExample() {
  356. time_passed = 0.0;
  357. amplitude = 10.0;
  358. speed = 1.0;
  359. }
  360. void GDExample::_process(float delta) {
  361. time_passed += speed * delta;
  362. Vector2 new_position = Vector2(
  363. amplitude + (amplitude * sin(time_passed * 2.0)),
  364. amplitude + (amplitude * cos(time_passed * 1.5))
  365. );
  366. set_position(new_position);
  367. }
  368. ...
  369. void GDExample::set_speed(float p_speed) {
  370. speed = p_speed;
  371. }
  372. float GDExample::get_speed() const {
  373. return speed;
  374. }
  375. Now when the project is compiled, we'll see another property called speed.
  376. Changing its value will make the animation go faster or slower.
  377. Furthermore, we added a property range which describes in which range the value can be.
  378. The first two arguments are the minimum and maximum value and the third is the step size.
  379. .. note::
  380. For simplicity, we've only used the hint_range of the property method.
  381. There are a lot more options to choose from. These can be used to
  382. further configure how properties are displayed and set on the Godot side.
  383. Signals
  384. -------
  385. Last but not least, signals fully work in GDExtension as well. Having your extension
  386. react to a signal given out by another object requires you to call ``connect``
  387. on that object. We can't think of a good example for our wobbling Godot icon, we
  388. would need to showcase a far more complete example.
  389. This is the required syntax:
  390. .. code-block:: C++
  391. some_other_node->connect("the_signal", this, "my_method");
  392. Note that you can only call ``my_method`` if you've previously registered it in
  393. your ``_bind_methods`` method.
  394. Having your object sending out signals is more common. For our wobbling
  395. Godot icon, we'll do something silly just to show how it works. We're going to
  396. emit a signal every time a second has passed and pass the new location along.
  397. In our ``gdexample.h`` header file, we need to define a new member ``time_emit``:
  398. .. code-block:: C++
  399. ...
  400. float time_passed;
  401. float time_emit;
  402. float amplitude;
  403. ...
  404. This time, the changes in ``gdexample.cpp`` are more elaborate. First,
  405. you'll need to set ``time_emit = 0.0;`` in either our ``_init`` method or in our
  406. constructor. We'll look at the other 2 needed changes one by one.
  407. In our ``_bind_methods`` method, we need to declare our signal. This is done
  408. as follows:
  409. .. code-block:: C++
  410. void GDExample::_bind_methods() {
  411. ...
  412. ClassDB::add_property("GDExample", PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_speed", "get_speed");
  413. ADD_SIGNAL(MethodInfo("position_changed", PropertyInfo(Variant::OBJECT, "node"), PropertyInfo(Variant::VECTOR2, "new_pos")));
  414. }
  415. Here, our ``ADD_SIGNAL`` method can be a single call first taking the
  416. signals name, then having pairs of the type specifying the parameter name and
  417. the value of each parameter we'll send along with this signal.
  418. Next, we'll need to change our ``_process`` method:
  419. .. code-block:: C++
  420. void GDExample::_process(float delta) {
  421. time_passed += speed * delta;
  422. Vector2 new_position = Vector2(
  423. amplitude + (amplitude * sin(time_passed * 2.0)),
  424. amplitude + (amplitude * cos(time_passed * 1.5))
  425. );
  426. set_position(new_position);
  427. time_emit += delta;
  428. if (time_emit > 1.0) {
  429. emit_signal("position_changed", this, new_position);
  430. time_emit = 0.0;
  431. }
  432. }
  433. After a second has passed, we emit our signal and reset our counter. We can add
  434. our parameter values directly to ``emit_signal``.
  435. Once the GDExtension library is compiled, we can go into Godot and select our sprite
  436. node. In the **Node** dock, we can find our new signal and link it up by pressing
  437. the **Connect** button or double-clicking the signal. We've added a script on
  438. our main node and implemented our signal like this:
  439. .. code-block:: GDScript
  440. extends Node
  441. func _on_Sprite2D_position_changed(node, new_pos):
  442. print("The position of " + node.get_class() + " is now " + str(new_pos))
  443. Every second, we output our position to the console.
  444. Next steps
  445. ----------
  446. We hope the above example showed you the basics. You can
  447. build upon this example to create full-fledged scripts to control nodes in Godot
  448. using C++.