自定資源格式載入器

簡介

ResourceFormatLoader 是用來載入檔案資源的工廠介面 (Factory Interface)。資源是主要的容器,當重複載入相同的檔案路徑時,會參照到之前載入的 Resource。當然,載入的資源是無狀態的。

本篇指南假設讀者已瞭解如何建立 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 中的 Core 型別 或受管理的資源可能沒有包含適當的替代品。此時 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 中使用 ResourceLoader 處理常式來註冊 ResourcesFormatLoader 。該處理常式會在 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())