カスタムリソース形式のローダー

はじめに

ResourceFormatLoaderは、ファイルアセットを読み込むファクトリインターフェイスです。リソースはプライマリコンテナです。同じファイル パスで再度読み込みが呼び出されると、以前に読み込まれたリソースが参照されます。当然のことながら、読み込まれたリソースはステートレスである必要があります。

このガイドは、読者がC++モジュールとGodotデータ型を作成する方法を知っていることを前提としています。そうでない場合は、このガイド C++のカスタムモジュール を参照してください。

何のために?

  • 多くのファイル形式に対する新しいサポートの追加

  • オーディオフォーマット

  • ビデオフォーマット

  • 機械学習モデル

だめなんですか?

  • ラスター画像

ImageFormatLoaderを使用してイメージを読み込む必要があります。

ResourceFormatLoaderの作成

各ファイル形式は、データコンテナと ResourceFormatLoader で構成されています。

ResourceFormatLoaders are usually simple classes which return all the necessary metadata for supporting new extensions in Godot. The class must return the format name and the extension string.

さらに、ResourceFormatLoadersは load 機能を持つファイルパスをリソースに変換する必要があります。リソースを読み込むには、 load はデータのシリアル化を読み取り、処理する必要があります。

/* 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;
}

考慮事項

一部のライブラリは、IO処理などの特定の一般的なルーチンを定義しない場合があります。したがって、Godot呼び出しの翻訳が必要です。

たとえば、 FileAccess 呼び出しを std::istream に変換するためのコードを次に示します。

#include "core/os/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は ResourceFormatLoaderResourceLoader ハンドラで登録します。ハンドラは 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.instance();
    ResourceLoader::add_resource_format_loader(json_loader);

    json_saver.instance();
    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())