Ejemplo de GDNative C

Introducción

Este tutorial introducira lo mínimo necesario para crear modulos de GDNative. Este debería ser tu inicio en el mundo del GDNative. Entender estos contenidos de este tutorial te ayudara a entender todo lo que vendrá luego de esto.

Before we begin, you can download the source code to the example object we describe below in the GDNative-demos repository.

Este proyecto de ejemplo también contiene un archivo SConstruct que facilita un poco la compilación, pero en este tutorial haremos las cosas a mano para entender el proceso.

GDNative puede ser usado para crear bastantes tipos de adiciones a Godot, usar interfaces tales como PluginScript or ARVRInterfaceGDNative. En este tutorial vamos a ver como crear un módulo NativeScript. NativeScript te permite escribir lógica en C o C++ de manera similar a como escribirias un archivo GDScript. Estaresmos creando el equivalente en C de este GDScript:

extends Reference

var data

func _ready():
    data = "World from GDScript!"

func get_data():
    return data

Los próximos tutoriales se centrarán en los otros tipos de modulos GDNative y explicarán cuándo y cómo usar cada uno.

Pre-requisitos

Antes de comenzar, necesitará algunas cosas:

  1. Un ejecutable de Godot para tu versión objetivo.

  2. Un compilador de C. En Linux, instale gcc o clang mediante su gestor de paquetes. En macOS, puede instalar Xcode desde la App Store de Mac. En Windows, puede usar Visual Studio 2015 o superior, o MinGW-w64.

  3. A Git clone of the godot-headers repository: these are the C headers for Godot's public API exposed to GDNative.

Para esto le sugerimos que cree una carpeta para este proyecto de ejemplo de GDNative, después abra una terminal en esa carpeta y ejecutelo:

git clone https://github.com/godotengine/godot-headers.git --branch=3.4

Esto requiere que descarges los arhivos requeridos dentro de la carpeta.

Truco

If you plan to use Git for your GDNative project, you can also add godot-headers as a Git submodule.

Nota

The godot-headers repository has different branches. As Godot evolves, so does GDNative. While we try to preserve compatibility between version, you should always build your GDNative module against headers matching the Godot stable branch (e.g. 3.4) and ideally actual release (e.g. 3.4.4-stable) that you use. GDNative modules built against older versions of the Godot headers may work with newer versions of the engine, but not the other way around.

The master branch of the godot-headers repository is kept in line with the master branch of Godot and thus contains the GDNative class and structure definitions that will work with the latest development builds.

If you want to write a GDNative module for a stable version of Godot, look at the available Git tags (with git tags) for the one matching your engine version. In the godot-headers repository, such tags are prefixed with godot-, so you can e.g. checkout the godot-3.4.4-stable tag for use with Godot 3.4.4. In your cloned repository, you can do:

git checkout godot-3.4.4-stable

If a tag matching your stable release is missing for any reason, you can fall back to the matching stable branch (e.g. 3.4), which you would also check out with git checkout 3.4.

Si está construyendo Godot desde una fuente con tus propios cambios que afectan GDNative, puede encontrar las clases actualizadas y la definición de la estructura en <godotsource>/modules/gdnative/include

Nuestra fuente C

Comencemos por escribir nuestro código principal. Eventualmente, queremos terminar con una estructura de archivos que se vea similar a esto:

+ <your development folder>
  + godot-headers
    - <lots of files here>
  + simple
    + bin
      - libsimple.dll/so/dylib
      - libsimple.gdnlib
      - simple.gdns
    main.tscn
    project.godot
  + src
    - simple.c

Open up Godot and create a new project called "simple" alongside your godot-headers Git clone. This will create the simple folder and project.godot file. Then manually create a src folder alongside the simple folder, and a bin subfolder in the simple folder.

Comenzaremos echando un vistazo a lo que nuestro archivo simple.c contiene. Por el momento, vamos a crear una fuente singular en C sin una cabecera para simplificar las cosas. Cuando comiences a escribir proyectos más grandes, es recomendable dividir tu proyecto en varios documentos; sin embargo, eso va más allá de la meta de este tutorial.

Veremos el código fuente poco a poco así que todas las partes debajo deberían estar juntas dentro de un gran archivo. Cada sección será explicada a medida que sea añadida.

#include <gdnative_api_struct.gen.h>

#include <string.h>

const godot_gdnative_core_api_struct *api = NULL;
const godot_gdnative_ext_nativescript_api_struct *nativescript_api = NULL;

El código superior incluye una cabecera en referencia a la estructura de la API de GDNative y una cabecera estándar, las cuales usaremos más adelante para operaciones de texto. Después define dos punteros a otras dos estructuras. GDNative incluye una gran repertorio de funciones para enviar llamados de vuelta al ejecutable principal de Godot. Con el fin de que tu módulo tenga acceso a tales funciones, GDNative provee a tu aplicación con una estructura que contiene punteros a todas estas funciones.

Para mantener esta implementación modular y fácilmante extendible, las funciones núcleo están disponibles a través del "núcleo" de la estructura de la API, pero funciones adicionales tienen su propia "estructura de GDNative" que son accesibles entre extensiones.

En nuestro ejemplo, accederemos a una de estas extensiones para ganar acceso a las funciones especificamente necesitadas para NativeScript.

Un archivo NativeScript se comporta como un script cualquiera en Godot. Debido a que la API de NativeScript es de un nivel bastante bajo, esta requiere que la biblioteca especifique varios elementos con mayor detalle que otros sistemas de scripting, como lo es GDScript. Cuando una instancia de NativeScript es creada, un constructor de la biblioteca es llamado. Cuando la misma instancia es eliminada, el destructor correspondiente es ejectuado.

void *simple_constructor(godot_object *p_instance, void *p_method_data);
void simple_destructor(godot_object *p_instance, void *p_method_data, void *p_user_data);
godot_variant simple_get_data(godot_object *p_instance, void *p_method_data,
        void *p_user_data, int p_num_args, godot_variant **p_args);

Estas son declaraciones frontales para las funciones que implementaremos para nuestro objeto. Necesitamos un constructor y un destructor. Adicionalmente, el objeto tendrá un método llamado get_data.

Lo siguiente es el primero de muchos puntos de entrada que Godot llamará cuando nuestra libería dinámica es cargada. Estos métodos están prefijados con godot_``(puedes cambiar esto luego) seguido de su nombre. ``gdnative_init es una función que inicia nuestra libería dinámica. Godot dará un "pointer" a una estructura que contenga varios bits de información que quizás encontremos útiles entre los cuales el "pointer" a nuestras estructuras de API.

Para cualquier estructura adicional de la API necesitamos hacer un bucle a través de nuestro array de extensiones y comprobar el tipo de extensión.

void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options *p_options) {
    api = p_options->api_struct;

    // Now find our extensions.
    for (int i = 0; i < api->num_extensions; i++) {
        switch (api->extensions[i]->type) {
            case GDNATIVE_EXT_NATIVESCRIPT: {
                nativescript_api = (godot_gdnative_ext_nativescript_api_struct *)api->extensions[i];
            }; break;
            default: break;
        }
    }
}

A continuación está gdnative_terminate la cual es llamada antes de que la biblioteca sea descargada. Godot descargará la biblioteca cuando no sea usada por ningún objeto. Aquí puedes ejecutar cualquier aseo que sea necesario, por ahora, vamos a limpiar nuestros punteros a la API.

void GDN_EXPORT godot_gdnative_terminate(godot_gdnative_terminate_options *p_options) {
    api = NULL;
    nativescript_api = NULL;
}

Finalmente, tenemos nativescript_init que es la función más importante que necesitaremos hoy. Esta función será llamada por Godot en el proceso de cargar una biblioteca de GDNative y comunica de vuelta al motor los objetos que hacemos disponibles.

void GDN_EXPORT godot_nativescript_init(void *p_handle) {
    godot_instance_create_func create = { NULL, NULL, NULL };
    create.create_func = &simple_constructor;

    godot_instance_destroy_func destroy = { NULL, NULL, NULL };
    destroy.destroy_func = &simple_destructor;

    nativescript_api->godot_nativescript_register_class(p_handle, "SIMPLE", "Reference",
            create, destroy);

    godot_instance_method get_data = { NULL, NULL, NULL };
    get_data.method = &simple_get_data;

    godot_method_attributes attributes = { GODOT_METHOD_RPC_MODE_DISABLED };

    nativescript_api->godot_nativescript_register_method(p_handle, "SIMPLE", "get_data",
            attributes, get_data);
}

Primero le indicamos al motor cuáles archivos son implementados al llamar nativescript_register_class. El primer parámetro es el puntero de referencia que se nos entrega. El segundo es el nombre de la clase de nuestro objeto. El tercero es el tipo de Objeto del cual «heredamos»; no es herencia real, sino una aproximación suficiente. Finalmente, nuestros parámetros cuatro y cinco son descripciones de nuestro constructor y destructor.

We then tell Godot about our methods (well our one method in this case), by calling nativescript_register_method for each method of our class. In our case, that is just get_data. Our first parameter is yet again our handle pointer. The second is again the name of the object class we're registering. The third is the name of our function as it will be known to GDScript. The fourth is our attributes setting (see godot_method_rpc_mode enum in godot-headers/nativescript/godot_nativescript.h for possible values). The fifth and final parameter is a description of which function to call when the method gets called.

La estructura descriptiva instance_method contiene el puntero de la función como su primer campo. Los otros dos campos en estas estructuras son para especificar datos de usuario por método; el segundo es el campo method_data el cual es pasado en todas las llamadas a funciones bajo el argumento p_method_data. La utilidad de esto es que puedes reusar una función para métodos diferentes en varias clases-script. Si el valor method_data es un puntero a memoria que necesita ser liberada, el tercer campo free_func puede contener un puntero a una función que liberará esa memoria. Esa función de liberación es llamada cuando el script mismo (¡no la instancia!) Es descargado (comúnmente al momento de descargar bibliotecas).

Ahora, comencemos a trabajar en las funciones de nuestro objeto. Primero, definiremos una estructura que usaremos para almacenar la información de miembro de una instancia de nuestra clase GDNative.

typedef struct user_data_struct {
    char data[256];
} user_data_struct;

Después, definiremos nuestro constructor. Todo lo que hacemos en nuestro constructor es asignar memoria para nuestra estructura y llenarla con información. Pon atención a que usamos las funciones de memoria de Godot con el fin de que la memoria sea rastreada, y regresamos el puntero a nuestra nueva estructura. Este puntero funcionará como nuestro identificador de instancia en caso de que varios objetos sean inicializados.

Este puntero será pasado a cualquiera de nuestras funciones relacionadas a nuestro objeto en forma de un parámetro llamado p_user_data, y puede ser utilizado para identificar nuestra instancia y acceder su información interna.

void *simple_constructor(godot_object *p_instance, void *p_method_data) {
    user_data_struct *user_data = api->godot_alloc(sizeof(user_data_struct));
    strcpy(user_data->data, "World from GDNative!");

    return user_data;
}

Nuestro destructor es llamado cuando Godot ha terminado de usar nuestro objeto y liberamos la información interna de nuestra instancia.

void simple_destructor(godot_object *p_instance, void *p_method_data, void *p_user_data) {
    api->godot_free(p_user_data);
}

Finalmente, implementaremos nuestra función get_data. La información siempre es enviada y regresada como variantes, así que para regresar nuestra información (que es una cadena de caracteres), primero tenemos que convertir nuestra cadena de C a un objeto de texto de Godot, para después copiar ese objeto a la variante que regresaremos.

godot_variant simple_get_data(godot_object *p_instance, void *p_method_data,
        void *p_user_data, int p_num_args, godot_variant **p_args) {
    godot_string data;
    godot_variant ret;
    user_data_struct *user_data = (user_data_struct *)p_user_data;

    api->godot_string_new(&data);
    api->godot_string_parse_utf8(&data, user_data->data);
    api->godot_variant_new_string(&ret, &data);
    api->godot_string_destroy(&data);

    return ret;
}

Las cadenas de texto en Godot son distribuidas en la pila de memoria, por lo tanto, tienen un destructor que libera esa memoria. Los destructores son llamados godot_NOMBREDELTIPO_destroy. Cuando una variante se crea con una cadena de texto, hace referencia a la misma; lo que significa que la cadena original puede ser «destruida» para reducir la cuenta de referencias. Si eso no sucede, la memoria de cadenas de texto va a fugarse, ya que la cuenta de referencias jamás será cero y la memoria jamás será liberada. La variante regresada es destruida automáticamente por Godot.

Nota

En operaciones más complicadas puede ser confuso mantener rastro de qué valor necesita ser liberado y cual no. Como regla general: llama godot_NOMBREDELTIPO_destroy cuando sea necesario en lugar del destructor de C++. El destructor de cadenas de texto sería llamado en C++ después de que la variante fue creada, así que lo mismo es necesario en C.

La variante que regresamos es destruida automáticamente por Godot.

Y este es el código fuente entero de nuestro módulo.

Compilando

Ahora necesitamos compilar nuestro código fuente. Como mencionamos, nuestro proyecto de ejemplo en GitHub contiene una configuración de SCons que se encarga del trabajo duro por ti, pero en nuestro tutorial llamaremos los compiladores directamente.

Asumiendo que te estás apegando a la estructura de carpetas sugerida previamente, lo mejor es abrir una sesión de tu terminal en la carpeta src y ejecutar los comandos desde allí. Asegúrate de crear la carpeta bin antes de continuar.

En Linux:

gcc -std=c11 -fPIC -c -I../godot-headers simple.c -o simple.o
gcc -rdynamic -shared simple.o -o ../simple/bin/libsimple.so

En macOS:

clang -std=c11 -fPIC -c -I../godot-headers simple.c -o simple.os
clang -dynamiclib simple.os -o ../simple/bin/libsimple.dylib

En Windows:

cl /Fosimple.obj /c simple.c /nologo -EHsc -DNDEBUG /MD /I. /I..\godot-headers
link /nologo /dll /out:..\simple\bin\libsimple.dll /implib:..\simple\bin\libsimple.lib simple.obj

Nota

En la compilación de Windows también tendrás una biblioteca libsimple.lib. Esta es una biblioteca que se puede compilar en un proyecto para proporcionar acceso a la DLL. La obtenemos como subproducto y no lo necesitamos :) Al exportar el juego para lanzamiento, este archivo será ignorado.

Creando el archivo GDNativeLibrary(.gdnlib)

Con nuestro módulo compilado, ahora tenemos que crear un recurso GDNativeLibrary con la extensión .gdnlib, la cual colocaremos junto con las bibliotecas dinámicas. Este archivo le dice a Godot cuáles bibliotecas dinámicas son parte de nuestro módulo y necesitan ser cargadas por plataforma.

Podemos usar Godot para generar este archivo, así que abre el proyecto «simple» en el editor.

Inicia presionando el botón de crear recurso en el inspector:

../../../_images/new_resource.gif

Y selecciona GDNativeLibrary:

../../../_images/gdnativelibrary_resource.png

Deberías de ver un editor contextual aparecer en el panel inferior. Usa el botón de «Expandir Panel Inferior» en la parte inferior derecha para expandirlo a su altitud completa:

../../../_images/gdnativelibrary_editor.png

Propiedades generales

En el inspector, tienes varias propiedades para controlar la carga de la Biblioteca.

Si Load Once está habilitada, nuestra biblioteca se cargará sólo una vez y cada script individual que use nuestra biblioteca usará la misma información. Cada variable que definas globalmente será accesible por cada instancia del objeto que crees. Si Load Once está deshabilitada, una nueva copia de la biblioteca será cargada a la memoria cada vez que un script acceda a la biblioteca.

Si Singleton está habilitado, nuestra librería se carga automáticamente y se llama a una función llamada godot_gdnative_singleton. Dejaremos eso para otro tutorial.

El prefijo Symbol Prefix trabaja con nuestras funciones principales, tales como godot_ en godot_nativescript_init que vimos previamente. SI usas varias bibliotecas GDNative que quieres conectar estáticamente, tendrás que usar prefijos distintos. De nuevo, este es un tema para enfocarse en un tutorial distinto, sólo es necesario en este momento para el despliegue en iOS, ya que a esta plataforma no le gustan las bibliotecas dinámicas.

Reloadable define si la biblioteca debería de ser recargada cuando el editor pierde y recupera el enfoque, principalmente para encontrar símbolos nuevos o modificados por cualquier cambio hecho a la biblioteca de manera externa.

Librerías de la plataforma

El plugin GDNativeLibrary del editor te permite configurar dos elementos por cada plataforma y arquitectura que planees apoyar.

La columna Dinamyc Library (sección entry en el archivo guardado) nos dice para cada combinación de plataforma y característica qué librería dinámica debe ser cargada. Esto también informa al exportador los ficheros que deben exportarse al exportar a una plataforma específica.

La columna Dependencies (o la sección dependencies) le dice a Godot cuáles otros archivos tienen que ser exportados por plataforma con el fin de que nuestra librería funcione. Imagina que tu módulo GDNative usa otro DLL para implementar funcionalidad de una librería tercera o externa, aquí es dónde alistas tales DLLs.

Para nuestro ejemplo, sólo construimos bibliotecas para Linux, macOS y/o Windows, así que puedes enlazar los campos relevantes haciendo clic en el botón de carpeta. Si construiste las tres bibliotecas, deberías tener algo como:

../../../_images/gdnativelibrary_editor_complete.png

Guardando el recurso

Podemos guardar nuestro recurso GDNativeLibrary como bin/libsimple.gdnlib con el botón Guardar en el Inspector:

../../../_images/gdnativelibrary_save.png

El archivo es guardado en un formato texto y debería tener contenido similar a este:

[general]

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

[entry]

OSX.64="res://bin/libsimple.dylib"
OSX.32="res://bin/libsimple.dylib"
Windows.64="res://bin/libsimple.dll"
X11.64="res://bin/libsimple.so"

[dependencies]

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

Creando el archivo NativeScript (.gdns)

With our .gdnlib file we've told Godot how to load our library, now we need to tell it about our "SIMPLE" object class. We do this by creating a NativeScript resource file with .gdns extension.

De la misma manera que hicimos con nuestro recurso GDNativeLibrary, haz clic en el botón para crear un nuevo recurso en el inspector y selecciona NativeScript:

../../../_images/nativescript_resource.png

The inspector will show a few properties that we need to fill. As Class Name we enter "SIMPLE" which is the object class name that we declared in our C source when calling godot_nativescript_register_class. We also need to select our .gdnlib file by clicking on Library and selecting Load:

../../../_images/nativescript_library.png

Nota

El campo Class Name tiene que estar escrito de la misma manera que fue asignado en godot_nativescript_init al registrar la clase.

Finalmente, haz clic en el icono de guardado y guarda el archivo bajo el nombre bin/simple.gdns:

../../../_images/save_gdns.gif

Es momento de crear nuestra escena. Agrega un nodo de Control a tu escena como raíz y llámalo main. Después, agrégale los nodos hijos Button y Label (Botón y Etiqueta, respectivamente). Posiciónalos en algún lugar agradable de la pantalla y dale un nombre a tu Botón.

../../../_images/c_main_scene_layout.png

Selecciona el nodo de Control y adjúntale un script:

../../../_images/add_main_script.gif

Ahora, conecta la señal pressed del botón a tu script:

../../../_images/connect_button_signal.gif

No olvides guardar tu escena, llámala main.tscn.

Ahora podemos implementar nuestro código de main.gd:

extends Control

# load the Simple library
onready var data = preload("res://bin/simple.gdns").new()

func _on_Button_pressed():
    $Label.text = "Data = " + data.get_data()

Después de todo esto, nuestro proyecto debería funcionar. La primera vez que lo ejecutes Godot te preguntará cuál es tu escena principal, selecciona main.tscn y ya está listo:

../../../_images/c_sample_result.png