Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

Importer des plugins

Note

Ce tutoriel suppose que vous savez déjà comment créer des plugins génériques. En cas de doute, reportez-vous à la page Création de plugins. Cela suppose également que vous connaissiez le système d'importation de Godot.

Introduction

Un plugin d'importation est un type spécial d'outil de l'éditeur qui permet aux ressources personnalisées d'être importées par Godot et d'être traitées comme des ressources de première classe. L'éditeur lui-même est fourni avec de nombreux plugins d'importation pour gérer les ressources communes comme les images PNG, les modèles Collada et glTF, les sons Ogg Vorbis, et bien d'autres.

This tutorial shows how to create an import plugin to load a custom text file as a material resource. This text file will contain three numeric values separated by comma, which represents the three channels of a color, and the resulting color will be used as the albedo (main color) of the imported material. In this example it contains the pure blue color (zero red, zero green, and full blue):

0,0,255

Configuration

Tout d'abord, nous avons besoin d'un plugin générique qui va prendre en charge l'initialisation et la destruction de notre plugin d'importation. Ajoutons d'abord le fichier plugin.cfg :

[plugin]

name="Silly Material Importer"
description="Imports a 3D Material from an external text file."
author="Yours Truly"
version="1.0"
script="material_import.gd"

Puis, nous avons besoin du fichier material_import.gd pour ajouter et enlever le plugin d'importation quand nous en aurons besoin :

# material_import.gd
@tool
extends EditorPlugin


var import_plugin


func _enter_tree():
    import_plugin = preload("import_plugin.gd").new()
    add_import_plugin(import_plugin)


func _exit_tree():
    remove_import_plugin(import_plugin)
    import_plugin = null

Quand ce plugin est activé, il va créer une nouvelle instance du plugin d'importation (que nous allons bientôt faire) et l'ajouter à l'éditeur en utilisant la méthode add_import_plugin(). Nous stockons une référence à cette méthode dans un membre de classe import_plugin pour pouvoir y faire référence plus tard lors de sa suppression. La méthode remove_import_plugin() est appelée quand le plugin est désactivé pour nettoyer la mémoire et laisser l'éditeur savoir que le plugin d'importation n'est plus disponible.

Notez que le plugin d'importation est de type référence, il n'as donc pas besoin d’être explicitement libéré de la mémoire en utilisant la fonction free(). Il sera libéré automatiquement par le moteur.

La classe EditorImportPlugin

Le personnage principal de ce spectacle est la classe EditorImportPlugin class. Elle est responsable de l'implémentation des méthodes qui sont appelée par Godot quand il a besoin de savoir comment gérer des fichiers.

Commençons a coder notre plugin, une méthode a la fois :

# import_plugin.gd
@tool
extends EditorImportPlugin


func _get_importer_name():
    return "demos.sillymaterial"

The first method is the _get_importer_name(). This is a unique name for your plugin that is used by Godot to know which import was used in a certain file. When the files needs to be reimported, the editor will know which plugin to call.

func _get_visible_name():
    return "Silly Material"

The _get_visible_name() method is responsible for returning the name of the type it imports and it will be shown to the user in the Import dock.

Vous devez choisir ce nom comme suite à "Import as", par exemple "Import as Silly Material ". Vous pouvez lui donner le nom que vous voulez, mais nous vous recommandons d'utiliser un nom descriptif pour votre plugin.

func _get_recognized_extensions():
    return ["mtxt"]

Godot's import system detects file types by their extension. In the _get_recognized_extensions() method you return an array of strings to represent each extension that this plugin can understand. If an extension is recognized by more than one plugin, the user can select which one to use when importing the files.

Astuce

Des extensions fréquentes comme .json et .txt peuvent être utilisées par plusieurs plugins. Il peut aussi y avoir des fichiers dans le projet qui ne contiennent que des données pour le jeu et ne doivent pas être importés. Il faut faire attention au moment de l’importation de valider les données, et ne jamais compter sur le fait que le fichier soit bien formaté.

func _get_save_extension():
    return "material"

Les fichiers importés sont enregistrés dans le dossier .import à la racine du projet. Leur extension doit correspondre au type de ressource que vous importez, mais comme Godot ne peut pas savoir ce que vous utiliserez (car il peut y avoir plusieurs extensions valides pour la même ressource), il faut déclarer ce qui sera utilisé pour l’importation.

Puisqu’on importe un Material, nous allons utiliser une extension spéciale pour ce type de ressource. Si vous importez une scène, vous pouvez utiliser scn. Les ressources génériques peuvent utiliser l’extension res. Cependant, le moteur n’impose rien.

func _get_resource_type():
    return "StandardMaterial3D"

La ressource importée a un type spécifique, pour que l’éditeur puisse savoir à quel emplacement de propriété elle appartient. Cela permet de faire un glisser-déposer depuis le dock Système de fichiers vers une propriété dans l’Inspecteur.

In our case it's a StandardMaterial3D, which can be applied to 3D objects.

Note

Si vous avez besoin d’importer différents types depuis la même extension, il faudra créer plusieurs plugins d’importation. Vous pouvez abstraire le code d’importation dans un autre fichier pour éviter la duplication.

Options et préréglages

Votre plugin peut offrir différentes options pour permettre à l’utilisateur de contrôler la manière dont la ressource sera importée. Si un ensemble d’options est courant, vous pouvez aussi créer des préréglages pour simplifier la vie à l’utilisateur. L’image suivante montre comment les options apparaîtront dans l’éditeur :

../../../_images/import_plugin_options.png

Puisqu’il peut y avoir de nombreux préréglages et qu’ils sont identifiés par un nombre, il est de bonne pratique d’utiliser une énumération pour pouvoir s’y référer en utilisant des noms.

@tool
extends EditorImportPlugin


enum Presets { DEFAULT }


...

Maintenant que l’énumération est définie, revenons aux méthodes d’un plugin d’importation :

func _get_preset_count():
    return Presets.size()

The _get_preset_count() method returns the amount of presets that this plugins defines. We only have one preset now, but we can make this method future-proof by returning the size of our Presets enumeration.

func _get_preset_name(preset_index):
    match preset_index:
        Presets.DEFAULT:
            return "Default"
        _:
            return "Unknown"

Here we have the _get_preset_name() method, which gives names to the presets as they will be presented to the user, so be sure to use short and clear names.

On peut utiliser l’instruction match pour mieux structurer le code. Il sera ainsi facile d’ajouter des nouveaux préréglages à l’avenir. On utilise aussi le pattern joker pour toujours retourner quelque chose. Bien que Godot ne demande pas de préréglages au-delà du nombre que vous avez défini, il vaut toujours mieux être prudent.

Si vous n’avez qu’un seul préréglage, vous pouvez simplement retourner directement son nom, mais il faut dans ce cas faire attention quand vous ajouterez d’autres préréglages.

func _get_import_options(path, preset_index):
    match preset_index:
        Presets.DEFAULT:
            return [{
                       "name": "use_red_anyway",
                       "default_value": false
                    }]
        _:
            return []

This is the method which defines the available options. _get_import_options() returns an array of dictionaries, and each dictionary contains a few keys that are checked to customize the option as its shown to the user. The following table shows the possible keys:

Clé

Type

Description

name

Chaîne de caractères

Le nom de l’option. À l’affichage, les tirets bas deviennent des espaces et les premières lettres des mots sont passées en capitales.

default_value

N’importe

La valeur par défaut de l’option pour ce préréglage.

property_hint

Valeur d’énumération

Une des valeurs de PropertyHint, à utiliser pour comme indication.

hint_string

Chaîne de caractères

Le texte d’indication de la propriété. Le même qu’on ajouterait à l’instruction export en GDScript.

usage

Valeur d’énumération

Une des valeurs de PropertyUsageFlags définissant l’usage.

Les clés name et default_value sont obligatoires, les autres sont optionnelles.

Note that the _get_import_options method receives the preset number, so you can configure the options for each different preset (especially the default value). In this example we use the match statement, but if you have lots of options and the presets only change the value you may want to create the array of options first and then change it based on the preset.

Avertissement

The _get_import_options method is called even if you don't define presets (by making _get_preset_count return zero). You have to return an array even it's empty, otherwise you can get errors.

func _get_option_visibility(path, option_name, options):
    return true

For the _get_option_visibility() method, we simply return true because all of our options (i.e. the single one we defined) are visible all the time.

Si vous devez rendre certaines options visibles uniquement si une autre est définie avec une certaine valeur, vous pouvez ajouter la logique dans cette méthode.

La méthode import

The heavy part of the process, responsible for converting the files into resources, is covered by the _import() method. Our sample code is a bit long, so let's split in a few parts:

func _import(source_file, save_path, options, r_platform_variants, r_gen_files):
    var file = FileAccess.open(source_file, FileAccess.READ)
    if file == null:
        return FileAccess.get_open_error()

    var line = file.get_line()

The first part of our import method opens and reads the source file. We use the FileAccess class to do that, passing the source_file parameter which is provided by the editor.

S'il y a une erreur lors de l'ouverture du fichier, nous la retournons pour faire savoir à l'éditeur que l'importation n'a pas réussi.

var channels = line.split(",")
if channels.size() != 3:
    return ERR_PARSE_ERROR

var color
if options.use_red_anyway:
    color = Color8(255, 0, 0)
else:
    color = Color8(int(channels[0]), int(channels[1]), int(channels[2]))

Ce code prend la ligne du fichier qu'il a lu auparavant et la divise en morceaux séparés par une virgule. S'il y a plus ou moins que les trois valeurs, il considère le fichier comme non valide et signale une erreur.

Ensuite, il crée une nouvelle variable Color et fixe ses valeurs en fonction du fichier d'entrée. Si l'option use_red_anyway est activée, alors elle définit la couleur comme un rouge pur à la place.

var material = StandardMaterial3D.new()
material.albedo_color = color

This part makes a new StandardMaterial3D that is the imported resource. We create a new instance of it and then set its albedo color as the value we got before.

return ResourceSaver.save(material, "%s.%s" % [save_path, _get_save_extension()])

This is the last part and quite an important one, because here we save the made resource to the disk. The path of the saved file is generated and informed by the editor via the save_path parameter. Note that this comes without the extension, so we add it using string formatting. For this we call the _get_save_extension method that we defined earlier, so we can be sure that they won't get out of sync.

Nous renvoyons également le résultat de la méthode ResourceSaver.save(), donc s'il y a une erreur dans cette étape, l'éditeur en sera informé.

Variantes de plate-forme et fichiers générés

Vous avez peut-être remarqué que notre plugin a ignoré deux arguments de la méthode import. Ce sont des arguments de retour (d'où le r au début de leur nom), ce qui signifie que l'éditeur les lira après avoir appelé votre méthode d'importation. Les deux sont des tableaux que vous pouvez remplir avec des informations.

L'argument r_platform_variants est utilisé si vous avez besoin d'importer la ressource différemment selon la plate-forme cible. Bien qu'il soit appelé variantes de plate-forme, il est basé sur la présence de feature tags, donc la même plateforme peut avoir plusieurs variantes selon la configuration.

Pour importer une variante de plate-forme, vous devez l'enregistrer avec la balise de fonctionnalité avant l'extension, puis pousser la balise dans le tableau r_platform_variants pour que l'éditeur sache que vous l'avez fait.

Par exemple, disons que nous enregistrons un matériau différent pour une plate-forme mobile. Nous aurions besoin de faire quelque chose comme ceci :

r_platform_variants.push_back("mobile")
return ResourceSaver.save(mobile_material, "%s.%s.%s" % [save_path, "mobile", _get_save_extension()])

L'argument r_gen_files est destiné aux fichiers supplémentaires qui sont générés au cours de votre processus d'importation et doivent être conservés. L'éditeur l'examinera pour comprendre les dépendances et s'assurer que le fichier supplémentaire n'est pas supprimé par inadvertance.

Il s'agit également d'un tableau et doit être rempli avec les chemins d'accès complets des fichiers que vous enregistrez. À titre d'exemple, créons un autre matériau pour la prochaine passe et enregistrons-le dans un fichier différent :

var next_pass = StandardMaterial3D.new()
next_pass.albedo_color = color.inverted()
var next_pass_path = "%s.next_pass.%s" % [save_path, _get_save_extension()]

err = ResourceSaver.save(next_pass, next_pass_path)
if err != OK:
    return err
r_gen_files.push_back(next_pass_path)

Essayer le plugin

Cela a été théorique, mais maintenant que le plugin d'importation est fait, testons-le. Assurez-vous que vous avez créé le fichier d'exemple (avec le contenu décrit dans la section d'introduction) et enregistrez-le sous le nom test.mtxt. Ensuite, activez le plugin dans les Paramètres du projet.

Si tout se passe bien, le plugin d'importation est ajouté à l'éditeur et le système de fichiers est scanné, faisant apparaître la ressource personnalisée sur le dock du système de fichiers. Si vous la sélectionnez et focalisez le dock d'importation, vous pouvez voir la seule option à sélectionner à cet endroit.

Create a MeshInstance3D node in the scene, and for its Mesh property set up a new SphereMesh. Unfold the Material section in the Inspector and then drag the file from the FileSystem dock to the material property. The object will update in the viewport with the blue color of the imported material.

../../../_images/import_plugin_trying.png

Allez dans le dock d'importation, activez l'option "Use Red Anyway" et cliquez sur "Reimport". Cela mettra à jour le matériel importé et devrait automatiquement mettre à jour la vue montrant la couleur rouge à la place.

Et c'est tout ! Votre premier plugin d'importation est terminé ! Faites preuve de créativité et créez des plugins pour vos formats préférés. Cela peut être très utile pour écrire vos données dans un format personnalisé puis les utiliser dans Godot comme s'il s'agissait de ressources natives. Cela montre à quel point le système d'importation est puissant et extensible.