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.

Ce tutoriel vous montrera comment créer un simple plugin d'importation pour charger un fichier texte personnalisé comme ressource matériel. Ce fichier texte contiendra trois valeurs numériques séparées par une virgule, qui représentent les trois canaux d'une couleur, et la couleur résultante sera utilisée comme albédo (couleur principale) du matériel importé. Dans cet exemple, il contiendra la couleur bleue pure (zéro rouge, zéro vert et bleu complet) :

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"

La première méthode est la get_importer_name(). Il s'agit d'un nom unique pour votre plugin qui est utilisé par Godot pour savoir quelle importation a été utilisée dans un certain fichier. Lorsque les fichiers doivent être réimportés, l'éditeur saura quel plugin appeler.

func get_visible_name():
    return "Silly Material"

La méthode get_visible_name() est chargée de renvoyer le nom du type importé et il sera affiché à l'utilisateur dans le dock d'importation.

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"]

Le système d'importation de Godot détecte les types de fichiers par leur extension. Dans la méthode get_recognized_extensions() vous retournez un tableau de chaînes de caractères pour représenter chaque extension que ce plugin peut comprendre. Si une extension est reconnue par plus d'un plugin, l'utilisateur peut choisir celui qu'il souhaite utiliser lors de l'importation des fichiers.

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 "SpatialMaterial"

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.

Dans notre cas c’est un SpatialMaterial, qui peut être appliqué à des objets 3D.

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()

La méthode get_preset_count() retourne le nombre de préréglages définis par ce plugin. Il n’y a qu’un seul préréglage pour le moment, mais on peut s’assurer que cette méthode marche encore à l’avenir en retournant la taille de notre énumération Presets.

func get_preset_name(preset):
    match preset:
        Presets.DEFAULT:
            return "Default"
        _:
            return "Unknown"

Nous avons ensuite la méthode get_preset_name(), qui nomme les préréglages tels qu’ils seront présentés à l’utilisateur, donc choisissez des noms courts et clairs.

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(preset):
    match preset:
        Presets.DEFAULT:
            return [{
                       "name": "use_red_anyway",
                       "default_value": false
                    }]
        _:
            return []

Voici la méthode qui définit les options disponibles. get_import_options() retourne un tableau de dictionnaires, et chaque dictionnaire contient quelques clés qui sont testées pour personnaliser l’option affichée à l’utilisateur. La table suivante montre les clés possible :

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.

Remarquez que la méthode get_import_options reçoit le numéro du préréglage pour que vous puissiez configurer les options pour chaque différent préréglage (en particulier la valeur par défaut). Dans cet exemple on utilise l’instruction match, mais s’il y a plein d’options et que les préréglages ne changent que la valeur, il vaut mieux commencer par créer le tableau d’option et le changer ensuite selon le préréglage.

Avertissement

La méthode get_import_options est appelée même si vous ne définissez pas de préréglages (en faisant en sorte que get_preset_count retourne zéro). Il faut retourner un tableau, même vide, sans quoi il y aura des erreurs.

func get_option_visibility(option, options):
    return true

Pour la méthode get_option_visibility(), nous retournons simplement true car toutes nos options (c'est-à-dire celle que nous avons définie) sont visibles en permanence.

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

La partie la plus lourde du processus, responsable de la conversion des fichiers en ressources, est couverte par la méthode import(). Notre exemple de code est un peu long, alors séparons-le en quelques parties :

func import(source_file, save_path, options, r_platform_variants, r_gen_files):
    var file = File.new()
    var err = file.open(source_file, File.READ)
    if err != OK:
        return err

    var line = file.get_line()

    file.close()

La première partie de notre méthode d'importation s'ouvre et lit le fichier source. Nous utilisons la classe File pour faire cela, en passant le paramètre source_file qui est fourni par l'éditeur.

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 = SpatialMaterial.new()
material.albedo_color = color

Cette partie crée un nouveau SpatialMaterial qui est la ressource importée. Nous en créons une nouvelle instance, puis définissons sa couleur d'albédo comme la valeur que nous avons obtenue auparavant.

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

C'est la dernière partie et elle est assez importante, car ici nous sauvegardons la ressource créée sur le disque. Le chemin du fichier sauvegardé est généré et renseigné par l'éditeur via le paramètre save_path. Notez que ce paramètre est fourni sans l'extension, donc nous l'ajoutons en utilisant string formatting. Pour cela, nous appelons la méthode get_save_extension que nous avons définie précédemment, afin que nous puissions être sûrs qu'ils ne se désynchroniseront pas.

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("%s.%s.%s" % [save_path, "mobile", get_save_extension()], mobile_material)

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 = SpatialMaterial.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_path, next_pass)
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.

Créez un nœud MeshInstance dans la scène et pour sa propriété Mesh, configurez un nouveau SphereMesh. Dépliez la section Matériau dans l'inspecteur, puis faites glisser le fichier du dock FileSystem vers la propriété du matériau. L'objet sera mis à jour dans la fenêtre avec la couleur bleue du matériau importé.

../../../_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.