自定义资源格式加载器

简介

ResourceFormatLoader 是一个用来加载文件资源的工厂接口。资源是基本容器。当再次在同一文件路径上调用load时,将引用先前加载的Resource。自然,加载的资源必须是无状态的。

本指南假定读者知道如何创建C++模块和Godot数据类型。如果没有,请参考指南 自定义C++模块

可以做什么?

  • 添加对多种文件格式的新支持
  • 音效格式
  • 视频格式
  • 机器学习模型

不可以做什么?

  • 光栅图像

应使用ImageFormatLoader加载图像。

创建一个资源格式加载器

每种文件格式都包含一个数据容器和一个 ResourceFormatLoader

ResourceFormatLoader通常是简单的类,它们返回所有必需的元数据以支持Godot中的新扩展。该类必须返回格式名称和扩展名字符串。

此外,ResourceFormatLoaders 必须使用 load 函数将文件路径转换为资源(Resource)。要加载资源,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");
}

Creating a ResourceFormatSaver

If you'd like to be able to edit and save a resource, you can implement a 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需要新的注册数据类型来理解其他二进制格式,例如机器学习模型。

Here is an example of creating a custom datatype:

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

Save a file called demo.json with the following contents and place it in the project's root folder:

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

Then attach the following script to any node:

extends Node

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

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