custom_resource_format_loaders.rst 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  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_c++`
  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. /* resource_loader_json.h */
  40. #ifndef RESOURCE_LOADER_JSON_H
  41. #define RESOURCE_LOADER_JSON_H
  42. #include "core/io/resource_loader.h"
  43. class ResourceFormatLoaderJson : public ResourceFormatLoader {
  44. GDCLASS(ResourceFormatLoaderJson, ResourceFormatLoader);
  45. public:
  46. virtual RES load(const String &p_path, const String &p_original_path, Error *r_error = NULL);
  47. virtual void get_recognized_extensions(List<String> *r_extensions) const;
  48. virtual bool handles_type(const String &p_type) const;
  49. virtual String get_resource_type(const String &p_path) const;
  50. };
  51. #endif // RESOURCE_LOADER_JSON_H
  52. .. code-block:: cpp
  53. /* resource_loader_json.cpp */
  54. #include "resource_loader_json.h"
  55. #include "resource_json.h"
  56. RES ResourceFormatLoaderJson::load(const String &p_path, const String &p_original_path, Error *r_error) {
  57. Ref<JsonResource> json = memnew(JsonResource);
  58. if (r_error) {
  59. *r_error = OK;
  60. }
  61. Error err = json->load_file(p_path);
  62. return json;
  63. }
  64. void ResourceFormatLoaderJson::get_recognized_extensions(List<String> *r_extensions) const {
  65. if (!r_extensions->find("json")) {
  66. r_extensions->push_back("json");
  67. }
  68. }
  69. String ResourceFormatLoaderJson::get_resource_type(const String &p_path) const {
  70. return "Resource";
  71. }
  72. bool ResourceFormatLoaderJson::handles_type(const String &p_type) const {
  73. return ClassDB::is_parent_class(p_type, "Resource");
  74. }
  75. Creating a ResourceFormatSaver
  76. ------------------------------
  77. If you'd like to be able to edit and save a resource, you can implement a
  78. ``ResourceFormatSaver``:
  79. .. code-block:: cpp
  80. /* resource_saver_json.h */
  81. #ifndef RESOURCE_SAVER_JSON_H
  82. #define RESOURCE_SAVER_JSON_H
  83. #include "core/io/resource_saver.h"
  84. class ResourceFormatSaverJson : public ResourceFormatSaver {
  85. GDCLASS(ResourceFormatSaverJson, ResourceFormatSaver);
  86. public:
  87. virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0);
  88. virtual bool recognize(const RES &p_resource) const;
  89. virtual void get_recognized_extensions(const RES &p_resource, List<String> *r_extensions) const;
  90. };
  91. #endif // RESOURCE_SAVER_JSON_H
  92. .. code-block:: cpp
  93. /* resource_saver_json.cpp */
  94. #include "resource_saver_json.h"
  95. #include "resource_json.h"
  96. #include "scene/resources/resource_format_text.h"
  97. Error ResourceFormatSaverJson::save(const String &p_path, const RES &p_resource, uint32_t p_flags) {
  98. Ref<JsonResource> json = memnew(JsonResource);
  99. Error error = json->save_file(p_path, p_resource);
  100. return error;
  101. }
  102. bool ResourceFormatSaverJson::recognize(const RES &p_resource) const {
  103. return Object::cast_to<JsonResource>(*p_resource) != NULL;
  104. }
  105. void ResourceFormatSaverJson::get_recognized_extensions(const RES &p_resource, List<String> *r_extensions) const {
  106. if (Object::cast_to<JsonResource>(*p_resource)) {
  107. r_extensions->push_back("json");
  108. }
  109. }
  110. Creating custom data types
  111. --------------------------
  112. Godot may not have a proper substitute within its :ref:`doc_core_types`
  113. or managed resources. Godot needs a new registered data type to
  114. understand additional binary formats such as machine learning models.
  115. Here is an example of creating a custom datatype:
  116. .. code-block:: cpp
  117. /* resource_json.h */
  118. #ifndef RESOURCE_JSON_H
  119. #define RESOURCE_JSON_H
  120. #include "core/io/json.h"
  121. #include "core/variant_parser.h"
  122. class JsonResource : public Resource {
  123. GDCLASS(JsonResource, Resource);
  124. protected:
  125. static void _bind_methods() {
  126. ClassDB::bind_method(D_METHOD("set_dict", "dict"), &JsonResource::set_dict);
  127. ClassDB::bind_method(D_METHOD("get_dict"), &JsonResource::get_dict);
  128. ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "content"), "set_dict", "get_dict");
  129. }
  130. private:
  131. Dictionary content;
  132. public:
  133. Error load_file(const String &p_path);
  134. Error save_file(const String &p_path, const RES &p_resource);
  135. void set_dict(const Dictionary &p_dict);
  136. Dictionary get_dict();
  137. };
  138. #endif // RESOURCE_JSON_H
  139. .. code-block:: cpp
  140. /* resource_json.cpp */
  141. #include "resource_json.h"
  142. Error JsonResource::load_file(const String &p_path) {
  143. Error error;
  144. FileAccess *file = FileAccess::open(p_path, FileAccess::READ, &error);
  145. if (error != OK) {
  146. if (file) {
  147. file->close();
  148. }
  149. return error;
  150. }
  151. String json_string = String("");
  152. while (!file->eof_reached()) {
  153. json_string += file->get_line();
  154. }
  155. file->close();
  156. String error_string;
  157. int error_line;
  158. JSON json;
  159. Variant result;
  160. error = json.parse(json_string, result, error_string, error_line);
  161. if (error != OK) {
  162. file->close();
  163. return error;
  164. }
  165. content = Dictionary(result);
  166. return OK;
  167. }
  168. Error JsonResource::save_file(const String &p_path, const RES &p_resource) {
  169. Error error;
  170. FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &error);
  171. if (error != OK) {
  172. if (file) {
  173. file->close();
  174. }
  175. return error;
  176. }
  177. Ref<JsonResource> json_ref = p_resource.get_ref_ptr();
  178. JSON json;
  179. file->store_string(json.print(json_ref->get_dict(), " "));
  180. file->close();
  181. return OK;
  182. }
  183. void JsonResource::set_dict(const Dictionary &p_dict) {
  184. content = p_dict;
  185. }
  186. Dictionary JsonResource::get_dict() {
  187. return content;
  188. }
  189. Considerations
  190. ~~~~~~~~~~~~~~
  191. Some libraries may not define certain common routines such as IO handling.
  192. Therefore, Godot call translations are required.
  193. For example, here is the code for translating ``FileAccess``
  194. calls into ``std::istream``.
  195. .. code-block:: cpp
  196. #include "core/os/file_access.h"
  197. #include <istream>
  198. #include <streambuf>
  199. class GodotFileInStreamBuf : public std::streambuf {
  200. public:
  201. GodotFileInStreamBuf(FileAccess *fa) {
  202. _file = fa;
  203. }
  204. int underflow() {
  205. if (_file->eof_reached()) {
  206. return EOF;
  207. } else {
  208. size_t pos = _file->get_position();
  209. uint8_t ret = _file->get_8();
  210. _file->seek(pos); // Required since get_8() advances the read head.
  211. return ret;
  212. }
  213. }
  214. int uflow() {
  215. return _file->eof_reached() ? EOF : _file->get_8();
  216. }
  217. private:
  218. FileAccess *_file;
  219. };
  220. References
  221. ~~~~~~~~~~
  222. - `istream <http://www.cplusplus.com/reference/istream/istream/>`_
  223. - `streambuf <http://www.cplusplus.com/reference/streambuf/streambuf/?kw=streambuf>`_
  224. - `core/io/file_access.h <https://github.com/godotengine/godot/blob/master/core/os/file_access.h>`_
  225. Registering the new file format
  226. -------------------------------
  227. Godot registers ``ResourcesFormatLoader`` with a ``ResourceLoader``
  228. handler. The handler selects the proper loader automatically
  229. when ``load`` is called.
  230. .. code-block:: cpp
  231. /* register_types.h */
  232. void register_json_types();
  233. void unregister_json_types();
  234. .. code-block:: cpp
  235. /* register_types.cpp */
  236. #include "register_types.h"
  237. #include "core/class_db.h"
  238. #include "resource_loader_json.h"
  239. #include "resource_saver_json.h"
  240. #include "resource_json.h"
  241. static Ref<ResourceFormatLoaderJson> json_loader;
  242. static Ref<ResourceFormatSaverJson> json_saver;
  243. void register_json_types() {
  244. ClassDB::register_class<JsonResource>();
  245. json_loader.instance();
  246. ResourceLoader::add_resource_format_loader(json_loader);
  247. json_saver.instance();
  248. ResourceSaver::add_resource_format_saver(json_saver);
  249. }
  250. void unregister_json_types() {
  251. ResourceLoader::remove_resource_format_loader(json_loader);
  252. json_loader.unref();
  253. ResourceSaver::remove_resource_format_saver(json_saver);
  254. json_saver.unref();
  255. }
  256. References
  257. ~~~~~~~~~~
  258. - `core/io/resource_loader.cpp <https://github.com/godotengine/godot/blob/master/core/io/resource_loader.cpp>`_
  259. Loading it on GDScript
  260. ----------------------
  261. Save a file called ``demo.json`` with the following contents and place it in the
  262. project's root folder:
  263. .. code-block:: json
  264. {
  265. "savefilename": "demo.json",
  266. "demo": [
  267. "welcome",
  268. "to",
  269. "godot",
  270. "resource",
  271. "loaders"
  272. ]
  273. }
  274. Then attach the following script to any node::
  275. extends Node
  276. onready var json_resource = load("res://demo.json")
  277. func _ready():
  278. print(json_resource.get_dict())