Связывание внешних библиотек

Модули

Пример Summator в Собственные модули на C++ отлично подходит для небольших пользовательских модулей, но что делать, если вы хотите использовать более крупную внешнюю библиотеку? Давайте рассмотрим пример использования Festival — библиотеки синтеза речи (преобразования текста в речь), написанной на C++.

Для связки внешней библиотеки, установить директорию модуля подобно примеру с Summator :

godot/modules/tts/

Далее вам предстоит создать заголовочный файл с классом TTS:

godot/modules/tts/tts.h
#pragma once

#include "core/object/ref_counted.h"

class TTS : public RefCounted {
    GDCLASS(TTS, RefCounted);

protected:
    static void _bind_methods();

public:
    bool say_text(String p_txt);

    TTS();
};

И затем вы добавляете файл cpp.

godot/modules/tts/tts.cpp
#include "tts.h"

#include <festival.h>

bool TTS::say_text(String p_txt) {

    //convert Godot String to Godot CharString to C string
    return festival_say_text(p_txt.ascii().get_data());
}

void TTS::_bind_methods() {

    ClassDB::bind_method(D_METHOD("say_text", "txt"), &TTS::say_text);
}

TTS::TTS() {
    festival_initialize(true, 210000); //not the best way to do it as this should only ever be called once.
}

Также как и ранее, новый класс нуждается в регистрации, так что нужно добавить ещё два файла:

register_types.h
register_types.cpp

Важно

Эти файлы должны находиться в папке верхнего уровня вашего модуля (рядом с файлами SCsub и config.py), чтобы модуль был зарегистрирован правильно.

Эти файлы должны содержать следующее:

godot/modules/tts/register_types.h
void initialize_tts_module(ModuleInitializationLevel p_level);
void uninitialize_tts_module(ModuleInitializationLevel p_level);
/* yes, the word in the middle must be the same as the module folder name */
godot/modules/tts/register_types.cpp
#include "register_types.h"

#include "core/object/class_db.h"
#include "tts.h"

void initialize_tts_module(ModuleInitializationLevel p_level) {
    if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
        return;
    }
    ClassDB::register_class<TTS>();
}

void uninitialize_tts_module(ModuleInitializationLevel p_level) {
    // Nothing to do here in this example.
}

Далее вам необходимо создать файл SCsub, чтобы система сборки скомпилировала этот модуль:

godot/modules/tts/SCsub
Import('env')

env_tts = env.Clone()
env_tts.add_source_files(env.modules_sources, "*.cpp") # Add all cpp files to the build

You'll need to install the external library on your machine to get the .a library files. See the library's official documentation for specific instructions on how to do this for your operating system. We've included the installation commands for Linux below, for reference.

sudo apt-get install festival festival-dev  # Installs festival and speech_tools libraries
apt-cache search festvox-*  # Displays list of voice packages
sudo apt-get install festvox-don festvox-rablpc16k festvox-kallpc16k festvox-kdlpc16k  # Installs voices

Важно

Голоса, используемые Festival (и любые другие потенциальные внешние/сторонние ресурсы), имеют разные лицензии и условия использования; некоторые (если не большинство) из них могут быть проблематичными с Godot, даже если сама библиотека Festival совместима с лицензией MIT. Обязательно ознакомьтесь с лицензиями и условиями использования.

Внешнюю библиотеку также необходимо установить внутри модуля, чтобы исходные файлы были доступны компилятору, а код модуля оставался самодостаточным. Библиотеки festival и speech_tools можно установить из каталога modules/tts/ через git с помощью следующих команд:

git clone https://github.com/festvox/festival
git clone https://github.com/festvox/speech_tools

Если вы не хотите чтобы внешние кодовые файлы репозитория соединились с вашим репозиторием, вы может связаться с ними вместо добавления их как под-модулей (внутри папки modules/tts/), как показано ниже:

git submodule add https://github.com/festvox/festival
git submodule add https://github.com/festvox/speech_tools

Важно

Обратите внимание, что подмодули Git не используются в репозитории Godot. Если вы разрабатываете модуль для интеграции с основным репозиторием Godot, не используйте подмодули. Если ваш модуль не будет интегрирован, вы всегда можете попробовать реализовать внешнюю библиотеку как GDExtension.

Чтобы добавить директории включения(include) для компилятора, вы может добавить их в пути переменных сред:

godot/modules/tts/SCsub
# These paths are relative to /modules/tts/
env_tts.Append(CPPPATH=["speech_tools/include", "festival/src/include"])

# LIBPATH and LIBS need to be set on the real "env" (not the clone)
# to link the specified libraries to the Godot executable.

# This is an absolute path where your .a libraries reside.
# If using a relative path, you must convert it to a
# full path using a utility function, such as `Dir('...').abspath`.
env.Append(LIBPATH=[Dir('libpath').abspath])

# Check with the documentation of the external library to see which library
# files should be included/linked.
env.Append(LIBS=['Festival', 'estools', 'estbase', 'eststring'])

Если вы желаете добавить собственные флаги компиляции когда строите модуль, вы должны сначала клонировать env, чтобы он не добавлял флаги целиком в Godot (что вызовет ошибки). К примеру SCsub с собственными флагами:

godot/modules/tts/SCsub
Import('env')

env_tts = env.Clone()
env_tts.add_source_files(env.modules_sources, "*.cpp")
# Append CCFLAGS flags for both C and C++ code.
env_tts.Append(CCFLAGS=['-O2'])
# If you need to, you can:
# - Append CFLAGS for C code only.
# - Append CXXFLAGS for C++ code only.

В итоге скрипт должен выглядеть так:

godot/modules/tts/festival/
godot/modules/tts/libpath/libestbase.a
godot/modules/tts/libpath/libestools.a
godot/modules/tts/libpath/libeststring.a
godot/modules/tts/libpath/libFestival.a
godot/modules/tts/speech_tools/
godot/modules/tts/config.py
godot/modules/tts/tts.h
godot/modules/tts/tts.cpp
godot/modules/tts/register_types.h
godot/modules/tts/register_types.cpp
godot/modules/tts/SCsub

Использование модуля

Теперь вы можете использовать ваш новый модуль из любого скрипта:

var t = TTS.new()
var script = "Hello world. This is a test!"
var is_spoken = t.say_text(script)
print('is_spoken: ', is_spoken)

А вывод будет is_spoken: True если текст будет произнесён.