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.

Primeiros passos

Visão geral do fluxo de trabalho

Como uma GDExtension, o godot-cpp é mais complicado de usar do que o GDScript e o C#. Se decidir trabalhar com ele, eis o que esperar do seu fluxo de trabalho:

  • Crie um novo projeto godot-cpp (a partir do modelo (template), ou do zero, conforme explicado abaixo).

  • Desenvolva seu código localmente com a sua IDE favorita.

  • Compile e teste seu código com a versão compatível mais antiga do Godot.

  • Crie compilações para todas as plataformas que deseja suportar (ex.: usando GitHub Actions).

  • Optional: Publish on the Godot Asset Store.

Exemplo de projeto

Para o seu primeiro projeto godot-cpp, recomendamos começar por este guia para entender a tecnologia envolvida no godot-cpp. Depois de terminar, você pode usar o modelo do godot-cpp, que possui uma cobertura melhor de recursos, como uma pipeline do GitHub Actions e códigos padrão (boilerplate) úteis para o SConstruct. No entanto, o modelo não se explica com um nível alto de detalhes, e é por isso que recomendamos passar por este guia primeiro.

Configurando o projeto

Existem alguns pré-requisitos que você precisará:

  • Um executável do Godot 4.

  • Um compilador C++.

  • SCons como ferramenta de compilação.

  • Uma cópia do repositório godot-cpp.

Veja também Configuring an IDE e Compiling, já que as ferramentas de compilação são idênticas às que você precisa para compilar o Godot a partir do código-fonte.

Você pode baixar o repositório do godot-cpp do GitHub ou deixar que o Git faça o trabalho por você. Note que este repositório possui ramificações (branches) diferentes para versões diferentes do Godot. GDExtensions não funcionarão em versões mais antigas do Godot (apenas no Godot 4 ou superior) e vice-versa, portanto, certifique-se de baixar a ramificação correta.

Nota

Para usar a GDExtension, você precisa usar a ramificação do godot-cpp que corresponda à versão do Godot que você tem como alvo. Por exemplo, se o seu alvo é o Godot 4.1, use a ramificação 4.1. Ao longo deste tutorial, usaremos 4.x, que precisará ser substituída pela versão do Godot que você está visando.

A ramificação master é a ramificação de desenvolvimento que é atualizada regularmente para funcionar com a ramificação master do Godot.

Aviso

GDExtensions destinadas a uma versão anterior do Godot devem funcionar em versões menores (minor) posteriores, mas não o contrário. Por exemplo, uma GDExtension destinada ao Godot 4.2 deve funcionar perfeitamente no Godot 4.3, mas uma destinada ao Godot 4.3 não funcionará no Godot 4.2.

Há uma exceção a isso: extensões destinadas ao Godot 4.0 não funcionarão com o Godot 4.1 e posteriores (veja Atualizando seu GDExtension para 4.1).

Se você estiver controlando a versão do seu projeto usando o Git, é recomendado adicioná-lo como um submódulo do 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, você também pode cloná-lo para a pasta do projeto:

mkdir gdextension_cpp_example
cd gdextension_cpp_example
git clone -b 4.x https://github.com/godotengine/godot-cpp

Nota

Se você decidir baixar o repositório ou cloná-lo em sua pasta, certifique-se de manter a mesma estrutura de pastas que configuramos aqui. Grande parte do código que apresentaremos aqui pressupõe que o projeto tenha essa estrutura.

Se você clonou o exemplo a partir do link especificado na introdução, os submódulos não são inicializados automaticamente. Você precisará executar os seguintes comandos:

cd gdextension_cpp_example
git submodule update --init

Isso inicializará o repositório na pasta do seu projeto.

Criando um plugin simples

Agora é hora de construir um plugin de verdade. Começaremos criando um projeto vazio do Godot no qual colocaremos alguns arquivos.

Abra o Godot e crie um novo projeto. Para este exemplo, nós o colocaremos em uma pasta chamada project dentro da estrutura de pastas da nossa GDExtension.

Em nosso projeto, criaremos uma cena contendo um nó chamado "Main" e a salvaremos como main.tscn. Voltaremos a isso mais tarde.

De volta à pasta do módulo GDExtension de nível superior, também vamos criar uma subpasta chamada src na qual colocaremos nossos arquivos de código-fonte.

Agora você deve ter os diretórios project, godot-cpp e src em seu módulo GDExtension.

Sua estrutura de pastas agora deve estar assim:

gdextension_cpp_example/
|
+--project/                  # game example/demo to test the extension
|
+--godot-cpp/             # C++ bindings
|
+--src/                   # source code of the extension we are building

Na pasta src, começaremos criando o nosso arquivo de cabeçalho (header file) para o nó GDExtension que vamos criar. Nós o nomearemos como gdexample.h:

gdextension_cpp_example/src/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

Há algumas coisas a serem observadas no código acima. Incluímos o arquivo sprite2d.hpp, que contém as vinculações para a classe Sprite2D. Estaremos estendendo essa classe em nosso módulo.

Estamos usando o namespace godot, já que tudo na GDExtension é definido dentro desse namespace.

Em seguida, temos a definição da nossa classe, que herda do nosso Sprite2D por meio de uma classe contêiner. Veremos alguns efeitos colaterais disso mais adiante. A macro GDCLASS configura algumas coisas internas para nós.

Depois disso, declaramos uma única variável-membro chamada time_passed.

No próximo bloco, estamos definindo nossos métodos; temos nosso construtor e destrutor definidos, mas há duas outras funções que provavelmente parecerão familiares para alguns, e um método novo.

A primeira é _bind_methods, que é uma função estática que o Godot chamará para descobrir quais métodos podem ser chamados e quais propriedades a classe expõe. A segunda é a nossa função _process, que funcionará exatamente da mesma forma que a função _process com a qual você está acostumado no GDScript.

Vamos implementar nossas funções criando o nosso arquivo gdexample.cpp:

gdextension_cpp_example/src/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);
}

Este deve ser direto. Estamos implementando cada método da nossa classe que definimos em nosso arquivo de cabeçalho.

Observe a nossa função _process, que acompanha quanto tempo se passou e calcula uma nova posição para o nosso sprite usando funções de seno e cosseno.

Precisamos de mais um arquivo C++; nós o nomearemos como register_types.cpp. Nosso plugin GDExtension pode conter múltiplas classes, cada uma com seu próprio arquivo de cabeçalho e código-fonte, assim como implementamos a classe GDExample acima. O que precisamos agora é de um pequeno trecho de código que informe ao Godot sobre todas as classes presentes no nosso plugin GDExtension.

gdextension_cpp_example/src/register_types.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();
}
}

As funções initialize_example_module e uninitialize_example_module são chamadas, respectivamente, quando o Godot carrega o nosso plugin e quando o descarrega. Tudo o que estamos fazendo aqui é percorrer as funções em nosso módulo de vinculações para inicializá-las, mas você pode precisar configurar mais coisas dependendo das suas necessidades. Chamamos a macro GDREGISTER_CLASS para cada uma das nossas classes em nossa biblioteca.

Nota

Você pode encontrar informações sobre a macro GDREGISTER_CLASS (e alternativas) em Classe de objeto.

A função importante é a terceira função, chamada example_library_init. Primeiro chamamos uma função em nossa biblioteca de vinculações que cria um objeto de inicialização. Esse objeto registra as funções de inicialização e terminação da GDExtension. Além disso, ele define o nível de inicialização (core, servers, scene, editor, level).

Por fim, precisamos do arquivo de cabeçalho para o register_types.cpp, chamado register_types.h.

gdextension_cpp_example/src/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);

Compilando o plugin

To compile the project we need to define how SCons using should compile it using an SConstruct file which references the one in godot-cpp. Writing it from scratch is outside the scope of this tutorial, but you can download the SConstruct file we prepared. We'll cover a more customizable, detailed example on how to use these build files in a subsequent tutorial.

Nota

Este arquivo SConstruct foi escrito para ser usado com a versão master mais recente do godot-cpp; você pode precisar fazer pequenas alterações ao usá-lo com versões mais antigas ou consultar o arquivo SConstruct na documentação do Godot 4.x.

Depois de baixar o arquivo SConstruct, coloque-o na estrutura de pastas da sua GDExtension junto com as pastas godot-cpp, src e project, e então execute:

scons platform=<platform>

Você pode omitir a opção platform se estiver compilando para a plataforma que está usando no momento. A lista de opções de platform disponíveis depende de quais dependências de plataforma estão configuradas (use platform=list para ver todas as plataformas disponíveis). Veja Introdução ao sistema de compilação para mais detalhes.

Agora você deve conseguir encontrar a biblioteca compilada em project/bin/.

Nota

Aqui, compilamos tanto o godot-cpp quanto a nossa biblioteca gdexample como compilações de depuração (debug), que é o padrão. Para compilações otimizadas, você deve compilá-los usando a opção target=template_release.

Usando o módulo GDExtension

Antes de voltarmos para o Godot, precisamos criar mais um arquivo em project/bin/.

Esse arquivo informa ao Godot quais bibliotecas dinâmicas devem ser carregadas para cada plataforma e a função de entrada para o módulo. Ele se chama 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"

Este arquivo contém uma seção configuration que controla a função de entrada do módulo. Você também deve definir a versão mínima compatível do Godot com compatibility_minimum, o que impede que versões mais antigas do Godot tentem carregar a sua extensão. A flag reloadable permite a recarga automática da sua extensão pelo editor toda vez que você a recompila, sem a necessidade de reiniciar o editor. Isso funciona apenas se você compilar a sua extensão em modo de depuração (padrão).

A seção libraries é a parte importante: ela indica ao Godot a localização da biblioteca dinâmica no sistema de arquivos do projeto para cada plataforma suportada. Isso também fará com que apenas esse arquivo seja exportado quando você exportar o projeto, o que significa que o pacote de dados não conterá bibliotecas incompatíveis com a plataforma de destino.

Você pode aprender mais sobre arquivos .gdextension em O arquivo .gdextension.

Aqui está outra visão geral para verificar a estrutura correta dos arquivos:

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

Hora de voltar para o Godot. Carregamos a cena principal que criamos lá no início e agora adicionamos um nó GDExample recém-disponível à cena:

../../../_images/gdextension_cpp_nodes.webp

Vamos atribuir o logotipo do Godot a este nó como nossa textura e desativar a propriedade centered:

../../../_images/gdextension_cpp_sprite.webp

Estamos finalmente prontos para rodar o projeto:

Adicionando propriedades

O GDScript permite adicionar propriedades ao seu script usando a palavra-chave export. Na GDExtension, você precisa registrar as propriedades com uma função getter e setter ou implementar diretamente os métodos _get_property_list, _get e _set de um objeto (mas isso vai muito além do escopo deste tutorial).

Vamos adicionar uma propriedade que nos permita controlar a amplitude da nossa onda.

Em nosso arquivo gdexample.h, precisamos adicionar uma variável de membro e funções getter e setter:

...
private:
    double time_passed;
    double amplitude;

public:
    void set_amplitude(const double p_amplitude);
    double get_amplitude() const;
...

Em nosso arquivo gdexample.cpp, precisamos fazer uma série de alterações; mostraremos apenas os métodos que acabamos alterando, não remova as linhas que estamos omitindo:

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;
}

Assim que você compilar o módulo com essas alterações implementadas, verá que uma propriedade foi adicionada à nossa interface. Agora você pode alterar essa propriedade e, quando rodar o seu projeto, verá que o nosso ícone do Godot se desloca ao longo de uma trajetória maior.

Vamos fazer o mesmo, mas para a velocidade da nossa animação, utilizando uma função setter e getter. Nosso arquivo de cabeçalho gdexample.h novamente precisa de apenas mais algumas linhas de código:

...
    double amplitude;
    double speed;
...
    void _process(double delta) override;
    void set_speed(const double p_speed);
    double get_speed() const;
...

Isso exige mais algumas alterações no nosso arquivo gdexample.cpp; novamente, estamos mostrando apenas os métodos que mudaram, portanto, não remova nada do que omitimos:

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;
}

Agora, quando o projeto for compilado, veremos outra propriedade chamada speed. Alterar o seu valor fará com que a animação vá mais rápido ou mais devagar. Além disso, adicionamos uma dica de intervalo (property range) que descreve em qual intervalo o valor pode estar. Os dois primeiros argumentos são o valor mínimo e máximo, e o terceiro é o tamanho do passo (step).

Nota

Por simplicidade, usamos apenas o hint_range do método de propriedade. Há muito mais opções disponíveis para escolher. Elas podem ser usadas para configurar ainda mais como as propriedades são exibidas e definidas no lado do Godot. Você pode encontrar mais informações sobre as dicas de propriedades aqui: @GlobalScope.

Sinais

Por último, mas não menos importante, os sinais também funcionam perfeitamente na GDExtension. Fazer com que a sua extensão reaja a um sinal emitido por outro objeto exige que você chame connect nesse objeto. Não conseguimos pensar em um bom exemplo para o nosso ícone oscilante do Godot; precisaríamos apresentar um exemplo muito mais completo.

Esta é a sintaxe necessária:

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

Para conectar o nosso sinal the_signal vindo de algum outro nó com o nosso método my_method, precisamos fornecer ao método connect o nome do sinal e um Callable. O Callable guarda informações sobre um objeto no qual um método pode ser chamado. No nosso caso, ele associa a nossa instância de objeto atual this ao método my_method do objeto. Em seguida, o método connect adicionará isso aos observadores de the_signal. Sempre que the_signal for emitido, o Godot saberá qual método de qual objeto ele precisa chamar.

Note que você só pode chamar my_method se o tiver registrado previamente no seu método _bind_methods. Caso contrário, o Godot não saberá da existência de my_method.

Para saber mais sobre Callable, confira a referência de classe aqui: Callable.

Ter o seu objeto enviando sinais é mais comum. Para o nosso ícone oscilante do Godot, faremos algo bobo apenas para mostrar como funciona. Vamos emitir um sinal toda vez que um segundo se passar e passar a nova localização junto.

Em nosso arquivo de cabeçalho gdexample.h, precisamos definir um novo membro time_emit:

...
    double time_passed;
    double time_emit;
    double amplitude;
...

Desta vez, as alterações em gdexample.cpp são mais elaboradas. Primeiro, você precisará definir time_emit = 0.0; no nosso método _init ou no nosso construtor. Vamos analisar as outras 2 alterações necessárias uma por uma.

Em nosso método _bind_methods, precisamos declarar o nosso sinal. Isso é feito da seguinte forma:

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")));
}

Aqui, nossa macro ADD_SIGNAL pode ser uma única chamada com um argumento MethodInfo. O primeiro parâmetro do MethodInfo será o nome do sinal, e os parâmetros restantes são tipos PropertyInfo que descrevem os pontos essenciais de cada um dos parâmetros do método. Os parâmetros PropertyInfo são definidos com o tipo de dados do parâmetro e, em seguida, o nome que o parâmetro terá por padrão.

Então aqui, adicionamos um sinal com um MethodInfo que nomeia o sinal como "position_changed". Os parâmetros PropertyInfo descrevem dois argumentos essenciais, um do tipo Object e o outro do tipo Vector2, respectivamente chamados de "node" e "new_pos".

Em seguida, precisaremos alterar o nosso método _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;
    }
}

Depois que um segundo se passou, emitimos o nosso sinal e reiniciamos o nosso contador. Podemos adicionar os valores dos nossos parâmetros diretamente em emit_signal.

Assim que a biblioteca GDExtension for compilada, podemos ir para o Godot e selecionar o nosso nó de sprite. Na aba Node (Nó), podemos encontrar o nosso novo sinal e vinculá-lo pressionando o botão Connect (Conectar) ou clicando duas vezes no sinal. Adicionamos um script no nosso nó principal e implementamos o nosso sinal assim:

extends Node

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

A cada segundo, exibimos a nossa posição no console.

Próximos passos

Esperamos que o exemplo acima tenha lhe mostrado o básico. Você pode construir a partir deste exemplo para criar scripts completos para controlar nós no Godot usando C++!

Em vez de basear o seu projeto na configuração do exemplo acima, recomendamos recomeçar agora clonando o modelo do godot-cpp e basear o seu projeto nele. Ele possui uma cobertura melhor de recursos, como uma action de compilação do GitHub e códigos padrão (boilerplate) úteis adicionais para o SConstruct.