Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Per cominciare
Panoramica del flusso di lavoro
Essendo una GDExtension, godot-cpp è più complicato da usare rispetto a GDScript e C#. Se si decide di usarlo, ecco cosa aspettarsi dal proprio flusso di lavoro:
Creare un nuovo progetto godot-cpp (dal template, oppure da zero, come spiegato in seguito).
Sviluppare il tuo codice con il proprio IDE preferito localmente.
Creare e testare il proprio codice con la prima versione compatibile di Godot.
Creare build per tutte le piattaforme che si desidera supportare (ad esempio attraverso GitHub Actions).
Facoltativo: pubblicarla sulla Libreria dei contenuti di Godot.
Progetto d'esempio
Per il proprio primo progetto con godot-cpp, consigliamo di iniziare con questa guida per comprendere la tecnologia alla base di godot-cpp. Una volta completata, è possibile utilizzare il template godot-cpp, che offre una copertura più completa di funzionalità, come una pipeline di GitHub Actions e un utile codice boilerplate SConstruct. Tuttavia, il template non fornisce spiegazioni molto dettagliate, motivo per cui consigliamo di leggere prima questa guida.
Configurare il progetto
Ci sono alcuni prerequisiti necessari:
Un eseguibile di Godot 4.
Un compilatore per C++.
SCons come strumento di compilazione.
Una copia del repository godot-cpp.
Consultare anche Configurazione di un IDE e Compilazione poiché gli strumenti di compilazione sono identici a quelli necessari per compilare Godot dal codice sorgente.
È possibile scaricare il repository godot-cpp da GitHub oppure lasciare che Git lo faccia. Si noti che questo repository ha diversi rami per le diverse versioni di Godot. GDExtension non funzioneranno nelle versioni precedenti di Godot (solo Godot 4 e successive) e viceversa, quindi assicurarsi di scaricare il ramo corretto.
Nota
Per utilizzare GDExtension è necessario utilizzare il ramo godot-cpp corrispondente alla versione di Godot puntata. Ad esempio, se si utilizza Godot 4.1, utilizzare il ramo 4.1. In questo tutorial utilizziamo la versione 4.x, che dovrà essere sostituita con la versione di Godot puntata.
Il ramo master è il ramo di sviluppo che è aggiornato regolarmente per funzionare con il ramo master di Godot.
Avvertimento
Le GDExtension destinate a una versione precedente di Godot dovrebbero funzionare anche nelle versioni minori successive, ma non viceversa. Ad esempio, una GDExtension destinata a Godot 4.2 dovrebbe funzionare perfettamente anche in Godot 4.3, ma una destinata a Godot 4.3 non funzionerà in Godot 4.2.
C'è una sola eccezione a questa regola: le estensioni destinate a Godot 4.0 non funzioneranno con Godot 4.1 e versioni successive (consultare Aggiornamento di GDExtension per 4.1).
Se si utilizza Git per il controllo versione del proprio progetto, si consiglia di aggiungerlo come sottomodulo di Git:
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
Alternativamente, è anche possibile clonarlo nella cartella del progetto:
mkdir gdextension_cpp_example
cd gdextension_cpp_example
git clone -b 4.x https://github.com/godotengine/godot-cpp
Nota
Se si decide di scaricare il repository o di clonarlo nella propria cartella, assicurarsi di mantenere la stessa struttura di cartelle che abbiamo impostato qui. Gran parte del codice che mostreremo qui presuppone che il progetto abbia questa struttura.
Se è stato clonato l'esempio dal link specificato nell'introduzione, i sottomoduli non vengono inizializzati automaticamente. Sarà necessario eseguire i seguenti comandi:
cd gdextension_cpp_example
git submodule update --init
Questo inizializzerà il repository nella cartella del proprio progetto.
Creare una semplice estensione
Ora è il momento di creare una vera e propria estensione. Inizieremo creando un progetto Godot vuoto in cui inseriremo alcuni file.
Aprire Godot e creare un nuovo progetto. Per questo esempio, lo posizioneremo in una cartella chiamata project all'interno della struttura di cartelle della nostra GDExtension.
Nel nostro progetto, creeremo una scena contenente un nodo chiamato "Main" e la salveremo come main.tscn. Ci torneremo più avanti.
Tornando alla cartella principale del modulo GDExtension, creeremo anche una sottocartella chiamata src in cui inseriremo i nostri file sorgente.
Ora si dovrebbero avere le cartelle project, godot-cpp e src nel proprio modulo GDExtension.
La struttura di cartelle dovrebbe ora apparire così:
gdextension_cpp_example/
|
+--project/ # game example/demo to test the extension
|
+--godot-cpp/ # C++ bindings
|
+--src/ # source code of the extension we are building
Nella cartella src, inizieremo creando il file di intestazione per il nodo GDExtension che andremo a creare. Lo chiameremo gdexample.h:
#pragma once
#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;
};
} // namespace godot
Ci sono un paio di cose da notare riguardo a quanto sopra. Includiamo sprite2d.hpp che contiene i binding alla classe Sprite2D. Estenderemo questa classe nel nostro modulo.
Utilizziamo il namespace godot, poiché tutto in GDExtension è definito all'interno di questo namespace.
Poi abbiamo la definizione della nostra classe, che eredita da Sprite2D tramite una classe contenitore. Vedremo alcuni effetti collaterali di questo più avanti. La macro GDCLASS configura alcune cose interne per noi.
Dopodiché, dichiariamo una singola variabile membro chiamata time_passed.
Nel blocco successivo definiamo i nostri metodi: abbiamo il costruttore e il distruttore definiti, ma ci sono altre due funzioni che probabilmente sembrano familiari per alcuni, e un nuovo metodo.
La prima è _bind_methods, una funzione statica che Godot chiamerà per scoprire quali metodi possono essere richiamati e quali proprietà espongono. La seconda è la nostra funzione _process, che funzionerà esattamente come la funzione _process a cui ci si abituati in GDScript.
Implementiamo ora le nostre funzioni creando il file gdexample.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);
}
Questo dovrebbe essere semplice. Stiamo implementando ciascun metodo della nostra classe che abbiamo definito nel nostro file di intestazione.
Notare la nostra funzione _process, che tiene traccia del tempo trascorso e calcola una nuova posizione per il nostro sprite tramite le funzioni seno e coseno.
Ci serve un altro file C++; lo chiameremo register_types.cpp. Il nostro plugin GDExtension può contenere più classi, ognuna con il proprio file di intestazione e sorgente, come abbiamo implementato in GDExample in precedenza. Ora ci serve un piccolo frammento di codice che comunichi a Godot tutte le classi presenti nel nostro plugin GDExtension.
#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();
}
}
Le funzioni initialize_example_module e uninitialize_example_module sono chiamate rispettivamente quando Godot carica la nostra estensione e quando la scarica. Qui ci limitiamo ad analizzare le funzioni nel nostro modulo di binding per inizializzarle, ma potrebbe essere necessario configurare altre cose a seconda delle esigenze. Chiamiamo la macro GDREGISTER_CLASS per ciascuna delle classi presenti nella nostra libreria.
Nota
È possibile trovare informazioni su GDREGISTER_CLASS (e alternative) in Classe Object.
La funzione importante è la terza funzione chiamata example_library_init. Per prima cosa, chiamiamo una funzione nella nostra libreria di binding che crea un oggetto di inizializzazione. Questo oggetto registra le funzioni di inizializzazione e terminazione della GDExtension. Inoltre, imposta il livello di inizializzazione (core, servers, scene, editor, level).
Infine, ci serve il file di intestazione per register_types.cpp chiamato register_types.h.
#pragma once
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
void initialize_example_module(ModuleInitializationLevel p_level);
void uninitialize_example_module(ModuleInitializationLevel p_level);
Compilare l'estensione
Per compilare il progetto, dobbiamo definire come SCons deve compilarlo utilizzando un file SConstruct che fa riferimento a quello presente in godot-cpp. Scriverlo da zero è fuori lo scopo di questo tutorial, ma è possibile scaricare il file SConstruct che abbiamo preparato. Tratteremo un esempio più dettagliato e personalizzabile su come utilizzare questi file di build in un tutorial successivo.
Nota
Questo file SConstruct è stato scritto per essere utilizzato con l'ultima versione master di godot-cpp; potrebbe essere necessario apportare piccole modifiche per utilizzarlo con versioni precedenti oppure fare riferimento al file SConstruct nella documentazione di Godot 4.x.
Una volta scaricato il file SConstruct, inserirlo nella struttura di cartelle GDExtension assieme a godot-cpp, src e project, poi eseguirlo:
scons platform=<platform>
È possibile omettere l'opzione platform se si sta compilando per la piattaforma attualmente in uso. L'elenco delle opzioni platform disponibili dipende dalle dipendenze della piattaforme configurate (usare platform=list per vedere tutte le piattaforme disponibili). Consultare Introduzione al sistema di compilazione per ulteriori dettagli.
Ora la libreria compilata si dovrebbe trovare in project/bin/.
Nota
Qui abbiamo compilato sia godot-cpp sia la nostra libreria gdexample in modalità debug, che è l'impostazione predefinita. Per ottenere build ottimizzate, è consigliabile compilarle utilizzando l'opzione target=template_release.
Utilizzo del modulo GDExtension
Prima di tornare a Godot, dobbiamo creare un altro file nella cartella project/bin/.
Questo file indica a Godot quali librerie dinamiche serve caricare per ciascuna piattaforma e la funzione di ingresso per il modulo. Si chiama gdexample.gdextension.
[configuration]
entry_symbol = "example_library_init"
compatibility_minimum = "4.1"
reloadable = true
[libraries]
macos.debug = "./libgdexample.macos.template_debug.dylib"
macos.release = "./libgdexample.macos.template_release.dylib"
windows.debug.x86_32 = "./gdexample.windows.template_debug.x86_32.dll"
windows.release.x86_32 = "./gdexample.windows.template_release.x86_32.dll"
windows.debug.x86_64 = "./gdexample.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "./gdexample.windows.template_release.x86_64.dll"
linux.debug.x86_64 = "./libgdexample.linux.template_debug.x86_64.so"
linux.release.x86_64 = "./libgdexample.linux.template_release.x86_64.so"
linux.debug.arm64 = "./libgdexample.linux.template_debug.arm64.so"
linux.release.arm64 = "./libgdexample.linux.template_release.arm64.so"
linux.debug.rv64 = "./libgdexample.linux.template_debug.rv64.so"
linux.release.rv64 = "./libgdexample.linux.template_release.rv64.so"
Questo file contiene una sezione configuration che controlla la funzione di ingresso del modulo. Si consiglia anche di impostare la versione minima compatibile di Godot tramite compatibility_minimum, che impedisce alle versioni precedenti di Godot di tentare di caricare l'estensione. Il flag reloadable abilita il ricaricamento automatico dell'estensione dall'editor ogni volta che è ricompilata, senza bisogno di riavviare l'editor. Questo funziona solo se si compila l'estensione in modalità debug (predefinita).
La sezione libraries è la parte più importante: indica a Godot la posizione della libreria dinamica nel filesystem del progetto per ogni piattaforma supportata. Inoltre, quando si esporta il progetto, sarà esportato solo quel file, il che significa che il pacchetto dati non conterrà librerie incompatibili con la piattaforma di destinazione.
Si possono trovare ulteriori informazioni sui file .gdextension in Il file .gdextension.
Ecco un'altra panoramica per verificare la corretta struttura dei file:
gdextension_cpp_example/
|
+--project/ # 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
È ora di tornare a lavorare su Godot. Carichiamo la scena principale che abbiamo creato molto prima all'inizio e aggiungiamo alla scena il nodo GDExample, ora appena disponibile:
Assegneremo il logo di Godot a questo nodo come texture, e disabilireremo la proprietà centered:
Siamo finalmente pronti ad avviare il progetto:
Aggiungere proprietà
GDScript consente di aggiungere proprietà allo script attraverso la parola chiave export. In GDExtension è necessario registrare le proprietà con una funzione getter e una setter oppure implementare direttamente i metodi _get_property_list, _get e _set di un oggetto (ma questo va ben oltre lo scopo di questo tutorial).
Aggiungiamo una proprietà che ci permetta di controllare l'ampiezza della nostra onda.
Nel nostro file gdexample.h dobbiamo aggiungere una variabile membro e le funzioni getter e setter:
...
private:
double time_passed;
double amplitude;
public:
void set_amplitude(const double p_amplitude);
double get_amplitude() const;
...
Nel nostro file gdexample.cpp dobbiamo apportare una serie di modifiche; mostreremo solo i metodi che effettivamente modifichiamo, non rimuovere le righe che omettiamo:
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;
}
Una volta compilato il modulo con queste modifiche, verrà aggiunta una proprietà alla nostra interfaccia. Ora è possibile modificare questa proprietà e, quando viene eseguito il progetto, sarà evidente che l'icona di Godot si sposta lungo una figura più grande.
Facciamo lo stesso, ma per la velocità della nostra animazione, e utilizziamo una funzione setter e una funzione getter. Anche in questo caso, il nostro file di intestazione gdexample.h necessita solo di poche righe di codice in più:
...
double amplitude;
double speed;
...
void _process(double delta) override;
void set_speed(const double p_speed);
double get_speed() const;
...
Ciò richiede qualche altra modifica al nostro file gdexample.cpp; anche in questo caso, mostriamo solo i metodi che sono stati modificati, quindi non rimuovete nulla di ciò che abbiamo omesso:
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;
}
Ora, una volta compilato il progetto, vedremo un'altra proprietà chiamata "speed". Modificandone il valore, l'animazione sarà più veloce o più lenta. Inoltre, abbiamo aggiunto una proprietà "range" che descrive l'intervallo entro il quale il valore può trovarsi. I primi due argomenti sono il valore minimo e massimo, mentre il terzo è la dimensione del passo.
Nota
Per semplicità, abbiamo utilizzato solo il parametro hint_range del metodo della proprietà. Esistono molte altre opzioni tra cui scegliere. È possibile usarle per configurare ulteriormente come vengono visualizzate e impostate le proprietà lato Godot. Puoi trovare maggiori informazioni sulle indicazioni di proprietà qui: @GlobalScope.
Segnali
Infine, ma non meno importante, i segnali funzionano perfettamente anche in GDExtension. Affinché l'estensione reagisca a un segnale emesso da un altro oggetto, bisogna chiamare connect su quell'oggetto. Non ci viene in mente un buon esempio per la nostra icona di Godot oscillante; avremmo bisogno di mostrare un esempio molto più completo.
Questa è la sintassi necessaria:
some_other_node->connect("the_signal", Callable(this, "my_method"));
Per collegare il nostro segnale the_signal proveniente da un altro nodo al nostro metodo my_method, dobbiamo fornire al metodo connect il nome del segnale e un Callable. Il Callable contiene informazioni su un oggetto su cui è possibile chiamare un metodo. Nel nostro caso, associa l'istanza corrente dell'oggetto this al metodo my_method dell'oggetto stesso. Il metodo connect aggiungerà quindi questo oggetto agli osservatori di the_signal. Ora, ogni volta che the_signal viene emesso, Godot saprà quale metodo di quale oggetto deve chiamare.
Si note che si può chiamare my_method solo se è stato precedentemente registrato nel metodo _bind_methods. Altrimenti Godot non saprà che my_method esiste.
Per saperne di più su Callable, consultare la documentazione della classe qui: Callable.
È piuttosto comune che un oggetto invii segnali. Per la nostra icona di Godot oscillante, faremo qualcosa di semplice per mostrare come funziona. Emetteremo un segnale ogni secondo trascorso e passeremo la nuova posizione in giro.
Nel nostro file di intestazione gdexample.h, dobbiamo definire un nuovo membro time_emit:
...
double time_passed;
double time_emit;
double amplitude;
...
Questa volta, le modifiche in gdexample.cpp sono più elaborate. Innanzitutto, sarà necessario impostare time_emit = 0.0; nel nostro metodo _init o nel nostro costruttore. Analizzeremo le altre due modifiche necessarie una per una.
Nel nostro metodo _bind_methods, dobbiamo dichiarare il nostro segnale. Lo facciamo così:
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")));
}
In questo caso, la nostra macro ADD_SIGNAL può essere una singola chiamata con un argomento MethodInfo. Il primo parametro di MethodInfo sarà il nome del segnale, mentre i parametri rimanenti saranno di tipo PropertyInfo, che descrivono l'essenziale di ciascun parametro del metodo. I parametri PropertyInfo sono definiti con il tipo di dati del parametro e il nome che il parametro avrà come predefinito.
Quindi, qui aggiungiamo un segnale, con un MethodInfo che denota il segnale "position_changed". I parametri PropertyInfo descrivono due argomenti essenziali, uno di tipo Object e l'altro di tipo Vector2, denominati rispettivamente "node" e "new_pos".
Successivamente, dovremmo modificare il nostro metodo _process:
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;
}
}
Trascorso un secondo, emettiamo il nostro segnale e ripristiniamo il contatore. Possiamo aggiungere i valori dei parametri direttamente a emit_signal.
Una volta compilata la libreria GDExtension, possiamo aprire Godot e selezionare il nostro nodo sprite. Nel pannello Nodo, possiamo trovare il nostro nuovo segnale e collegarlo premendo il pulsante Connetti o facendo doppio clic sul segnale. Abbiamo aggiunto uno script al nostro nodo principale e implementato il segnale così:
extends Node
func _on_Sprite2D_position_changed(node, new_pos):
print("The position of " + node.get_class() + " is now " + str(new_pos))
Ogni secondo, stampiamo la nostra posizione nella console.
Next steps
Speriamo che l'esempio precedente abbia illustrato le nozioni di base. Si può partire da questo esempio per creare script completi che permettano di controllare i nodi in Godot attraverso il C++!
Invece di basare il proprio progetto sull'esempio di configurazione sopra riportato, consigliamo di ricominciare da capo clonando il template godot-cpp e di basare il proprio progetto su di esso. Offre una copertura migliore di funzionalità, come un'azione di build di GitHub e altro utile boilerplate SConstruct.