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.

Собственные загрузчики форматов ресурсов

Введение

ResourceFormatLoader это интерфейс фабрики для загрузки файлов ассетов. Ресурсы это первичные контейнеры. Когда загрузка вызывается для того же файла снова, предыдущий загруженный Resource создаст ссылку. Естественно, загруженные ресурсы должны быть stateless.

Это руководство подразумевает что читатель знаком с созданием модулей на C++ и разбирается в типах данных Godot. Если нет, то ознакомьтесь с данным гайдом: doc_custom_modules_in_c++.

Ссылки

Для чего это?

  • Добавление новой поддержки для множества форматов файлов

  • Форматы звука

  • Форматы видео

  • Форматы машинного обучения

Что нет?

  • Растровые изображения

ImageFormatLoader должен использоваться для загрузки изображений.

Ссылки

Создание ResourceFormatLoader

Каждый формат файла состоит из контейнера данных и ResourceFormatLoader.

ResourceFormatLoader'ы - это классы, которые возвращают все необходимые метаданные для поддержки новых расширений в Godot. Класс должен возвращать имя формата и название расширения.

Кроме того, загрузчики Resourceformat должны преобразовывать пути к файлам в ресурсы с помощью функции загрузки. Чтобы загрузить ресурс, загрузка должна читать и записывать сериализацию данных.

resource_loader_json.h
#ifndef RESOURCE_LOADER_JSON_H
#define RESOURCE_LOADER_JSON_H

#include "core/io/resource_loader.h"

class ResourceFormatLoaderJson : public ResourceFormatLoader {
    GDCLASS(ResourceFormatLoaderJson, ResourceFormatLoader);
public:
    virtual RES load(const String &p_path, const String &p_original_path, Error *r_error = NULL);
    virtual void get_recognized_extensions(List<String> *r_extensions) const;
    virtual bool handles_type(const String &p_type) const;
    virtual String get_resource_type(const String &p_path) const;
};
#endif // RESOURCE_LOADER_JSON_H
resource_loader_json.cpp
#include "resource_loader_json.h"

#include "resource_json.h"

RES ResourceFormatLoaderJson::load(const String &p_path, const String &p_original_path, Error *r_error) {
Ref<JsonResource> json = memnew(JsonResource);
    if (r_error) {
        *r_error = OK;
    }
    Error err = json->load_file(p_path);
    return json;
}

void ResourceFormatLoaderJson::get_recognized_extensions(List<String> *r_extensions) const {
    if (!r_extensions->find("json")) {
        r_extensions->push_back("json");
    }
}

String ResourceFormatLoaderJson::get_resource_type(const String &p_path) const {
    return "Resource";
}

bool ResourceFormatLoaderJson::handles_type(const String &p_type) const {
    return ClassDB::is_parent_class(p_type, "Resource");
}

Создание ResourceFormatSaver

Если вы хотите иметь возможность изменять и сохранять ресурс, то вы можете реализовать ResourceFormatSaver:

resource_saver_json.h
#ifndef RESOURCE_SAVER_JSON_H
#define RESOURCE_SAVER_JSON_H

#include "core/io/resource_saver.h"

class ResourceFormatSaverJson : public ResourceFormatSaver {
    GDCLASS(ResourceFormatSaverJson, ResourceFormatSaver);
public:
    virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0);
    virtual bool recognize(const RES &p_resource) const;
    virtual void get_recognized_extensions(const RES &p_resource, List<String> *r_extensions) const;
};
#endif // RESOURCE_SAVER_JSON_H
resource_saver_json.cpp
#include "resource_saver_json.h"

#include "resource_json.h"
#include "scene/resources/resource_format_text.h"

Error ResourceFormatSaverJson::save(const String &p_path, const RES &p_resource, uint32_t p_flags) {
    Ref<JsonResource> json = memnew(JsonResource);
    Error error = json->save_file(p_path, p_resource);
    return error;
}

bool ResourceFormatSaverJson::recognize(const RES &p_resource) const {
    return Object::cast_to<JsonResource>(*p_resource) != NULL;
}

void ResourceFormatSaverJson::get_recognized_extensions(const RES &p_resource, List<String> *r_extensions) const {
    if (Object::cast_to<JsonResource>(*p_resource)) {
        r_extensions->push_back("json");
    }
}

Создание кастомных типов данных

У Godot может не быть подходящей замены в его Основные типы или управляемых ресурсах. Godot нужен новый зарегистрированный тип данных, чтобы понять дополнительные двоичные форматы, такие как модели машинного обучения.

Вот простой пример создания кастомного типа данных:

resource_json.h
#ifndef RESOURCE_JSON_H
#define RESOURCE_JSON_H

#include "core/io/json.h"
#include "core/variant_parser.h"

class JsonResource : public Resource {
    GDCLASS(JsonResource, Resource);

protected:
    static void _bind_methods() {
        ClassDB::bind_method(D_METHOD("set_dict", "dict"), &JsonResource::set_dict);
        ClassDB::bind_method(D_METHOD("get_dict"), &JsonResource::get_dict);

        ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "content"), "set_dict", "get_dict");
    }

private:
    Dictionary content;

public:
    Error load_file(const String &p_path);
    Error save_file(const String &p_path, const RES &p_resource);

    void set_dict(const Dictionary &p_dict);
    Dictionary get_dict();
};
#endif // RESOURCE_JSON_H
resource_json.cpp
#include "resource_json.h"

Error JsonResource::load_file(const String &p_path) {
    Error error;
    FileAccess *file = FileAccess::open(p_path, FileAccess::READ, &error);
    if (error != OK) {
        if (file) {
            file->close();
        }
        return error;
    }

    String json_string = String("");
    while (!file->eof_reached()) {
        json_string += file->get_line();
    }
    file->close();

    String error_string;
    int error_line;
    JSON json;
    Variant result;
    error = json.parse(json_string, result, error_string, error_line);
    if (error != OK) {
        file->close();
        return error;
    }

    content = Dictionary(result);
    return OK;
}

Error JsonResource::save_file(const String &p_path, const RES &p_resource) {
    Error error;
    FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &error);
    if (error != OK) {
        if (file) {
            file->close();
        }
        return error;
    }

    Ref<JsonResource> json_ref = p_resource.get_ref_ptr();
    JSON json;

    file->store_string(json.print(json_ref->get_dict(), "    "));
    file->close();
    return OK;
}

void JsonResource::set_dict(const Dictionary &p_dict) {
    content = p_dict;
}

Dictionary JsonResource::get_dict() {
    return content;
}

Соображения

Some libraries may not define certain common routines such as IO handling. Therefore, Godot call translations are required.

Для примера, вот код для перевода вызовов FileAccess в std::istream.

#include "core/io/file_access.h"

#include <istream>
#include <streambuf>

class GodotFileInStreamBuf : public std::streambuf {

public:
    GodotFileInStreamBuf(FileAccess *fa) {
        _file = fa;
    }
    int underflow() {
        if (_file->eof_reached()) {
            return EOF;
        } else {
            size_t pos = _file->get_position();
            uint8_t ret = _file->get_8();
            _file->seek(pos); // Required since get_8() advances the read head.
            return ret;
        }
    }
    int uflow() {
        return _file->eof_reached() ? EOF : _file->get_8();
    }

private:
    FileAccess *_file;
};

Ссылки

Регистрация нового формата файлов

Godot регистрирует ResourcesFormatLoader с обработчиком` ResourceLoader". Обработчик автоматически выбирает нужный загрузчик при вызове load.

register_types.h
void register_json_types();
void unregister_json_types();
register_types.cpp
#include "register_types.h"

#include "core/class_db.h"
#include "resource_loader_json.h"
#include "resource_saver_json.h"
#include "resource_json.h"

static Ref<ResourceFormatLoaderJson> json_loader;
static Ref<ResourceFormatSaverJson> json_saver;

void register_json_types() {
    ClassDB::register_class<JsonResource>();

    json_loader.instantiate();
    ResourceLoader::add_resource_format_loader(json_loader);

    json_saver.instantiate();
    ResourceSaver::add_resource_format_saver(json_saver);
}

void unregister_json_types() {
    ResourceLoader::remove_resource_format_loader(json_loader);
    json_loader.unref();

    ResourceSaver::remove_resource_format_saver(json_saver);
    json_saver.unref();
}

Ссылки

Загрузка GDScript

Сохраните файл под названием demo.json со следующим содержанием и положите его в корневую папку проекта:

{
  "savefilename": "demo.json",
  "demo": [
    "welcome",
    "to",
    "godot",
    "resource",
    "loaders"
  ]
}

После чего прикрепите этот скрипт к любому узлу:

extends Node

@onready var json_resource = load("res://demo.json")

func _ready():
    print(json_resource.get_dict())