custom_resource_format_loaders.rst 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. .. _doc_custom_resource_format_loaders:
  2. Custom resource format loaders
  3. ==============================
  4. Introduction
  5. ------------
  6. ResourceFormatLoader is a factory interface for loading file assets.
  7. Resources are primary containers. When load is called on the same file
  8. path again, the previous loaded Resource will be referenced. Naturally,
  9. loaded resources must be stateless.
  10. This guide assumes the reader knows how to create C++ modules and Godot
  11. data types. If not, refer to this guide: :ref:`doc_custom_modules_in_cpp`
  12. References
  13. ~~~~~~~~~~
  14. - :ref:`ResourceLoader<class_resourceloader>`
  15. - `core/io/resource_loader.cpp <https://github.com/godotengine/godot/blob/master/core/io/resource_loader.cpp>`_
  16. What for?
  17. ---------
  18. - Adding new support for many file formats
  19. - Audio formats
  20. - Video formats
  21. - Machine learning models
  22. What not?
  23. ---------
  24. - Raster images
  25. ImageFormatLoader should be used to load images.
  26. References
  27. ~~~~~~~~~~
  28. - `core/io/image_loader.h <https://github.com/godotengine/godot/blob/master/core/io/image_loader.h>`_
  29. Creating a ResourceFormatLoader
  30. -------------------------------
  31. Each file format consist of a data container and a ``ResourceFormatLoader``.
  32. ResourceFormatLoaders are classes which return all the
  33. necessary metadata for supporting new extensions in Godot. The
  34. class must return the format name and the extension string.
  35. In addition, ResourceFormatLoaders must convert file paths into
  36. resources with the ``load`` function. To load a resource, ``load`` must
  37. read and handle data serialization.
  38. .. code-block:: cpp
  39. :caption: resource_loader_json.h
  40. #pragma once
  41. #include "core/io/resource_loader.h"
  42. class ResourceFormatLoaderJson : public ResourceFormatLoader {
  43. GDCLASS(ResourceFormatLoaderJson, ResourceFormatLoader);
  44. public:
  45. virtual RES load(const String &p_path, const String &p_original_path, Error *r_error = NULL);
  46. virtual void get_recognized_extensions(List<String> *r_extensions) const;
  47. virtual bool handles_type(const String &p_type) const;
  48. virtual String get_resource_type(const String &p_path) const;
  49. };
  50. .. code-block:: cpp
  51. :caption: resource_loader_json.cpp
  52. #include "resource_loader_json.h"
  53. #include "resource_json.h"
  54. RES ResourceFormatLoaderJson::load(const String &p_path, const String &p_original_path, Error *r_error) {
  55. Ref<JsonResource> json = memnew(JsonResource);
  56. if (r_error) {
  57. *r_error = OK;
  58. }
  59. Error err = json->load_file(p_path);
  60. return json;
  61. }
  62. void ResourceFormatLoaderJson::get_recognized_extensions(List<String> *r_extensions) const {
  63. if (!r_extensions->find("json")) {
  64. r_extensions->push_back("json");
  65. }
  66. }
  67. String ResourceFormatLoaderJson::get_resource_type(const String &p_path) const {
  68. return "Resource";
  69. }
  70. bool ResourceFormatLoaderJson::handles_type(const String &p_type) const {
  71. return ClassDB::is_parent_class(p_type, "Resource");
  72. }
  73. Creating a ResourceFormatSaver
  74. ------------------------------
  75. If you'd like to be able to edit and save a resource, you can implement a
  76. ``ResourceFormatSaver``:
  77. .. code-block:: cpp
  78. :caption: resource_saver_json.h
  79. #pragma once
  80. #include "core/io/resource_saver.h"
  81. class ResourceFormatSaverJson : public ResourceFormatSaver {
  82. GDCLASS(ResourceFormatSaverJson, ResourceFormatSaver);
  83. public:
  84. virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0);
  85. virtual bool recognize(const RES &p_resource) const;
  86. virtual void get_recognized_extensions(const RES &p_resource, List<String> *r_extensions) const;
  87. };
  88. .. code-block:: cpp
  89. :caption: resource_saver_json.cpp
  90. #include "resource_saver_json.h"
  91. #include "resource_json.h"
  92. #include "scene/resources/resource_format_text.h"
  93. Error ResourceFormatSaverJson::save(const String &p_path, const RES &p_resource, uint32_t p_flags) {
  94. Ref<JsonResource> json = memnew(JsonResource);
  95. Error error = json->save_file(p_path, p_resource);
  96. return error;
  97. }
  98. bool ResourceFormatSaverJson::recognize(const RES &p_resource) const {
  99. return Object::cast_to<JsonResource>(*p_resource) != NULL;
  100. }
  101. void ResourceFormatSaverJson::get_recognized_extensions(const RES &p_resource, List<String> *r_extensions) const {
  102. if (Object::cast_to<JsonResource>(*p_resource)) {
  103. r_extensions->push_back("json");
  104. }
  105. }
  106. Creating custom data types
  107. --------------------------
  108. Godot may not have a proper substitute within its :ref:`doc_core_types`
  109. or managed resources. Godot needs a new registered data type to
  110. understand additional binary formats such as machine learning models.
  111. Here is an example of creating a custom datatype:
  112. .. code-block:: cpp
  113. :caption: resource_json.h
  114. #pragma once
  115. #include "core/io/json.h"
  116. #include "core/variant_parser.h"
  117. class JsonResource : public Resource {
  118. GDCLASS(JsonResource, Resource);
  119. protected:
  120. static void _bind_methods() {
  121. ClassDB::bind_method(D_METHOD("set_dict", "dict"), &JsonResource::set_dict);
  122. ClassDB::bind_method(D_METHOD("get_dict"), &JsonResource::get_dict);
  123. ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "content"), "set_dict", "get_dict");
  124. }
  125. private:
  126. Dictionary content;
  127. public:
  128. Error load_file(const String &p_path);
  129. Error save_file(const String &p_path, const RES &p_resource);
  130. void set_dict(const Dictionary &p_dict);
  131. Dictionary get_dict();
  132. };
  133. .. code-block:: cpp
  134. :caption: resource_json.cpp
  135. #include "resource_json.h"
  136. Error JsonResource::load_file(const String &p_path) {
  137. Error error;
  138. FileAccess *file = FileAccess::open(p_path, FileAccess::READ, &error);
  139. if (error != OK) {
  140. if (file) {
  141. file->close();
  142. }
  143. return error;
  144. }
  145. String json_string = String("");
  146. while (!file->eof_reached()) {
  147. json_string += file->get_line();
  148. }
  149. file->close();
  150. String error_string;
  151. int error_line;
  152. JSON json;
  153. Variant result;
  154. error = json.parse(json_string, result, error_string, error_line);
  155. if (error != OK) {
  156. file->close();
  157. return error;
  158. }
  159. content = Dictionary(result);
  160. return OK;
  161. }
  162. Error JsonResource::save_file(const String &p_path, const RES &p_resource) {
  163. Error error;
  164. FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &error);
  165. if (error != OK) {
  166. if (file) {
  167. file->close();
  168. }
  169. return error;
  170. }
  171. Ref<JsonResource> json_ref = p_resource.get_ref_ptr();
  172. JSON json;
  173. file->store_string(json.print(json_ref->get_dict(), " "));
  174. file->close();
  175. return OK;
  176. }
  177. void JsonResource::set_dict(const Dictionary &p_dict) {
  178. content = p_dict;
  179. }
  180. Dictionary JsonResource::get_dict() {
  181. return content;
  182. }
  183. Considerations
  184. ~~~~~~~~~~~~~~
  185. Some libraries may not define certain common routines such as IO handling.
  186. Therefore, Godot call translations are required.
  187. For example, here is the code for translating ``FileAccess``
  188. calls into ``std::istream``.
  189. .. code-block:: cpp
  190. #include "core/io/file_access.h"
  191. #include <istream>
  192. #include <streambuf>
  193. class GodotFileInStreamBuf : public std::streambuf {
  194. public:
  195. GodotFileInStreamBuf(FileAccess *fa) {
  196. _file = fa;
  197. }
  198. int underflow() {
  199. if (_file->eof_reached()) {
  200. return EOF;
  201. } else {
  202. size_t pos = _file->get_position();
  203. uint8_t ret = _file->get_8();
  204. _file->seek(pos); // Required since get_8() advances the read head.
  205. return ret;
  206. }
  207. }
  208. int uflow() {
  209. return _file->eof_reached() ? EOF : _file->get_8();
  210. }
  211. private:
  212. FileAccess *_file;
  213. };
  214. References
  215. ~~~~~~~~~~
  216. - `istream <https://cplusplus.com/reference/istream/istream/>`_
  217. - `streambuf <https://cplusplus.com/reference/streambuf/streambuf/?kw=streambuf>`_
  218. - `core/io/file_access.h <https://github.com/godotengine/godot/blob/master/core/io/file_access.h>`_
  219. Registering the new file format
  220. -------------------------------
  221. Godot registers ``ResourcesFormatLoader`` with a ``ResourceLoader``
  222. handler. The handler selects the proper loader automatically
  223. when ``load`` is called.
  224. .. code-block:: cpp
  225. :caption: register_types.h
  226. void register_json_types();
  227. void unregister_json_types();
  228. .. code-block:: cpp
  229. :caption: register_types.cpp
  230. #include "register_types.h"
  231. #include "core/class_db.h"
  232. #include "resource_loader_json.h"
  233. #include "resource_saver_json.h"
  234. #include "resource_json.h"
  235. static Ref<ResourceFormatLoaderJson> json_loader;
  236. static Ref<ResourceFormatSaverJson> json_saver;
  237. void register_json_types() {
  238. ClassDB::register_class<JsonResource>();
  239. json_loader.instantiate();
  240. ResourceLoader::add_resource_format_loader(json_loader);
  241. json_saver.instantiate();
  242. ResourceSaver::add_resource_format_saver(json_saver);
  243. }
  244. void unregister_json_types() {
  245. ResourceLoader::remove_resource_format_loader(json_loader);
  246. json_loader.unref();
  247. ResourceSaver::remove_resource_format_saver(json_saver);
  248. json_saver.unref();
  249. }
  250. References
  251. ~~~~~~~~~~
  252. - `core/io/resource_loader.cpp <https://github.com/godotengine/godot/blob/master/core/io/resource_loader.cpp>`_
  253. Loading it on GDScript
  254. ----------------------
  255. Save a file called ``demo.json`` with the following contents and place it in the
  256. project's root folder:
  257. .. code-block:: json
  258. {
  259. "savefilename": "demo.json",
  260. "demo": [
  261. "welcome",
  262. "to",
  263. "godot",
  264. "resource",
  265. "loaders"
  266. ]
  267. }
  268. Then attach the following script to any node:
  269. ::
  270. extends Node
  271. @onready var json_resource = load("res://demo.json")
  272. func _ready():
  273. print(json_resource.get_dict())