Exemple GDNative C++

Introduction

Ce tutoriel s'appuie sur les informations données dans l'exemple GDNative C, nous vous recommandons donc vivement de le lire en premier.

Les liaisons(bindings) C++ pour GDNative sont construites au-dessus de l'API NativeScript GDNative et fournissent un moyen plus agréable d'"étendre" les nœuds de Godot en utilisant le C++. Cela équivaut à écrire des scripts en GDScript, mais en C++ à la place.

Godot 3.1 a vu l'introduction des ajouts de NativeScript 1.1 qui ont permis à l'équipe GDNative de construire une bibliothèque de liens C++ plus agréable. Ces changements ont maintenant été fusionnés dans la branche master et seront la voie que nous suivrons. Si vous voulez écrire un plugin C++ GDNative qui supporte également Godot 3.0, vous devrez utiliser la branche 3.0 et la syntaxe NativeScript 1.0. Nous allons les montrer côte à côte dans cette article.

Vous pouvez télécharger l'exemple complet que nous allons créer dans ce tutoriel on GitHub.

Mise en place du projet

Il y a quelques prérequis dont vous aurez besoin :

  • un exécutable Godot 3.x,
  • un compilateur C++,
  • SCons comme outil de construction,
  • une copie du dépôt godot-cpp.

Voir aussi Compiling car les outils de build sont identiques à ceux dont vous avez besoin pour compiler Godot à partir de la source.

Vous pouvez télécharger ces dépôts sur GitHub ou laisser Git faire le travail pour vous. Notez que ces dépôts ont maintenant différentes branches pour différentes versions de Godot. Les modules GDNative écrits pour une version antérieure de Godot fonctionneront dans les versions plus récentes (à l'exception d'une rupture de compatibilité dans les interfaces ARVR entre 3.0 et 3.1) mais pas l'inverse, donc assurez-vous de télécharger la bonne branche. Notez également que la version de Godot que vous utilisez pour générer l'api.json devient votre version minimale.

Si vous versionnez votre projet en utilisant Git, il est bon de les ajouter en tant que sous-modules Git :

mkdir gdnative_cpp_example
cd gdnative_cpp_example
git init
git submodule add https://github.com/GodotNativeTools/godot-cpp
cd godot-cpp
git submodule update --init
mkdir gdnative_cpp_example
cd gdnative_cpp_example
git init
git submodule add -b 3.0 https://github.com/GodotNativeTools/godot-cpp
cd godot-cpp
git submodule update --init

Si vous décidez de télécharger simplement les dépôts ou de les cloner dans votre dossier de projet, assurez-vous de garder la disposition du dossier identique à celle décrite ici, car une grande partie du code que nous allons présenter ici suppose que le projet suit cette disposition.

Assurez-vous que vous clonez récursivement pour pull les deux dépôts :

mkdir gdnative_cpp_example
cd gdnative_cpp_example
git clone --recursive https://github.com/GodotNativeTools/godot-cpp
mkdir gdnative_cpp_example
cd gdnative_cpp_example
git clone --recursive -b 3.0 https://github.com/GodotNativeTools/godot-cpp

Note

godot-cpp inclut maintenant godot_headers comme un sous-module imbriqué, si vous les avez téléchargés manuellement, assurez-vous de placer godot_headers dans le dossier godot-cpp.

Vous n'êtes pas obligé de le faire de cette façon, mais nous avons trouvé que c'était plus facile à gérer. Si vous décidez de télécharger simplement les dépôts ou de les cloner dans votre dossier, assurez-vous de conserver la même disposition du dossier que celle que nous avons définie ici, car une grande partie du code que nous allons présenter ici suppose que le projet a cette disposition.

Si vous avez cloné l'exemple à partir du lien indiqué dans l'introduction, les sous-modules ne sont pas automatiquement initialisés. Vous devrez exécuter les commandes suivantes :

cd gdnative_cpp_example
git submodule update --init --recursive

Cela permettra de cloner ces deux dépôts dans votre dossier de projet.

Construire(Building) des liaisons(bindings) C ++

Maintenant que nous avons téléchargé nos prérequis, il est temps de build les liaisons(bindings) C++.

Le dépôt contient une copie des métadonnées pour la version actuelle de Godot, mais si vous devez construire ces liens pour une version plus récente de Godot, il suffit d'appeler l'exécutable Godot :

godot --gdnative-generate-json-api api.json

Placez le fichier api.json résultant dans le dossier du projet et ajoutez use_custom_api_file=yes custom_api_file=../api.json à la commande scons ci-dessous.

Pour générer et compiler les liaisons(bindings), utilisez cette commande (en remplaçant <platform> par windows, linux ou osx selon votre système d'exploitation) :

Pour accélérer la compilation, ajoutez -jN à la fin de la ligne de commande SCons où N est le nombre de threads CPU que vous avez sur votre système. L'exemple ci-dessous utilise 4 threads.

cd godot-cpp
scons platform=<platform> generate_bindings=yes -j4
cd ..

Cette étape prendra un certain temps. Lorsqu'elle sera terminée, vous devriez disposer de bibliothèques statiques pouvant être compilées dans votre projet et stockées dans godot-cpp/bin/.

À un moment donné, des binaires compilés seront disponibles, ce qui rendra cette étape facultative.

Note

Vous devrez peut-être ajouter bits=64 à la commande sous Windows ou Linux. Nous travaillons toujours à une meilleure détection automatique.

Créer un plugin simple

Il est maintenant temps de construire un véritable plugin. Nous commencerons par créer un projet Godot vide dans lequel nous placerons quelques fichiers.

Ouvrez Godot et créez un nouveau projet. Pour cet exemple, nous allons le placer dans un dossier appelé demo à l'intérieur de la structure des dossiers de notre module GDNative.

Dans notre projet de démo, nous allons créer une scène contenant un nœud appelé "Main" et nous l'enregistrerons sous le nom de main.tscn. Nous y reviendrons plus tard.

De retour dans le dossier du module GDNative, nous allons également créer un sous-dossier appelé src dans lequel nous placerons nos fichiers source.

Vous devriez maintenant avoir les répertoires demo, godot-cpp, godot_headers, et src dans votre module GDNative.

Dans le dossier src, nous commencerons par créer notre fichier d'en-tête pour le nœud GDNative que nous allons créer. Nous le nommerons gdexample.h :

#ifndef GDEXAMPLE_H
#define GDEXAMPLE_H

#include <Godot.hpp>
#include <Sprite.hpp>

namespace godot {

class GDExample : public Sprite {
    GODOT_CLASS(GDExample, Sprite)

private:
    float time_passed;

public:
    static void _register_methods();

    GDExample();
    ~GDExample();

    void _init(); // our initializer called by Godot

    void _process(float delta);
};

}

#endif
#ifndef GDEXAMPLE_H
#define GDEXAMPLE_H

#include <Godot.hpp>
#include <Sprite.hpp>

namespace godot {

class GDExample : public godot::GodotScript<Sprite> {
    GODOT_CLASS(GDExample)

private:
    float time_passed;

public:
    static void _register_methods();

    GDExample();
    ~GDExample();

    void _process(float delta);
};

}

#endif

Il y a quelques éléments à noter dans ce qui précède. Nous incluons Godot.hpp qui contient toutes nos définitions de base. Ensuite, nous incluons Sprite.hpp qui contient les liens vers la classe Sprite. Nous allons étendre cette classe dans notre module.

Nous utilisons l'espace de noms godot, puisque tout ce qui est en GDNative est défini dans cet espace de noms.

Ensuite, nous avons notre définition de la classe, qui hérite de notre Sprite par le biais d'une classe conteneur. Nous en verrons les effets secondaires plus tard. La macro GODOT_CLASS met en place quelques éléments internes pour nous.

Après cela, nous déclarons une unique variable membre appelée time_passed.

Dans le bloc suivant, nous définissons nos méthodes, nous avons évidemment défini notre constructeur et notre destructeur, mais il y a deux autres fonctions qui sembleront probablement familières à certains, et une nouvelle méthode.

La première est _register_methods, qui est une fonction statique que Godot appellera pour savoir quelles méthodes peuvent être appelées sur notre NativeScript et quelles propriétés il expose. La seconde est notre fonction _process, qui fonctionnera exactement comme la fonction _process à laquelle vous êtes habitué dans le GDScript. La troisième est notre fonction _init qui est appelée après que Godot ait correctement configuré notre objet. Elle doit exister même si vous n'y placez aucun code.

Implémentons nos fonctions en créant notre fichier gdexample.cpp :

#include "gdexample.h"

using namespace godot;

void GDExample::_register_methods() {
    register_method("_process", &GDExample::_process);
}

GDExample::GDExample() {
}

GDExample::~GDExample() {
    // add your cleanup here
}

void GDExample::_init() {
    // initialize any variables here
    time_passed = 0.0;
}

void GDExample::_process(float 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);
}
#include "gdexample.h"

using namespace godot;

void GDExample::_register_methods() {
    register_method((char *)"_process", &GDExample::_process);
}

GDExample::GDExample() {
    // Initialize any variables here
    time_passed = 0.0;
}

GDExample::~GDExample() {
    // Add your cleanup procedure here
}

void GDExample::_process(float 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)));

    owner->set_position(new_position);
}

Celui-ci devrait être simple. Nous implémentons chaque méthode de notre classe que nous avons définie dans notre fichier d'en-tête. Notez que l'appel register_method doit exposer la méthode _process, sinon Godot ne pourra pas l'utiliser. Cependant, nous n'avons pas à parler à Godot de nos fonctions constructeur, destructeur et _init.

L'autre méthode à noter est notre fonction _process, qui garde simplement la trace du temps écoulé et calcule une nouvelle position pour notre sprite en utilisant une simple fonction sinus et cosinus. Ce qui se démarque, c'est d'appeler owner-> set_position pour appeler l'une des méthodes de construction de notre Sprite. C'est parce que notre classe est une classe conteneur ; owner pointe vers le nœud Sprite réel auquel notre script se rapporte. Dans le prochain NativeScript 1.1, set_position peut être appelé directement sur notre classe.

Il nous faut encore un fichier C++ ; nous le nommerons gdlibrary.cpp. Notre plugin GDNative peut contenir plusieurs NativeScripts, chacun avec son propre en-tête et son propre fichier source, comme nous l'avons fait pour GDExample ci-dessus. Ce dont nous avons besoin maintenant, c'est d'un petit bout de code qui renseigne Godot sur tous les NativeScripts de notre plugin GDNative.

#include "gdexample.h"

extern "C" void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options *o) {
    godot::Godot::gdnative_init(o);
}

extern "C" void GDN_EXPORT godot_gdnative_terminate(godot_gdnative_terminate_options *o) {
    godot::Godot::gdnative_terminate(o);
}

extern "C" void GDN_EXPORT godot_nativescript_init(void *handle) {
    godot::Godot::nativescript_init(handle);

    godot::register_class<godot::GDExample>();
}

Notez que nous n'utilisons pas l'espace de noms godot ici, puisque les trois fonctions implémentées ici doivent être définies sans espace de noms.

Les fonctions godot_gdnative_init et godot_gdnative_terminate sont appelées respectivement quand Godot charge notre plugin et quand il le décharge. Tout ce que nous faisons ici, c'est analyser les fonctions de notre module de liaisons(bindings) pour les initialiser, mais il se peut que vous ayez à configurer d'autres choses en fonction de vos besoins.

La fonction importante est la troisième fonction appelée godot_nativescript_init. Nous appelons d'abord une fonction dans notre bibliothèque de liaisons(bindings) qui fait ses trucs habituels. Ensuite, nous appelons la fonction register_class pour chacune de nos classes dans notre bibliothèque.

Compiler le plugin

Nous ne pouvons pas facilement écrire à la main un fichier SConstruct que SCons utiliserait pour construire(building). Pour cet exemple, il suffit d'utiliser ce fichier SConstruct codé en dur que nous avons préparé. Un exemple plus détaillé et personnalisable sur la façon d'utiliser ces fichiers de construction(build) sera présenté dans un tutoriel ultérieur.

Note

Ce fichier SConstruct a été écrit pour être utilisé avec le dernier godot-cpp master, vous devrez peut-être faire de petites modifications en l'utilisant avec des versions plus anciennes ou vous référer au fichier SConstruct dans la documentation de Godot 3.0.

Une fois que vous avez téléchargé le fichier SConstruct, placez-le dans le dossier de votre module GDNative à côté de godot-cpp, godot_headers et demo, puis exécutez :

scons platform=<platform>

Vous devriez maintenant être en mesure de trouver le module dans demo/bin/<platform>.

Note

Ici, nous avons compilé à la fois godot-cpp et notre bibliothèque gdexample en tant que constructions(builds) de débogage. Pour des builds optimisés, vous devriez les compiler en utilisant le commutateur target=release.

Utilisation du module GDNative

Avant de revenir à Godot, nous devons créer deux autres fichiers dans demo/bin/. Les deux peuvent être créés à l'aide de l'éditeur Godot, mais il peut être plus rapide de les créer directement.

Le premier est un fichier qui permet à Godot de savoir quelles bibliothèques dynamiques doivent être chargées pour chaque plate-forme et s'appelle gdexample.gdnlib.

[general]

singleton=false
load_once=true
symbol_prefix="godot_"
reloadable=false

[entry]

X11.64="res://bin/x11/libgdexample.so"
Windows.64="res://bin/win64/libgdexample.dll"
OSX.64="res://bin/osx/libgdexample.dylib"

[dependencies]

X11.64=[]
Windows.64=[]
OSX.64=[]

Ce fichier contient une section general qui contrôle la façon dont le module est chargé. Il contient également une section de préfixe qui devrait être laissée sur godot_ pour le moment. Si vous changez cela, vous devrez renommer diverses fonctions qui sont utilisées comme points d'entrée. Ceci a été ajouté pour la plateforme iPhone car elle ne permet pas de déployer des bibliothèques dynamiques, pourtant les modules GDNative sont liés de manière statique.

La section entry est la partie importante : elle indique à Godot l'emplacement de la bibliothèque dynamique dans le système de fichiers du projet pour chaque plate-forme supportée. Elle permet également d'exporter juste ce fichier lorsque vous exportez le projet, ce qui signifie que le paquet de données ne contiendra pas de bibliothèques incompatibles avec la plate-forme cible.

Enfin, la section dependencies vous permet de nommer des bibliothèques dynamiques supplémentaires qui devraient également être incluses. Ceci est important lorsque votre plugin GDNative implémente la bibliothèque de quelqu'un d'autre et vous demande de fournir une bibliothèque dynamique tierce avec votre projet.

Si vous double-cliquez sur le fichier gdexample.gdnlib dans Godot, vous verrez qu'il y a beaucoup plus d'options à définir :

../../../_images/gdnative_library.png

Le deuxième fichier que nous devons créer est un fichier utilisé par chaque NativeScript que nous avons ajouté à notre plugin. Nous le nommerons gdexample.gdns pour notre gdexample NativeScript.

[gd_resource type="NativeScript" load_steps=2 format=2]

[ext_resource path="res://bin/gdexample.gdnlib" type="GDNativeLibrary" id=1]

[resource]

resource_name = "gdexample"
class_name = "GDExample"
library = ExtResource( 1 )

Il s'agit d'une ressource Godot standard ; vous pouvez la créer directement dans votre scène, mais l'enregistrer dans un fichier facilite grandement sa réutilisation dans d'autres endroits. Cette ressource pointe vers notre fichier gdnlib, afin que Godot puisse savoir quelle bibliothèque dynamique contient notre NativeScript. Elle définit également le class_name qui identifie le NativeScript de notre plugin que nous voulons utiliser.

Il est temps de revenir dans Godot. Nous chargeons la scène principale que nous avons créée au début et ajoutons maintenant un Sprite à notre scène :

../../../_images/gdnative_cpp_nodes.png

Nous allons assigner le logo Godot en texture à ce sprite, désactiver la propriété centered et glisser notre fichier gdexample.gdns sur la propriété script du sprite :

../../../_images/gdnative_cpp_sprite.png

Nous sommes prêts à lancer le projet :

../../../_images/gdnative_cpp_animated.gif

Ajouter des propriétés

GDScript vous permet d’ajouter des propriétés à votre script en utilisant le mot-clé export. En GDNative il faut enregistrer les propriétés et il y a deux manières de le faire. Vous pouvez soit lier directement à un membre, soit utiliser des fonctions d’accès en lecture (getter) et en écriture (setter).

Note

Il y a une troisième option : tout comme en GDScript vous pouvez implémenter directement les méthodes _get_property_list, _get et _set d’un objet, mais ça dépasse la portée de ce tutoriel.

Nous allons examiner les deux méthodes, en commençant par la liaison directe. Ajoutons une propriété qui nous permette de contrôler l’amplitude de notre vague.

Dans notre fichier gdexample.h, il faut seulement ajouter une variable membre de la manière suivante :

...
private:
    float time_passed;
    float amplitude;
...

Dans notre fichier ``gdexample.cpp`, il faut faire un certain nombre de changements . Nous ne montrerons que les méthodes que nous changerons donc ne supprimez pas les lignes qui n’apparaissent pas ici :

void GDExample::_register_methods() {
    register_method("_process", &GDExample::_process);
    register_property<GDExample, float>("amplitude", &GDExample::amplitude, 10.0);
}

void GDExample::_init() {
    // initialize any variables here
    time_passed = 0.0;
    amplitude = 10.0;
}

void GDExample::_process(float 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::_register_methods() {
    register_method((char *)"_process", &GDExample::_process);
    register_property<GDExample, float>("amplitude", &GDExample::amplitude, 10.0);
}

GDExample::GDExample() {
    // initialize any variables here
    time_passed = 0.0;
    amplitude = 10.0;
}

void GDExample::_process(float delta) {
    time_passed += delta;

    Vector2 new_position = Vector2(
        amplitude + (amplitude * sin(time_passed * 2.0)),
        amplitude + (amplitude * cos(time_passed * 1.5))
    );

    owner->set_position(new_position);
}

Lorsque vous compilerez le module avec ces changements, vous verrez qu’une propriété a été ajoutée à notre interface. Vous pouvez maintenant changer cette propriété et en lançant le projet, vous verrez que notre icône Godot se déplace sur une plus grande zone.

Note

La propriété reloadable dans le ficher gdexample.gdnlib doit être définie à true pour que l’éditeur de Godot remarque automatiquement la propriété nouvellement ajoutée.

Cependant, il vaut mieux utiliser ce réglage avec prudence, en particulier quand des classe d’outils sont utilisées, car l’éditeur pourrait conserver des objets ayant des instances de scripts attachées, et gérées par une bibliothèque GDNative.

Faisons la même chose pour la vitesse de l’animation et utilisons des fonctions d’accès en lecture (getter) et écriture (setter). Le fichier d’en-tête gdexample.h n’a ici encore besoin que de quelques lignes de code en plus :

...
    float amplitude;
    float speed;
...
    void _process(float delta);
    void set_speed(float p_speed);
    float get_speed();
...

Il faut quelques modifications supplémentaires dans notre fichier gdexample.cpp. Là encore nous ne montrerons que les méthodes qui ont changé, donc ne pas supprimer ce que nous ne montrons pas ici :

void GDExample::_register_methods() {
    register_method("_process", &GDExample::_process);
    register_property<GDExample, float>("amplitude", &GDExample::amplitude, 10.0);
    register_property<GDExample, float>("speed", &GDExample::set_speed, &GDExample::get_speed, 1.0);
}

void GDExample::_init() {
    // initialize any variables here
    time_passed = 0.0;
    amplitude = 10.0;
    speed = 1.0;
}

void GDExample::_process(float 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(float p_speed) {
    speed = p_speed;
}

float GDExample::get_speed() {
    return speed;
}
void GDExample::_register_methods() {
    register_method((char *)"_process", &GDExample::_process);
    register_property<GDExample, float>("amplitude", &GDExample::amplitude, 10.0);
    register_property<GDExample, float>("speed", &GDExample::set_speed, &GDExample::get_speed, 1.0);
}

GDExample::GDExample() {
    // initialize any variables here
    time_passed = 0.0;
    amplitude = 10.0;
    speed = 1.0;
}

void GDExample::_process(float delta) {
    time_passed += speed * delta;

    Vector2 new_position = Vector2(
        amplitude + (amplitude * sin(time_passed * 2.0)),
        amplitude + (amplitude * cos(time_passed * 1.5))
    );

    owner->set_position(new_position);
}

void GDExample::set_speed(float p_speed) {
    speed = p_speed;
}

float GDExample::get_speed() {
    return speed;
}

Maintenant, quand le projet est compilé nous verrons une nouvelle propriété appelée speed. En changeant cette valeur, l’animation ira plus vite ou plus lentement.

Pour cet exemple il n’y a aucun avantage évident à utiliser des méthodes d’accès. Ça fait juste plus de code à écrire. Pour un exemple aussi simple que celui-ci, il peut être intéressant d’utiliser un setter si vous voulez faire une action précise quand la variable est modifiée, mais dans beaucoup de cas on peut se contenter de lier la variable.

Les méthodes d’accès deviennent bien plus utiles dans des situations plus complexes où il faut faire des choix en plus en fonction de l’état de vos objets.

Note

Pour simplifier, nous avons omis les paramètres optionnels dans l’appel de méthode register_property<class, type>. Ces paramètres sont rpc_mode, usage, hint et hint_string. Ils peuvent être utilisés pour configurer plus finement la manière dont les propriétés sont affichées et assignées du côté de Godot.

Les compilateurs C++ modernes sont capables de deviner la classe et le type de variable, et vous permettent de laisser tomber la partie <GDExample, float> de la méthode register_property. Cela dit, nous n’avons pas eu que de bonnes expériences avec ça.

Signaux

Pour terminer, les signaux sont eux aussi complètement supportés dans GDNative. Pour qu’un module réagisse à un signal transmis par un autre objet, il faut appeler la méthode connect sur cet objet. Nous ne trouvons pas de bon exemple pour notre icône Godot qui bouge ; pour ça il faudrait montrer un exemple beaucoup plus complet.

Voici néanmoins la syntaxe requise :

some_other_node->connect("the_signal", this, "my_method");
some_other_node->connect("the_signal", owner, "my_method");

Remarquez qu’on ne peut appeler my_method que si on l’a au préalable enregistrée dans la méthode _register_methods.

Il est beaucoup plus courant que les objets envoient des signaux. Pour notre icône Godot oscillante, nous allons faire quelque chose de débile, juste pour montrer comment ça marche. Nous allons émettre un signal à chaque seconde, et passer la nouvelle position.

Dans notre fichier d’en-tête gdexample.h, nous allons seulement définir le nouveau membre time_emit :

...
    float time_passed;
    float time_emit;
    float amplitude;
...

Les changements dans gdexample.cpp sont un peu plus élaborés cette fois-ci. Il faut d’abord régler time_emit = 0.0;, soit dans la méthode _init, soit dans le constructeur. Nous allons regarder les deux autres changements requis l’un après l’autre.

Dans notre méthode _register_methods, nous devons déclarer notre signal de la façon suivante :

void GDExample::_register_methods() {
    register_method("_process", &GDExample::_process);
    register_property<GDExample, float>("amplitude", &GDExample::amplitude, 10.0);
    register_property<GDExample, float>("speed", &GDExample::set_speed, &GDExample::get_speed, 1.0);

    register_signal<GDExample>((char *)"position_changed", "node", GODOT_VARIANT_TYPE_OBJECT, "new_pos", GODOT_VARIANT_TYPE_VECTOR2);
}
void GDExample::_register_methods() {
    register_method((char *)"_process", &GDExample::_process);
    register_property<GDExample, float>("amplitude", &GDExample::amplitude, 10.0);
    register_property<GDExample, float>("speed", &GDExample::set_speed, &GDExample::get_speed, 1.0);

    Dictionary args;
    args[Variant("node")] = Variant(Variant::OBJECT);
    args[Variant("new_pos")] = Variant(Variant::VECTOR2);
    register_signal<GDExample>((char *)"position_changed", args);
}

On voit ici une amélioration appréciable dans la dernière version de godot-cpp, où notre méthode register_signal peut consister en un seul appel, avec en arguments les noms des signaux, puis des paires de valeurs qui spécifient les nom et types de chaque paramètre envoyé en même temps que le signal.

Pour NativeScript 1.0 on construit d’abord un dictionnaire, dans lequel on dit à Godot les types d’arguments qu’on passe au signal, puis on l’enregistre.

Nous allons ensuite changer notre méthode _process :

void GDExample::_process(float 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;
    }
}
void GDExample::_process(float delta) {
    time_passed += speed * delta;

    Vector2 new_position = Vector2(
        amplitude + (amplitude * sin(time_passed * 2.0)),
        amplitude + (amplitude * cos(time_passed * 1.5))
    );

    owner->set_position(new_position);

    time_emit += delta;
    if (time_emit > 1.0) {
        Array args;
        args.push_back(Variant(owner));
        args.push_back(Variant(new_position));
        owner->emit_signal("position_changed", args);

        time_emit = 0.0;
    }
}

Après qu’une seconde a passé, nous émettons notre signal et remettons le compteur à zéro. À nouveau, dans la nouvelle version de godot-cpp nous pouvons ajouter notre paramètre directement dans emit_signal. En NativeScript 1.0 nous construisons d’abord un tableau de valeurs, puis nous appelons emit_signal.

Après avoir compilé, nous pouvons aller dans Godot et sélectionner notre nœud de sprite. Nous trouverons dans l’onglet Node notre nouveau signal, et nous le connecterons en appuyant sur connect. Nous avons ajouté un script sur notre nœud principal, et implémenté notre signal comme ceci :

extends Node

func _on_Sprite_position_changed(node, new_pos):
    print("The position of " + node.name + " is now " + str(new_pos))

À chaque seconde, nous affichons simplement notre position dans la console.

NativeScript 1.1 vs NativeScript 1.0

Dans l’exemple ci-dessus, il ne semble pas jusque-là y avoir une grande différence entre l’ancienne et la nouvelle syntaxes. La classe est définie de manière légèrement différente, et nous n’utilisons plus le membre owner pour appeler les méthodes du côté Godot de notre objet. Beaucoup des améliorations se cachent en fait en coulisses.

Cet exemple ne traite que de variables simples et de méthodes simples. NativeScript 1.1 révèle vraiment son intérêt quand on commence à passer des références à d’autres objets, ou quand on appelle des méthodes qui nécessitent des paramètres plus complexes.

La suite

Ce qui précède était un simple exemple, mais nous espérons qu’il vous ait montré les bases. Vous pouvez partir de là pour créer des scripts complets afin de contrôler des nœuds dans Godot, en utilisant C++.

Vous devriez pouvoir éditer et recompiler le plugin alors que l’éditeur Godot est ouvert ; relancez simplement le projet après que la bibliothèque a fini de compiler.