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. Un clon Git del repositorio godot_headers: estos son los cabeceros C para el API público de Godot expuesto a 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

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

Truco

Si planeas usar Git para tu proyecto en GDNative, puedes añadir godot_headers como un submódulo de Git.

Nota

El depósito Godot Headers tiene ramas distintas. Conforme Godot evoluciona, también lo hace GDNative. Mientras que intentamos conservar la compatibilidad entre cada versión, deberías de compilar tu módulo de GDNative de acuerdo a las cabeceras correspondientes a la rama estable de Godot (p. ej. 3.1), e idealmente la versión de lanzamiento (p. ej. 3.1.1-stable) que usas. Los módulos de GDNative compilados de acuerdo a versiones antiguas de cabeceras de Godot podrían funcionar con versiones nuevas del motor, pero no vice-versa.

La rama master del repositorio godot_headers semantiene a raya con la rama master de Godot y por eso contiene la clase GDNative y unas definiciones de estructuras que permitan trabajar con la última versión de desarrollo.

Si usted quiere escribir un módulo GDNative para una versión estable de Godot, mire las etiquetas Git dispoonibles (con git tags) para uno que iguale su versión del motor. En el repositorio godot_headers, tales etiquetas son prefijadas con godot-, por lo que usted puede, por ejemplo, revisar la etiqueta godot-3.1.1-stable para usarla con Godot 3.1.1. En su repositorio clonado, usted puede:

git checkout godot-3.1.1-stable

Si una etiqueta que iguala su versión estable está ausente por alguna razón, puedes retroceder a la rama estable que lo iguale (p. ej. 3.1), que usted podría también revisar con git checkout 3.1.

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

Abra Godot y cree un nuevo proyecto llamado "simple" junto a su clon de Git godot_headers. Esto va a crear la carpeta simple y el archivo project.godot. Entonces manualmente cree una carpeta src junto a la carpeta simple, y una subcarpeta bin en la carpeta simple.

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.

Entonces informamos a Godot sobre nuestros métodos (bueno, nuestro método en este caso), llamando nativescript_register_method por cada método de nuestra clase. En nuestro caso, get_data únicamente. De nuevo, nuestro primer parámetro es nuestro puntero de referencia. El segundo parámetro de nuevo es el nombre de la clase del objeto que registraremos. El tercero es el nombre de nuestra función por el cual GDScript lo reconocerá. El cuarto es nuestra configuración de atributos (ve el enum godot_method_rpc_mode en godot_headers/nativescript/godot_nativescript.h para leer valores posibles). El quinto y último parámetro es una descripción de qué función llamar cuando el método es llamado.

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