import_plugins.rst 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. :article_outdated: True
  2. .. _doc_import_plugins:
  3. Import plugins
  4. ==============
  5. .. note:: This tutorial assumes you already know how to make generic plugins. If
  6. in doubt, refer to the :ref:`doc_making_plugins` page. This also
  7. assumes you are acquainted with Godot's import system.
  8. Introduction
  9. ------------
  10. An import plugin is a special type of editor tool that allows custom resources
  11. to be imported by Godot and be treated as first-class resources. The editor
  12. itself comes bundled with a lot of import plugins to handle the common resources
  13. like PNG images, Collada and glTF models, Ogg Vorbis sounds, and many more.
  14. This tutorial shows how to create an import plugin to load a
  15. custom text file as a material resource. This text file will contain three
  16. numeric values separated by comma, which represents the three channels of a
  17. color, and the resulting color will be used as the albedo (main color) of the
  18. imported material. In this example it contains the pure blue color
  19. (zero red, zero green, and full blue):
  20. .. code-block:: none
  21. 0,0,255
  22. Configuration
  23. -------------
  24. First we need a generic plugin that will handle the initialization and
  25. destruction of our import plugin. Let's add the ``plugin.cfg`` file first:
  26. .. code-block:: ini
  27. [plugin]
  28. name="Silly Material Importer"
  29. description="Imports a 3D Material from an external text file."
  30. author="Yours Truly"
  31. version="1.0"
  32. script="material_import.gd"
  33. Then we need the ``material_import.gd`` file to add and remove the import plugin
  34. when needed:
  35. ::
  36. # material_import.gd
  37. @tool
  38. extends EditorPlugin
  39. var import_plugin
  40. func _enter_tree():
  41. import_plugin = preload("import_plugin.gd").new()
  42. add_import_plugin(import_plugin)
  43. func _exit_tree():
  44. remove_import_plugin(import_plugin)
  45. import_plugin = null
  46. When this plugin is activated, it will create a new instance of the import
  47. plugin (which we'll soon make) and add it to the editor using the
  48. :ref:`add_import_plugin() <class_EditorPlugin_method_add_import_plugin>` method. We store
  49. a reference to it in a class member ``import_plugin`` so we can refer to it
  50. later when removing it. The
  51. :ref:`remove_import_plugin() <class_EditorPlugin_method_remove_import_plugin>` method is
  52. called when the plugin is deactivated to clean up the memory and let the editor
  53. know the import plugin isn't available anymore.
  54. Note that the import plugin is a reference type, so it doesn't need to be
  55. explicitly released from memory with the ``free()`` function. It will be
  56. released automatically by the engine when it goes out of scope.
  57. The EditorImportPlugin class
  58. ----------------------------
  59. The main character of the show is the
  60. :ref:`EditorImportPlugin class <class_EditorImportPlugin>`. It is responsible for
  61. implementing the methods that are called by Godot when it needs to know how to deal
  62. with files.
  63. Let's begin to code our plugin, one method at time:
  64. ::
  65. # import_plugin.gd
  66. @tool
  67. extends EditorImportPlugin
  68. func _get_importer_name():
  69. return "demos.sillymaterial"
  70. The first method is the
  71. :ref:`_get_importer_name()<class_EditorImportPlugin_private_method__get_importer_name>`. This is a
  72. unique name for your plugin that is used by Godot to know which import was used
  73. in a certain file. When the files needs to be reimported, the editor will know
  74. which plugin to call.
  75. ::
  76. func _get_visible_name():
  77. return "Silly Material"
  78. The :ref:`_get_visible_name()<class_EditorImportPlugin_private_method__get_visible_name>` method is
  79. responsible for returning the name of the type it imports and it will be shown to the
  80. user in the Import dock.
  81. You should choose this name as a continuation to "Import as", e.g. *"Import as
  82. Silly Material"*. You can name it whatever you want but we recommend a
  83. descriptive name for your plugin.
  84. ::
  85. func _get_recognized_extensions():
  86. return ["mtxt"]
  87. Godot's import system detects file types by their extension. In the
  88. :ref:`_get_recognized_extensions()<class_EditorImportPlugin_private_method__get_recognized_extensions>`
  89. method you return an array of strings to represent each extension that this
  90. plugin can understand. If an extension is recognized by more than one plugin,
  91. the user can select which one to use when importing the files.
  92. .. tip:: Common extensions like ``.json`` and ``.txt`` might be used by many
  93. plugins. Also, there could be files in the project that are just data
  94. for the game and should not be imported. You have to be careful when
  95. importing to validate the data. Never expect the file to be well-formed.
  96. ::
  97. func _get_save_extension():
  98. return "material"
  99. The imported files are saved in the ``.import`` folder at the project's root.
  100. Their extension should match the type of resource you are importing, but since
  101. Godot can't tell what you'll use (because there might be multiple valid
  102. extensions for the same resource), you need to declare what will be used in
  103. the import.
  104. Since we're importing a Material, we'll use the special extension for such
  105. resource types. If you are importing a scene, you can use ``scn``. Generic
  106. resources can use the ``res`` extension. However, this is not enforced in any
  107. way by the engine.
  108. ::
  109. func _get_resource_type():
  110. return "StandardMaterial3D"
  111. The imported resource has a specific type, so the editor can know which property
  112. slot it belongs to. This allows drag and drop from the FileSystem dock to a
  113. property in the Inspector.
  114. In our case it's a :ref:`class_StandardMaterial3D`, which can be applied to 3D
  115. objects.
  116. .. note:: If you need to import different types from the same extension, you
  117. have to create multiple import plugins. You can abstract the import
  118. code on another file to avoid duplication in this regard.
  119. Options and presets
  120. -------------------
  121. Your plugin can provide different options to allow the user to control how the
  122. resource will be imported. If a set of selected options is common, you can also
  123. create different presets to make it easier for the user. The following image
  124. shows how the options will appear in the editor:
  125. .. image:: img/import_plugin_options.png
  126. Since there might be many presets and they are identified with a number, it's a
  127. good practice to use an enum so you can refer to them using names.
  128. ::
  129. @tool
  130. extends EditorImportPlugin
  131. enum Presets { DEFAULT }
  132. ...
  133. Now that the enum is defined, let's keep looking at the methods of an import
  134. plugin:
  135. ::
  136. func _get_preset_count():
  137. return Presets.size()
  138. The :ref:`_get_preset_count() <class_EditorImportPlugin_private_method__get_preset_count>` method
  139. returns the amount of presets that this plugins defines. We only have one preset
  140. now, but we can make this method future-proof by returning the size of our
  141. ``Presets`` enumeration.
  142. ::
  143. func _get_preset_name(preset_index):
  144. match preset_index:
  145. Presets.DEFAULT:
  146. return "Default"
  147. _:
  148. return "Unknown"
  149. Here we have the
  150. :ref:`_get_preset_name() <class_EditorImportPlugin_private_method__get_preset_name>` method, which
  151. gives names to the presets as they will be presented to the user, so be sure to
  152. use short and clear names.
  153. We can use the ``match`` statement here to make the code more structured. This
  154. way it's easy to add new presets in the future. We use the catch all pattern to
  155. return something too. Although Godot won't ask for presets beyond the preset
  156. count you defined, it's always better to be on the safe side.
  157. If you have only one preset you could simply return its name directly, but if
  158. you do this you have to be careful when you add more presets.
  159. ::
  160. func _get_import_options(path, preset_index):
  161. match preset_index:
  162. Presets.DEFAULT:
  163. return [{
  164. "name": "use_red_anyway",
  165. "default_value": false
  166. }]
  167. _:
  168. return []
  169. This is the method which defines the available options.
  170. :ref:`_get_import_options() <class_EditorImportPlugin_private_method__get_import_options>` returns
  171. an array of dictionaries, and each dictionary contains a few keys that are
  172. checked to customize the option as its shown to the user. The following table
  173. shows the possible keys:
  174. +-------------------+------------+----------------------------------------------------------------------------------------------------------+
  175. | Key | Type | Description |
  176. +===================+============+==========================================================================================================+
  177. | ``name`` | String | The name of the option. When showed, underscores become spaces and first letters are capitalized. |
  178. +-------------------+------------+----------------------------------------------------------------------------------------------------------+
  179. | ``default_value`` | Any | The default value of the option for this preset. |
  180. +-------------------+------------+----------------------------------------------------------------------------------------------------------+
  181. | ``property_hint`` | Enum value | One of the :ref:`PropertyHint <enum_@GlobalScope_PropertyHint>` values to use as hint. |
  182. +-------------------+------------+----------------------------------------------------------------------------------------------------------+
  183. | ``hint_string`` | String | The hint text of the property. The same as you'd add in the ``export`` statement in GDScript. |
  184. +-------------------+------------+----------------------------------------------------------------------------------------------------------+
  185. | ``usage`` | Enum value | One of the :ref:`PropertyUsageFlags <enum_@GlobalScope_PropertyUsageFlags>` values to define the usage. |
  186. +-------------------+------------+----------------------------------------------------------------------------------------------------------+
  187. The ``name`` and ``default_value`` keys are **mandatory**, the rest are optional.
  188. Note that the ``_get_import_options`` method receives the preset number, so you
  189. can configure the options for each different preset (especially the default
  190. value). In this example we use the ``match`` statement, but if you have lots of
  191. options and the presets only change the value you may want to create the array
  192. of options first and then change it based on the preset.
  193. .. warning:: The ``_get_import_options`` method is called even if you don't
  194. define presets (by making ``_get_preset_count`` return zero). You
  195. have to return an array even it's empty, otherwise you can get
  196. errors.
  197. ::
  198. func _get_option_visibility(path, option_name, options):
  199. return true
  200. For the
  201. :ref:`_get_option_visibility() <class_EditorImportPlugin_private_method__get_option_visibility>`
  202. method, we simply return ``true`` because all of our options (i.e. the single
  203. one we defined) are visible all the time.
  204. If you need to make certain option visible only if another is set with a certain
  205. value, you can add the logic in this method.
  206. The ``import`` method
  207. ---------------------
  208. The heavy part of the process, responsible for converting the files into
  209. resources, is covered by the :ref:`_import() <class_EditorImportPlugin_private_method__import>`
  210. method. Our sample code is a bit long, so let's split in a few parts:
  211. ::
  212. func _import(source_file, save_path, options, r_platform_variants, r_gen_files):
  213. var file = FileAccess.open(source_file, FileAccess.READ)
  214. if file == null:
  215. return FileAccess.get_open_error()
  216. var line = file.get_line()
  217. The first part of our import method opens and reads the source file. We use the
  218. :ref:`FileAccess <class_FileAccess>` class to do that, passing the ``source_file``
  219. parameter which is provided by the editor.
  220. If there's an error when opening the file, we return it to let the editor know
  221. that the import wasn't successful.
  222. ::
  223. var channels = line.split(",")
  224. if channels.size() != 3:
  225. return ERR_PARSE_ERROR
  226. var color
  227. if options.use_red_anyway:
  228. color = Color8(255, 0, 0)
  229. else:
  230. color = Color8(int(channels[0]), int(channels[1]), int(channels[2]))
  231. This code takes the line of the file it read before and splits it in pieces
  232. that are separated by a comma. If there are more or less than the three values,
  233. it considers the file invalid and reports an error.
  234. Then it creates a new :ref:`Color <class_Color>` variable and sets its values
  235. according to the input file. If the ``use_red_anyway`` option is enabled, then
  236. it sets the color as a pure red instead.
  237. ::
  238. var material = StandardMaterial3D.new()
  239. material.albedo_color = color
  240. This part makes a new :ref:`StandardMaterial3D <class_StandardMaterial3D>` that is the
  241. imported resource. We create a new instance of it and then set its albedo color
  242. as the value we got before.
  243. ::
  244. return ResourceSaver.save(material, "%s.%s" % [save_path, _get_save_extension()])
  245. This is the last part and quite an important one, because here we save the made
  246. resource to the disk. The path of the saved file is generated and informed by
  247. the editor via the ``save_path`` parameter. Note that this comes **without** the
  248. extension, so we add it using :ref:`string formatting <doc_gdscript_printf>`. For
  249. this we call the ``_get_save_extension`` method that we defined earlier, so we
  250. can be sure that they won't get out of sync.
  251. We also return the result from the
  252. :ref:`ResourceSaver.save() <class_ResourceSaver_method_save>` method, so if there's an
  253. error in this step, the editor will know about it.
  254. Platform variants and generated files
  255. -------------------------------------
  256. You may have noticed that our plugin ignored two arguments of the ``import``
  257. method. Those are *return arguments* (hence the ``r`` at the beginning of their
  258. name), which means that the editor will read from them after calling your import
  259. method. Both of them are arrays that you can fill with information.
  260. The ``r_platform_variants`` argument is used if you need to import the resource
  261. differently depending on the target platform. While it's called *platform*
  262. variants, it is based on the presence of :ref:`feature tags <doc_feature_tags>`,
  263. so even the same platform can have multiple variants depending on the setup.
  264. To import a platform variant, you need to save it with the feature tag before
  265. the extension, and then push the tag to the ``r_platform_variants`` array so the
  266. editor can know that you did.
  267. For example, let's say we save a different material for a mobile platform. We
  268. would need to do something like the following:
  269. ::
  270. r_platform_variants.push_back("mobile")
  271. return ResourceSaver.save(mobile_material, "%s.%s.%s" % [save_path, "mobile", _get_save_extension()])
  272. The ``r_gen_files`` argument is meant for extra files that are generated during
  273. your import process and need to be kept. The editor will look at it to
  274. understand the dependencies and make sure the extra file is not inadvertently
  275. deleted.
  276. This is also an array and should be filled with full paths of the files you
  277. save. As an example, let's create another material for the next pass and save it
  278. in a different file:
  279. ::
  280. var next_pass = StandardMaterial3D.new()
  281. next_pass.albedo_color = color.inverted()
  282. var next_pass_path = "%s.next_pass.%s" % [save_path, _get_save_extension()]
  283. err = ResourceSaver.save(next_pass, next_pass_path)
  284. if err != OK:
  285. return err
  286. r_gen_files.push_back(next_pass_path)
  287. Trying the plugin
  288. -----------------
  289. This has been theoretical, but now that the import plugin is done, let's
  290. test it. Make sure you created the sample file (with the contents described in
  291. the introduction section) and save it as ``test.mtxt``. Then activate the plugin
  292. in the Project Settings.
  293. If everything goes well, the import plugin is added to the editor and the file
  294. system is scanned, making the custom resource appear on the FileSystem dock. If
  295. you select it and focus the Import dock, you can see the only option to select
  296. there.
  297. Create a MeshInstance3D node in the scene, and for its Mesh property set up a new
  298. SphereMesh. Unfold the Material section in the Inspector and then drag the file
  299. from the FileSystem dock to the material property. The object will update in the
  300. viewport with the blue color of the imported material.
  301. .. image:: img/import_plugin_trying.png
  302. Go to Import dock, enable the "Use Red Anyway" option, and click on "Reimport".
  303. This will update the imported material and should automatically update the view
  304. showing the red color instead.
  305. And that's it! Your first import plugin is done! Now get creative and make
  306. plugins for your own beloved formats. This can be quite useful to write your
  307. data in a custom format and then use it in Godot as if they were native
  308. resources. This shows how the import system is powerful and extendable.