I'm messing around with Godot 4 and GDExtension, trying to hook up some C++ code. Right now, I'm a bit stuck because I can't find any good examples on how to expose not just classes, but functions, structs, and type aliases. Here's what I got:
register_types.cpp:
#include "register_types.h"
#include "../godot-cpp/gdextension/gdextension_interface.h"
#include "../godot-cpp/include/godot_cpp/core/class_db.hpp"
#include "../godot-cpp/include/godot_cpp/core/defs.hpp"
#include "../godot-cpp/include/godot_cpp/godot.hpp"
#include "cards.hpp"
void initialize_gdextension_types(godot::ModuleInitializationLevel p_level) {
if (p_level != godot::MODULE_INITIALIZATION_LEVEL_CORE) {
return;
}
// TODO: register my functions and structs
// TODO: figure out how to do it
godot::ObjectDB::register_object<Error>(); // <- no member named 'register_object' :)
}
void uninitialize_gdextension_types(godot::ModuleInitializationLevel p_level) {
if (p_level != godot::MODULE_INITIALIZATION_LEVEL_CORE) {
return;
}
}
extern "C" {
// Initialization
// NOTE: I don't think we need to touch anything here
GDExtensionBool GDE_EXPORT
poker_utils_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address,
GDExtensionClassLibraryPtr p_library,
GDExtensionInitialization *r_initialization) {
godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library,
r_initialization);
init_obj.register_initializer(initialize_gdextension_types);
init_obj.register_terminator(uninitialize_gdextension_types);
init_obj.set_minimum_library_initialization_level(
godot::MODULE_INITIALIZATION_LEVEL_CORE);
return init_obj.init();
}
}
cards.hpp:
#ifndef CARDS_H_
#define CARDS_H_
#include <string>
#include <variant>
struct Error {
std::string message;
};
using CardIndexResult = std::variant<int, Error>;
CardIndexResult getCardIndex(const std::string &card);
#endif // CARDS_H_
I tried the godot::ObjectDB::register_object<Error>();
thing, but no dice. So, how do I:
Error
struct into Godot?getCardIndex
callable from Godot scripts?CardIndexResult
type alias?I was able to compile the following:
// register_types.cpp
#include "register_types.h"
#include "../godot-cpp/gdextension/gdextension_interface.h"
#include "../godot-cpp/include/godot_cpp/core/class_db.hpp"
#include "../godot-cpp/include/godot_cpp/core/defs.hpp"
#include "../godot-cpp/include/godot_cpp/godot.hpp"
#include "deck.hpp"
void initialize_gdextension_types(godot::ModuleInitializationLevel p_level) {
if (p_level != godot::MODULE_INITIALIZATION_LEVEL_CORE) {
return;
}
godot::ClassDB::bind_static_method("@GlobalScope",
godot::D_METHOD("get_deck"), &getDeck);
}
void uninitialize_gdextension_types(godot::ModuleInitializationLevel p_level) {
if (p_level != godot::MODULE_INITIALIZATION_LEVEL_CORE) {
return;
}
}
extern "C" {
// Initialization
// NOTE: I don't think we need to touch anything here
GDExtensionBool GDE_EXPORT
poker_utils_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address,
GDExtensionClassLibraryPtr p_library,
GDExtensionInitialization *r_initialization) {
godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library,
r_initialization);
init_obj.register_initializer(initialize_gdextension_types);
init_obj.register_terminator(uninitialize_gdextension_types);
init_obj.set_minimum_library_initialization_level(
godot::MODULE_INITIALIZATION_LEVEL_CORE);
return init_obj.init();
}
}
And this is the defintion of the deck.hpp
file:
#ifndef GODOT_PLUGIN_DECK_H_
#define GODOT_PLUGIN_DECK_H_
#include "../godot-cpp/include/godot_cpp/godot.hpp"
#include "../godot-cpp/include/godot_cpp/templates/vector.hpp"
#include "../godot-cpp/include/godot_cpp/variant/variant.hpp"
godot::PackedStringArray getDeck();
#endif // GODOT_PLUGIN_DECK_H_
That function is a encapsulation of my original function, see it here:
// deck.cpp
#include "deck.hpp"
#include "../src/deck.hpp"
godot::PackedStringArray getDeck() {
godot::PackedStringArray cards;
auto deck = poker::getDeck();
for (const auto &card : deck) {
cards.push_back(godot::String(card.c_str()));
}
return cards;
};
get_deck
isn't showing at @GlobalScope
The extension is being detected by Godot and loaded, after restarting the editor I went right into the documentation of @GlobalScope
but get_deck
isn't showing. IDK what I'm doing wrong at this point.
So I ended making a class to encapsulate my functions, on register_types.cpp
at the initialize_gdextension_types
function I put godot::ClassDB::register_class<Poker>()
. The class itself takes care of registering its methods:
// poker.cpp
void Poker::_bind_methods() {
godot::ClassDB::bind_static_method("Poker", godot::D_METHOD("get_deck"),
&Poker::getDeck);
godot::ClassDB::bind_static_method(
"Poker", godot::D_METHOD("get_deck_shuffled"), &Poker::getDeckShuffled);
}
Now Godot can see the class and use its methods!
You're on the right track, but for your custom class or struct to be handled by Godot, it has to inherit from Godot's Object
type (or any other class), since you'll need a few specific member functions for the interaction with the engine (including scripting).
To do this, you have to use inheritance and an extra macro inserting the additional members:
class Error: public godot::Object {
GDCLASS(Error, godot::Object);
}
This should allow you to register the class in your initialization function and then pass it around, and create/destroy instances of it:
godot::ClassDB::register_class<Error>();
If your custom class needs additional members, you'd add a protected
member static void _bind_methods();
to your Error
class, which is then able to use similar ClassDB
methods to add any callbacks or properties you want to expose.
To bind a global function that's not a member, you can use ``, something like this:
godot::ClassDB::bind_static_method("ParentClassName", "getCardIndex", &getCardIndex, godot::String());
I'm not sure how to do it with global scope, I think you can pass "@GlobalScope"
or so, but I've never done it before and can only type from memory right now, so take this note with a grain of salt.