自定义资源格式加载器

前言

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

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

参考

可以做什么?

  • 添加对多种文件格式的新支持

  • 音频格式

  • 视频格式

  • 机器学习模型

不可以做什么?

  • 光栅图像

应使用ImageFormatLoader加载图像.

参考

创建 ResourceFormatLoader

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

ResourceFormatLoaders通常是简单的类, 返回支持Godot中新扩展的所有必要元数据. 该类必须返回格式名称和扩展字符串.

此外,ResourceFormatLoaders 必须使用 load 函数将文件路径转换为资源(Resource). 要加载资源, load 必须能够读取和处理序列化的资源数据.

  1. /* resource_loader_json.h */
  2. #ifndef RESOURCE_LOADER_JSON_H
  3. #define RESOURCE_LOADER_JSON_H
  4. #include "core/io/resource_loader.h"
  5. class ResourceFormatLoaderJson : public ResourceFormatLoader {
  6. GDCLASS(ResourceFormatLoaderJson, ResourceFormatLoader);
  7. public:
  8. virtual RES load(const String &p_path, const String &p_original_path, Error *r_error = NULL);
  9. virtual void get_recognized_extensions(List<String> *r_extensions) const;
  10. virtual bool handles_type(const String &p_type) const;
  11. virtual String get_resource_type(const String &p_path) const;
  12. };
  13. #endif // RESOURCE_LOADER_JSON_H
  1. /* resource_loader_json.cpp */
  2. #include "resource_loader_json.h"
  3. #include "resource_json.h"
  4. RES ResourceFormatLoaderJson::load(const String &p_path, const String &p_original_path, Error *r_error) {
  5. Ref<JsonResource> json = memnew(JsonResource);
  6. if (r_error) {
  7. *r_error = OK;
  8. }
  9. Error err = json->load_file(p_path);
  10. return json;
  11. }
  12. void ResourceFormatLoaderJson::get_recognized_extensions(List<String> *r_extensions) const {
  13. if (!r_extensions->find("json")) {
  14. r_extensions->push_back("json");
  15. }
  16. }
  17. String ResourceFormatLoaderJson::get_resource_type(const String &p_path) const {
  18. return "Resource";
  19. }
  20. bool ResourceFormatLoaderJson::handles_type(const String &p_type) const {
  21. return ClassDB::is_parent_class(p_type, "Resource");
  22. }

创建 ResourceFormatSaver

如果您希望能够编辑和保存资源, 则可以实现 ResourceFormatSaver:

  1. /* resource_saver_json.h */
  2. #ifndef RESOURCE_SAVER_JSON_H
  3. #define RESOURCE_SAVER_JSON_H
  4. #include "core/io/resource_saver.h"
  5. class ResourceFormatSaverJson : public ResourceFormatSaver {
  6. GDCLASS(ResourceFormatSaverJson, ResourceFormatSaver);
  7. public:
  8. virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0);
  9. virtual bool recognize(const RES &p_resource) const;
  10. virtual void get_recognized_extensions(const RES &p_resource, List<String> *r_extensions) const;
  11. };
  12. #endif // RESOURCE_SAVER_JSON_H
  1. /* resource_saver_json.cpp */
  2. #include "resource_saver_json.h"
  3. #include "resource_json.h"
  4. #include "scene/resources/resource_format_text.h"
  5. Error ResourceFormatSaverJson::save(const String &p_path, const RES &p_resource, uint32_t p_flags) {
  6. Ref<JsonResource> json = memnew(JsonResource);
  7. Error error = json->save_file(p_path, p_resource);
  8. return error;
  9. }
  10. bool ResourceFormatSaverJson::recognize(const RES &p_resource) const {
  11. return Object::cast_to<JsonResource>(*p_resource) != NULL;
  12. }
  13. void ResourceFormatSaverJson::get_recognized_extensions(const RES &p_resource, List<String> *r_extensions) const {
  14. if (Object::cast_to<JsonResource>(*p_resource)) {
  15. r_extensions->push_back("json");
  16. }
  17. }

创建自定义数据类型

Godot在其 核心类型 或托管的资源中可能没有适当的替代品. 这时Godot需要新的注册数据类型来理解其他二进制格式, 例如机器学习模型.

下面是创建自定义数据类型的示例:

  1. /* resource_json.h */
  2. #ifndef RESOURCE_JSON_H
  3. #define RESOURCE_JSON_H
  4. #include "core/io/json.h"
  5. #include "core/variant_parser.h"
  6. class JsonResource : public Resource {
  7. GDCLASS(JsonResource, Resource);
  8. protected:
  9. static void _bind_methods() {
  10. ClassDB::bind_method(D_METHOD("set_dict", "dict"), &JsonResource::set_dict);
  11. ClassDB::bind_method(D_METHOD("get_dict"), &JsonResource::get_dict);
  12. ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "content"), "set_dict", "get_dict");
  13. }
  14. private:
  15. Dictionary content;
  16. public:
  17. Error load_file(const String &p_path);
  18. Error save_file(const String &p_path, const RES &p_resource);
  19. void set_dict(const Dictionary &p_dict);
  20. Dictionary get_dict();
  21. };
  22. #endif // RESOURCE_JSON_H
  1. /* resource_json.cpp */
  2. #include "resource_json.h"
  3. Error JsonResource::load_file(const String &p_path) {
  4. Error error;
  5. FileAccess *file = FileAccess::open(p_path, FileAccess::READ, &error);
  6. if (error != OK) {
  7. if (file) {
  8. file->close();
  9. }
  10. return error;
  11. }
  12. String json_string = String("");
  13. while (!file->eof_reached()) {
  14. json_string += file->get_line();
  15. }
  16. file->close();
  17. String error_string;
  18. int error_line;
  19. JSON json;
  20. Variant result;
  21. error = json.parse(json_string, result, error_string, error_line);
  22. if (error != OK) {
  23. file->close();
  24. return error;
  25. }
  26. content = Dictionary(result);
  27. return OK;
  28. }
  29. Error JsonResource::save_file(const String &p_path, const RES &p_resource) {
  30. Error error;
  31. FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &error);
  32. if (error != OK) {
  33. if (file) {
  34. file->close();
  35. }
  36. return error;
  37. }
  38. Ref<JsonResource> json_ref = p_resource.get_ref_ptr();
  39. JSON json;
  40. file->store_string(json.print(json_ref->get_dict(), " "));
  41. file->close();
  42. return OK;
  43. }
  44. void JsonResource::set_dict(const Dictionary &p_dict) {
  45. content = p_dict;
  46. }
  47. Dictionary JsonResource::get_dict() {
  48. return content;
  49. }

注意事项

一些库可能未定义 IO 处理等通用例程。因此,需要 Godot 调用转换。

例如,下面是将 FileAccess 调用转换为 std::istream 的代码。

  1. #include "core/os/file_access.h"
  2. #include <istream>
  3. #include <streambuf>
  4. class GodotFileInStreamBuf : public std::streambuf {
  5. public:
  6. GodotFileInStreamBuf(FileAccess *fa) {
  7. _file = fa;
  8. }
  9. int underflow() {
  10. if (_file->eof_reached()) {
  11. return EOF;
  12. } else {
  13. size_t pos = _file->get_position();
  14. uint8_t ret = _file->get_8();
  15. _file->seek(pos); // Required since get_8() advances the read head.
  16. return ret;
  17. }
  18. }
  19. int uflow() {
  20. return _file->eof_reached() ? EOF : _file->get_8();
  21. }
  22. private:
  23. FileAccess *_file;
  24. };

参考

注册新的文件格式

Godot 用 ResourceLoader 处理程序注册 ResourcesFormatLoader. 当调用 load 时, 处理程序会自动选择合适的加载器.

  1. /* register_types.h */
  2. void register_json_types();
  3. void unregister_json_types();
  1. /* register_types.cpp */
  2. #include "register_types.h"
  3. #include "core/class_db.h"
  4. #include "resource_loader_json.h"
  5. #include "resource_saver_json.h"
  6. #include "resource_json.h"
  7. static Ref<ResourceFormatLoaderJson> json_loader;
  8. static Ref<ResourceFormatSaverJson> json_saver;
  9. void register_json_types() {
  10. ClassDB::register_class<JsonResource>();
  11. json_loader.instance();
  12. ResourceLoader::add_resource_format_loader(json_loader);
  13. json_saver.instance();
  14. ResourceSaver::add_resource_format_saver(json_saver);
  15. }
  16. void unregister_json_types() {
  17. ResourceLoader::remove_resource_format_loader(json_loader);
  18. json_loader.unref();
  19. ResourceSaver::remove_resource_format_saver(json_saver);
  20. json_saver.unref();
  21. }

参考

在 GDScript 中加载

保存具有以下内容的名为 demo.json 的文件,并将其放置在项目的根文件夹中:

  1. {
  2. "savefilename": "demo.json",
  3. "demo": [
  4. "welcome",
  5. "to",
  6. "godot",
  7. "resource",
  8. "loaders"
  9. ]
  10. }

创建一个节点并附加下面的脚本:

  1. extends Node
  2. onready var json_resource = load("res://demo.json")
  3. func _ready():
  4. print(json_resource.get_dict())