123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713 |
- .. _doc_gdextension_cpp_example:
- GDExtension C++ example
- =======================
- Introduction
- ------------
- The C++ bindings for GDExtension are built on top of the C GDExtension API
- and provide a nicer way to "extend" nodes and other built-in classes in Godot using C++.
- This new system allows the extension of Godot to nearly the same
- level as statically linked C++ modules.
- You can download the included example in the test folder of the godot-cpp
- repository `on GitHub <https://github.com/godotengine/godot-cpp>`__.
- Setting up the project
- ----------------------
- There are a few prerequisites you'll need:
- - a Godot 4 executable,
- - a C++ compiler,
- - SCons as a build tool,
- - a copy of the `godot-cpp
- repository <https://github.com/godotengine/godot-cpp>`__.
- See also :ref:`Compiling <toc-devel-compiling>` as the build tools are identical
- to the ones you need to compile Godot from source.
- You can download the `godot-cpp repository <https://github.com/godotengine/godot-cpp>`__ from GitHub or let Git do the work for you.
- Note that this repository has different branches for different versions
- 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.
- .. note::
- To use `GDExtension <https://godotengine.org/article/introducing-gd-extensions>`__
- you need to use the godot-cpp branch that matches the version of Godot that you are
- targeting. For example, if you're targeting Godot 4.1, use the ``4.1`` branch. Throughout
- this tutorial we use ``4.x``, which will need to be replaced with the version of Godot you
- are targeting.
- The ``master`` branch is the development branch which is updated regularly
- to work with Godot's ``master`` branch.
- .. warning::
- Our long-term goal is that GDExtensions targeting an earlier version of Godot will work
- in later minor versions, but not vice-versa. For example, a GDExtension targeting Godot 4.1
- should work just fine in Godot 4.2, but one targeting Godot 4.2 won't work in Godot 4.1.
- However, GDExtension is currently *experimental*, which means that we may break compatibility
- in order to fix major bugs or include critical features. For example, GDExtensions created
- for Godot 4.0 aren't compatible with Godot 4.1
- (see :ref:`updating_your_gdextension_for_godot_4_1`).
- If you are versioning your project using Git, it is recommended to add it as
- a Git submodule:
- .. code-block:: none
- mkdir gdextension_cpp_example
- cd gdextension_cpp_example
- git init
- git submodule add -b 4.x https://github.com/godotengine/godot-cpp
- cd godot-cpp
- git submodule update --init
- Alternatively, you can also clone it to the project folder:
- .. code-block:: none
- mkdir gdextension_cpp_example
- cd gdextension_cpp_example
- git clone -b 4.x https://github.com/godotengine/godot-cpp
- .. note::
- If you decide to download the repository or clone it into your folder,
- make sure to keep the folder layout the same as we've setup here. Much of
- the code we'll be showcasing here assumes the project has this layout.
- If you cloned the example from the link specified in the introduction, the
- submodules are not automatically initialized. You will need to execute the
- following commands:
- .. code-block:: none
- cd gdextension_cpp_example
- git submodule update --init
- This will initialize the repository in your project folder.
- Building the C++ bindings
- -------------------------
- Now that we've downloaded our prerequisites, it is time to build the C++
- bindings.
- The repository contains a copy of the metadata for the current Godot release,
- but if you need to build these bindings for a newer version of Godot, simply
- call the Godot executable:
- .. code-block:: none
- godot --dump-extension-api
- The resulting ``extension_api.json`` file will be created in the executable's
- directory. Copy it to the project folder and add ``custom_api_file=<PATH_TO_FILE>``
- to the scons command below.
- To generate and compile the bindings, use this command (replacing ``<platform>``
- with ``windows``, ``linux`` or ``macos`` depending on your OS):
- The build process automatically detects the number of CPU threads to use for
- parallel builds. To specify a number of CPU threads to use, add ``-jN`` at the
- end of the SCons command line where ``N`` is the number of CPU threads to use.
- .. code-block:: none
- cd godot-cpp
- scons platform=<platform> custom_api_file=<PATH_TO_FILE>
- cd ..
- This step will take a while. When it is completed, you should have static
- libraries that can be compiled into your project stored in ``godot-cpp/bin/``.
- .. note::
- You may need to add ``bits=64`` to the command on Windows or Linux.
- Creating a simple plugin
- ------------------------
- Now it's time to build an actual plugin. We'll start by creating an empty Godot
- project in which we'll place a few files.
- Open Godot and create a new project. For this example, we will place it in a
- folder called ``demo`` inside our GDExtension's folder structure.
- In our demo project, we'll create a scene containing a Node called "Main" and
- we'll save it as ``main.tscn``. We'll come back to that later.
- Back in the top-level GDExtension module folder, we're also going to create a
- subfolder called ``src`` in which we'll place our source files.
- You should now have ``demo``, ``godot-cpp``, and ``src``
- directories in your GDExtension module.
- Your folder structure should now look like this:
- .. code-block:: none
- gdextension_cpp_example/
- |
- +--demo/ # game example/demo to test the extension
- |
- +--godot-cpp/ # C++ bindings
- |
- +--src/ # source code of the extension we are building
- In the ``src`` folder, we'll start with creating our header file for the
- GDExtension node we'll be creating. We will name it ``gdexample.h``:
- .. code-block:: cpp
- #ifndef GDEXAMPLE_H
- #define GDEXAMPLE_H
- #include <godot_cpp/classes/sprite2d.hpp>
- namespace godot {
- class GDExample : public Sprite2D {
- GDCLASS(GDExample, Sprite2D)
- private:
- double time_passed;
- protected:
- static void _bind_methods();
- public:
- GDExample();
- ~GDExample();
- void _process(double delta) override;
- };
- }
- #endif
- There are a few things of note to the above. We include ``sprite2d.hpp`` which
- contains bindings to the Sprite2D class. We'll be extending this class in our
- module.
- We're using the namespace ``godot``, since everything in GDExtension is defined
- within this namespace.
- Then we have our class definition, which inherits from our Sprite2D through a
- container class. We'll see a few side effects of this later on. The
- ``GDCLASS`` macro sets up a few internal things for us.
- After that, we declare a single member variable called ``time_passed``.
- In the next block we're defining our methods, we have our constructor
- and destructor defined, but there are two other functions that will likely look
- familiar to some, and one new method.
- The first is ``_bind_methods``, which is a static function that Godot will
- call to find out which methods can be called and which properties it exposes.
- The second is our ``_process`` function, which will work exactly the same
- as the ``_process`` function you're used to in GDScript.
- Let's implement our functions by creating our ``gdexample.cpp`` file:
- .. code-block:: cpp
- #include "gdexample.h"
- #include <godot_cpp/core/class_db.hpp>
- using namespace godot;
- void GDExample::_bind_methods() {
- }
- GDExample::GDExample() {
- // Initialize any variables here.
- time_passed = 0.0;
- }
- GDExample::~GDExample() {
- // Add your cleanup here.
- }
- void GDExample::_process(double delta) {
- time_passed += delta;
- Vector2 new_position = Vector2(10.0 + (10.0 * sin(time_passed * 2.0)), 10.0 + (10.0 * cos(time_passed * 1.5)));
- set_position(new_position);
- }
- This one should be straightforward. We're implementing each method of our class
- that we defined in our header file.
- Note our ``_process`` function, which keeps track of how much time has passed
- and calculates a new position for our sprite using a sine and cosine function.
- There is one more C++ file we need; we'll name it ``register_types.cpp``. Our
- GDExtension plugin can contain multiple classes, each with their own header
- and source file like we've implemented ``GDExample`` up above. What we need now
- is a small bit of code that tells Godot about all the classes in our
- GDExtension plugin.
- .. code-block:: cpp
- #include "register_types.h"
- #include "gdexample.h"
- #include <gdextension_interface.h>
- #include <godot_cpp/core/defs.hpp>
- #include <godot_cpp/godot.hpp>
- using namespace godot;
- void initialize_example_module(ModuleInitializationLevel p_level) {
- if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
- return;
- }
- GDREGISTER_CLASS(GDExample);
- }
- void uninitialize_example_module(ModuleInitializationLevel p_level) {
- if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
- return;
- }
- }
- extern "C" {
- // Initialization.
- GDExtensionBool GDE_EXPORT example_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
- godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
- init_obj.register_initializer(initialize_example_module);
- init_obj.register_terminator(uninitialize_example_module);
- init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
- return init_obj.init();
- }
- }
- The ``initialize_example_module`` and ``uninitialize_example_module`` functions get
- called respectively when Godot loads our plugin and when it unloads it. All
- we're doing here is parse through the functions in our bindings module to
- initialize them, but you might have to set up more things depending on your
- needs. We call the ``GDREGISTER_CLASS`` macro for each of our classes in our library.
- The important function is the third function called ``example_library_init``.
- We first call a function in our bindings library that creates an initialization object.
- This object registers the initialization and termination functions of the GDExtension.
- Furthermore, it sets the level of initialization (core, servers, scene, editor, level).
- At last, we need the header file for the ``register_types.cpp`` named
- ``register_types.h``.
- .. code-block:: cpp
- #ifndef GDEXAMPLE_REGISTER_TYPES_H
- #define GDEXAMPLE_REGISTER_TYPES_H
- #include <godot_cpp/core/class_db.hpp>
- using namespace godot;
- void initialize_example_module(ModuleInitializationLevel p_level);
- void uninitialize_example_module(ModuleInitializationLevel p_level);
- #endif // GDEXAMPLE_REGISTER_TYPES_H
- Compiling the plugin
- --------------------
- We cannot easily write by hand a ``SConstruct`` file that SCons would use for
- building. For the purpose of this example, just use
- :download:`this hardcoded SConstruct file <files/cpp_example/SConstruct>` we've
- prepared. We'll cover a more customizable, detailed example on how to use these
- build files in a subsequent tutorial.
- .. note::
- This ``SConstruct`` file was written to be used with the latest ``godot-cpp``
- master, you may need to make small changes using it with older versions or
- refer to the ``SConstruct`` file in the Godot 4.x documentation.
- Once you've downloaded the ``SConstruct`` file, place it in your GDExtension folder
- structure alongside ``godot-cpp``, ``src`` and ``demo``, then run:
- .. code-block:: bash
- scons platform=<platform>
- You should now be able to find the module in ``demo/bin/<platform>``.
- When building for iOS, package the module as a static `.xcframework`, you can use
- following commands to do so:
- ::
- # compile simulator and device modules
- scons arch=universal ios_simulator=yes platform=ios target=<target>
- scons arch=arm64 ios_simulator=no platform=ios target=<target>
- # assemble xcframeworks
- xcodebuild -create-xcframework -library demo/bin/libgdexample.ios.<target>.a -library demo/bin/libgdexample.ios.<target>.simulator.a -output demo/bin/libgdexample.ios.<target>.xcframework
- xcodebuild -create-xcframework -library godot-cpp/bin/libgodot-cpp.ios.<target>.arm64.a -library godot-cpp/bin/libgodot-cpp.ios.<target>.universal.simulator.a -output demo/bin/libgodot-cpp.ios.<target>.xcframework
- .. note::
- Here, we've compiled both godot-cpp and our gdexample library as debug
- builds. For optimized builds, you should compile them using the
- ``target=template_release`` switch.
- Using the GDExtension module
- ----------------------------
- Before we jump back into Godot, we need to create one more file in
- ``demo/bin/``.
- This file lets Godot know what dynamic libraries should be
- loaded for each platform and the entry function for the module. It is called ``gdexample.gdextension``.
- .. code-block:: none
- [configuration]
- entry_symbol = "example_library_init"
- compatibility_minimum = "4.1"
- reloadable = true
- [libraries]
- macos.debug = "res://bin/libgdexample.macos.template_debug.framework"
- macos.release = "res://bin/libgdexample.macos.template_release.framework"
- ios.debug = "res://bin/libgdexample.ios.template_debug.xcframework"
- ios.release = "res://bin/libgdexample.ios.template_release.xcframework"
- windows.debug.x86_32 = "res://bin/libgdexample.windows.template_debug.x86_32.dll"
- windows.release.x86_32 = "res://bin/libgdexample.windows.template_release.x86_32.dll"
- windows.debug.x86_64 = "res://bin/libgdexample.windows.template_debug.x86_64.dll"
- windows.release.x86_64 = "res://bin/libgdexample.windows.template_release.x86_64.dll"
- linux.debug.x86_64 = "res://bin/libgdexample.linux.template_debug.x86_64.so"
- linux.release.x86_64 = "res://bin/libgdexample.linux.template_release.x86_64.so"
- linux.debug.arm64 = "res://bin/libgdexample.linux.template_debug.arm64.so"
- linux.release.arm64 = "res://bin/libgdexample.linux.template_release.arm64.so"
- linux.debug.rv64 = "res://bin/libgdexample.linux.template_debug.rv64.so"
- linux.release.rv64 = "res://bin/libgdexample.linux.template_release.rv64.so"
- android.debug.x86_64 = "res://bin/libgdexample.android.template_debug.x86_64.so"
- android.release.x86_64 = "res://bin/libgdexample.android.template_release.x86_64.so"
- android.debug.arm64 = "res://bin/libgdexample.android.template_debug.arm64.so"
- android.release.arm64 = "res://bin/libgdexample.android.template_release.arm64.so"
- [dependencies]
- ios.debug = {
- "res://bin/libgodot-cpp.ios.template_debug.xcframework": ""
- }
- ios.release = {
- "res://bin/libgodot-cpp.ios.template_release.xcframework": ""
- }
- This file contains a ``configuration`` section that controls the entry function of the module.
- You should also set the minimum compatible Godot version with ``compatability_minimum``,
- which prevents older version of Godot from trying to load your extension.
- The ``reloadable`` flag enables automatic reloading of your extension by the editor every time you recompile it,
- without needing to restart the editor. This only works if you compile your extension in debug mode (default).
- The ``libraries`` section is the important bit: it tells Godot the location of the
- dynamic library in the project's filesystem for each supported platform. It will
- also result in *just* that file being exported when you export the project,
- which means the data pack won't contain libraries that are incompatible with the
- target platform.
- Finally, the ``dependencies`` section allows you to name additional dynamic
- libraries that should be included as well. This is important when your GDExtension
- plugin implements someone else's library and requires you to supply a
- third-party dynamic library with your project.
- Here is another overview to check the correct file structure:
- .. code-block:: none
- gdextension_cpp_example/
- |
- +--demo/ # game example/demo to test the extension
- | |
- | +--main.tscn
- | |
- | +--bin/
- | |
- | +--gdexample.gdextension
- |
- +--godot-cpp/ # C++ bindings
- |
- +--src/ # source code of the extension we are building
- | |
- | +--register_types.cpp
- | +--register_types.h
- | +--gdexample.cpp
- | +--gdexample.h
- Time to jump back into Godot. We load up the main scene we created way back in
- the beginning and now add a newly available GDExample node to the scene:
- .. image:: img/gdextension_cpp_nodes.webp
- We're going to assign the Godot logo to this node as our texture, disable the
- ``centered`` property:
- .. image:: img/gdextension_cpp_sprite.webp
- We're finally ready to run the project:
- .. image:: img/gdextension_cpp_animated.gif
- Adding properties
- -----------------
- GDScript allows you to add properties to your script using the ``export``
- keyword. In GDExtension you have to register the properties with a getter and
- setter function or directly implement the ``_get_property_list``, ``_get`` and
- ``_set`` methods of an object (but that goes far beyond the scope of this
- tutorial).
- Lets add a property that allows us to control the amplitude of our wave.
- In our ``gdexample.h`` file we need to add a member variable and getter and setter
- functions:
- .. code-block:: cpp
- ...
- private:
- double time_passed;
- double amplitude;
- public:
- void set_amplitude(const double p_amplitude);
- double get_amplitude() const;
- ...
- In our ``gdexample.cpp`` file we need to make a number of changes, we will only
- show the methods we end up changing, don't remove the lines we're omitting:
- .. code-block:: cpp
- void GDExample::_bind_methods() {
- ClassDB::bind_method(D_METHOD("get_amplitude"), &GDExample::get_amplitude);
- ClassDB::bind_method(D_METHOD("set_amplitude", "p_amplitude"), &GDExample::set_amplitude);
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "amplitude"), "set_amplitude", "get_amplitude");
- }
- GDExample::GDExample() {
- // Initialize any variables here.
- time_passed = 0.0;
- amplitude = 10.0;
- }
- void GDExample::_process(double delta) {
- time_passed += delta;
- Vector2 new_position = Vector2(
- amplitude + (amplitude * sin(time_passed * 2.0)),
- amplitude + (amplitude * cos(time_passed * 1.5))
- );
- set_position(new_position);
- }
- void GDExample::set_amplitude(const double p_amplitude) {
- amplitude = p_amplitude;
- }
- double GDExample::get_amplitude() const {
- return amplitude;
- }
- Once you compile the module with these changes in place, you will see that a
- property has been added to our interface. You can now change this property and
- when you run your project, you will see that our Godot icon travels along a
- larger figure.
- Let's do the same but for the speed of our animation and use a setter and getter
- function. Our ``gdexample.h`` header file again only needs a few more lines of
- code:
- .. code-block:: cpp
- ...
- double amplitude;
- double speed;
- ...
- void _process(double delta) override;
- void set_speed(const double p_speed);
- double get_speed() const;
- ...
- This requires a few more changes to our ``gdexample.cpp`` file, again we're only
- showing the methods that have changed so don't remove anything we're omitting:
- .. code-block:: cpp
- void GDExample::_bind_methods() {
- ...
- ClassDB::bind_method(D_METHOD("get_speed"), &GDExample::get_speed);
- ClassDB::bind_method(D_METHOD("set_speed", "p_speed"), &GDExample::set_speed);
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_speed", "get_speed");
- }
- GDExample::GDExample() {
- time_passed = 0.0;
- amplitude = 10.0;
- speed = 1.0;
- }
- void GDExample::_process(double delta) {
- time_passed += speed * delta;
- Vector2 new_position = Vector2(
- amplitude + (amplitude * sin(time_passed * 2.0)),
- amplitude + (amplitude * cos(time_passed * 1.5))
- );
- set_position(new_position);
- }
- ...
- void GDExample::set_speed(const double p_speed) {
- speed = p_speed;
- }
- double GDExample::get_speed() const {
- return speed;
- }
- Now when the project is compiled, we'll see another property called speed.
- Changing its value will make the animation go faster or slower.
- Furthermore, we added a property range which describes in which range the value can be.
- The first two arguments are the minimum and maximum value and the third is the step size.
- .. note::
- For simplicity, we've only used the hint_range of the property method.
- There are a lot more options to choose from. These can be used to
- further configure how properties are displayed and set on the Godot side.
- Signals
- -------
- Last but not least, signals fully work in GDExtension as well. Having your extension
- react to a signal given out by another object requires you to call ``connect``
- on that object. We can't think of a good example for our wobbling Godot icon, we
- would need to showcase a far more complete example.
- This is the required syntax:
- .. code-block:: cpp
- some_other_node->connect("the_signal", Callable(this, "my_method"));
- To connect our signal ``the_signal`` from some other node with our method
- ``my_method``, we need to provide the ``connect`` method with the name of the signal
- and a ``Callable``. The ``Callable`` holds information about an object on which a method
- can be called. In our case, it associates our current object instance ``this`` with the
- method ``my_method`` of the object. Then the ``connect`` method will add this to the
- observers of ``the_signal``. Whenever ``the_signal`` is now emitted, Godot knows which
- method of which object it needs to call.
- Note that you can only call ``my_method`` if you've previously registered it in
- your ``_bind_methods`` method. Otherwise Godot will not know about the existence
- of ``my_method``.
- To learn more about ``Callable``, check out the class reference here: :ref:`Callable <class_Callable>`.
- Having your object sending out signals is more common. For our wobbling
- Godot icon, we'll do something silly just to show how it works. We're going to
- emit a signal every time a second has passed and pass the new location along.
- In our ``gdexample.h`` header file, we need to define a new member ``time_emit``:
- .. code-block:: cpp
- ...
- double time_passed;
- double time_emit;
- double amplitude;
- ...
- This time, the changes in ``gdexample.cpp`` are more elaborate. First,
- you'll need to set ``time_emit = 0.0;`` in either our ``_init`` method or in our
- constructor. We'll look at the other 2 needed changes one by one.
- In our ``_bind_methods`` method, we need to declare our signal. This is done
- as follows:
- .. code-block:: cpp
- void GDExample::_bind_methods() {
- ...
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed", PROPERTY_HINT_RANGE, "0,20,0.01"), "set_speed", "get_speed");
- ADD_SIGNAL(MethodInfo("position_changed", PropertyInfo(Variant::OBJECT, "node"), PropertyInfo(Variant::VECTOR2, "new_pos")));
- }
- Here, our ``ADD_SIGNAL`` macro can be a single call with a ``MethodInfo`` argument.
- ``MethodInfo``'s first parameter will be the signal's name, and its remaining parameters
- are ``PropertyInfo`` types which describe the essentials of each of the method's parameters.
- ``PropertyInfo`` parameters are defined with the data type of the parameter, and then the name
- that the parameter will have by default.
- So here, we add a signal, with a ``MethodInfo`` which names the signal "position_changed". The
- ``PropertyInfo`` parameters describe two essential arguments, one of type ``Object``, the other
- of type ``Vector2``, respectively named "node" and "new_pos".
- Next, we'll need to change our ``_process`` method:
- .. code-block:: cpp
- void GDExample::_process(double delta) {
- time_passed += speed * delta;
- Vector2 new_position = Vector2(
- amplitude + (amplitude * sin(time_passed * 2.0)),
- amplitude + (amplitude * cos(time_passed * 1.5))
- );
- set_position(new_position);
- time_emit += delta;
- if (time_emit > 1.0) {
- emit_signal("position_changed", this, new_position);
- time_emit = 0.0;
- }
- }
- After a second has passed, we emit our signal and reset our counter. We can add
- our parameter values directly to ``emit_signal``.
- Once the GDExtension library is compiled, we can go into Godot and select our sprite
- node. In the **Node** dock, we can find our new signal and link it up by pressing
- the **Connect** button or double-clicking the signal. We've added a script on
- our main node and implemented our signal like this:
- .. code-block:: gdscript
- extends Node
- func _on_Sprite2D_position_changed(node, new_pos):
- print("The position of " + node.get_class() + " is now " + str(new_pos))
- Every second, we output our position to the console.
- Next steps
- ----------
- We hope the above example showed you the basics. You can
- build upon this example to create full-fledged scripts to control nodes in Godot
- using C++.
|