Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Estensioni di importazione

Nota

Questo tutorial presuppone che si sappia già come creare estensioni generici. Se in dubbio, consultare la pagina Creare estenzioni. Si presume anche che si abbia familiarità con il sistema di importazione di Godot.

Introduzione

Un'estensione di importazione è un tipo speciale di strumento di editing che consente a Godot di importare risorse personalizzate e di trattarle come risorse di prima classe. L'editor stesso include numerose estensioni di importazione per gestire risorse comuni come immagini PNG, modelli Collada e glTF, suoni Ogg Vorbis e molti altri.

Questo tutorial mostra come creare un'estensione di importazione per caricare un file di testo personalizzato come risorsa materiale. Questo file di testo conterrà tre valori numerici separati da virgola, che rappresentano i tre canali di un colore, e il colore risultante sarà utilizzato come albedo (colore principale) del materiale importato. In questo esempio contiene il colore blu puro (zero rosso, zero verde e blu pieno):

0,0,255

Configurazione

Prima di tutto, abbiamo bisogno di un'estensione generica che gestisca l'inizializzazione e la distruzione della nostra estensione di importazione. Aggiungiamo prima il file 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"

Quindi abbiamo bisogno del file material_import.gd per aggiungere e rimuovere l'estensione di importazione quando necessario:

# 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

Quando questa estensione viene attivata, creerà una nuova istanza dell'estensione di importazione (che realizzeremo a breve) e la aggiungerà all'editor attraverso il metodo add_import_plugin(). Memorizziamo un riferimento ad essa in un membro della classe import_plugin in modo da poterla utilizzare in seguito quando la rimuoviama. Il metodo remove_import_plugin() viene chiamato quando l'estensione viene disattivata per liberare memoria e informare l'editor che l'estensione di importazione non è più disponibile.

Si noti che l'estensione di importazione è un tipo di riferimento, quindi non è necessario rilasciarlo esplicitamente dalla memoria con la funzione free(). Sarà rilasciato automaticamente dal motore quando non è più in uso.

La classe EditorImportPlugin

Il personaggio principale dello spettacolo è la classe EditorImportPlugin. È responsabile di implementare i metodi chiamati da Godot quando deve sapere come gestire i file.

Cominciamo a programmare la nostra estensione, un metodo alla volta:

# import_plugin.gd
@tool
extends EditorImportPlugin


func _get_importer_name():
    return "demos.sillymaterial"

Il primo metodo è _get_importer_name(). Questo è un nome univoco per l'estensione, utilizzato da Godot per identificare quale importazione è stata utilizzata in un determinato file. Quando i file si devono reimportare, l'editor saprà quale estensione chiamare.

func _get_visible_name():
    return "Silly Material"

Il metodo _get_visible_name() è responsabile di restituire il nome del tipo importato e sarà mostrato all'utente nel pannello di importazione.

Si dovrebbe scegliere questo nome come continuazione di "Importa come", ad esempio "Importa come Materiale ridicolo". Il nome da dargli è a piacere, ma consigliamo un nome descrittivo per la propria estensione.

func _get_recognized_extensions():
    return ["mtxt"]

Il sistema di importazione di Godot rileva i tipi di file in base alla loro estensione. Nel metodo _get_recognized_extensions() si restituisce un array di stringhe per rappresentare ciascuna estensione che questa estensione può riconoscere. Se un'estensione è riconosciuta da più di una estensione, l'utente può selezionare quale utilizzare all'importazione dei file.

Suggerimento

Estensioni comuni come .json e .txt potrebbero essere utilizzate da molte estensioni. Inoltre, potrebbero esserci file nel progetto che contengono solo dati per il gioco e non si dovrebbero importare. È necessario prestare attenzione durante l'importazione per convalidare i dati. Non aspettarsi mai che il file sia ben formato.

func _get_save_extension():
    return "material"

I file importati sono salvati nella cartella .import alla radice del progetto. La loro estensione dovrebbe corrispondere al tipo di risorsa che si sta importando, ma poiché Godot non può dire quale sarà usata (perché potrebbero esserci più estensioni valide per la stessa risorsa), è necessario dichiarare cosa sarà utilizzato nell'importazione.

Dato che stiamo importando un materiale, useremo l'estensione speciale per questo tipo di risorse. Se si importa una scena, è possibile usare scn. Le risorse generiche possono usare l'estensione res. Tuttavia, il motore non obbliga in alcun modo questa opzione.

func _get_resource_type():
    return "StandardMaterial3D"

La risorsa importata ha un tipo specifico, perciò l'editor può sapere a quale slot di proprietà appartiene. Questo consente di trascinare una risorsa dal pannello FileSystem a una proprietà nell'Ispettore.

Nel nostro caso è uno StandardMaterial3D, che può essere applicato agli oggetti 3D.

Nota

Se si devono importare tipi diversi dalla stessa estensione, è necessario creare più estensioni di importazione. Si può astrarre il codice di importazione in un altro file per evitare duplicazioni al riguardo.

Opzioni e preimpostazioni

La propria estensione può offrire diverse opzioni per consentire all'utente di controllare come la risorsa sarà importata. Se un insieme di opzioni selezionate è comune, è anche possibile creare diverse preimpostazioni per semplificare la procedura. L'immagine seguente mostra come appariranno le opzioni nell'editor:

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

Poiché potrebbero esserci molte preimpostazioni e sono identificati da un numero, è una buona norma utilizzare un enumerazione in modo da poterci fare riferimento per nome.

@tool
extends EditorImportPlugin


enum Presets { DEFAULT }


...

Ora che l'enumerazione è definita, continuiamo a esaminare i metodi di un'estensione di importazione:

func _get_preset_count():
    return Presets.size()

Il metodo _get_preset_count() restituisce il numero di preimpostazioni definite da questa estensione. Al momento abbiamo una sola preimpostazione, ma possiamo rendere questo metodo a prova di futuro restituendo la dimensione della nostra enumerazione Presets.

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

Qui abbiamo il metodo _get_preset_name(), che assegna i nomi alle preimpostazioni così come saranno presentati all'utente, quindi assicurarsi di usare nomi brevi e chiari.

Possiamo usare l'istruzione match qui per rendere il codice più strutturato. In questo modo sarà facile aggiungere nuove preimpostazioni in futuro. Usiamo anche il pattern catch-all per restituire qualcosa. Anche se Godot non richiederà preimpostazioni oltre il numero di preimpostazioni definito, è sempre meglio andare sul sicuro.

Se c'è una sola preimpostazione, è concesso restituirne direttamente il nome, ma facendo questo bisogna fare attenzione quando se ne aggiungono altri.

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

Questo è il metodo che definisce le opzioni disponibili. _get_import_options() restituisce un array di dizionari, ognuno dei quali contiene alcune chiavi che vengono verificate per personalizzare le opzioni mostrate all'utente. La tabella seguente mostra le possibili chiavi:

Chiave

Tipo

Descrizione

name

Stringa

Il nome dell'opzione. Quando viene mostrata, i trattini bassi diventano spazi e le prime lettere sono scritte in maiuscolo.

default_value

Qualsiasi

Il valore predefinito dell'opzione per questa preimpostazione.

property_hint

Valore di enumerazione

Uno dei valori di PropertyHint da usare come indicazione.

hint_string

Stringa

Il testo indicativo della proprietà. Lo stesso che si aggiungerebbe nell'istruzione export in GDScript.

usage

Valore di enumerazione

Uno dei valori di PropertyUsageFlags per definire l'utilizzo.

Le chiavi name e default_value sono obbligatorie, le altre sono facoltative.

Si noti che il metodo _get_import_options riceve il numero di preimpostazioni, in modo da poter configurare le opzioni per ogni preimpostazione (in particolare il valore predefinito). In questo esempio utilizziamo l'istruzione match, ma se si hanno molte opzioni e le preimpostazioni cambiano solo il valore, si consiglia di creare prima l'array di opzioni e poi modificarlo in base alla preimpostazione.

Avvertimento

Il metodo _get_import_options viene chiamato anche se non si definiscono preimpostazioni (facendo in modo che _get_preset_count restituisca zero). È necessario restituire un array anche se è vuoto, altrimenti si potrebbero verificare errori.

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

Per il metodo _get_option_visibility(), restituiamo semplicemente true perché tutte le nostre opzioni (vale a dire l'unica che abbiamo definito) sono visibili tutto il tempo.

Se è necessario rendere certe opzioni visibili solo se un'altra è impostata su un determinato valore, è possibile aggiungere la logica in questo metodo.

Il metodo import

La parte più pesante del processo, responsabile della conversione dei file in risorse, è gestita dal metodo _import(). Il nostro codice di esempio è un po' lungo, quindi dividiamolo in più parti:

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

La prima parte del nostro metodo di importazione apre e legge il file sorgente. Utilizziamo la classe FileAccess, passando il parametro source_file che è fornito dall'editor.

Se si verifica un errore all'apertura del file, lo restituiamo per informare l'editor che l'importazione non è avvenuta con successo.

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

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

Questo codice prende la riga del file letta in precedenza e la divide in parti separate da una virgola. Se i valori sono più o meno di tre, considera il file non valido e segnala un errore.

Poi crea una nuova variabile Color e ne imposta i valori in base al file di input. Se l'opzione use_red_anyway è abilitata, imposta invece il colore come rosso puro.

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

Questa parte crea un nuovo StandardMaterial3D che è la risorsa importata. Ne creiamo una nuova istanza e ne impostiamo il colore dell'albedo con il valore ottenuto in precedenza.

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

Questa è l'ultima parte, ed è piuttosto importante, perché qui salviamo la risorsa creata sul disco. Il percorso del file salvato viene generato e comunicato dall'editor tramite il parametro save_path. Si noti che questo percorso è senza l'estensione, quindi la aggiungiamo tramite una formattazione di stringa. Per questo, chiamiamo il metodo _get_save_extension che abbiamo definito in precedenza, in modo da essere sicuri che non si desincronizzino.

Restituiamo anche il risultato del metodo ResourceSaver.save(), quindi se si verifica un errore in questo passaggio, l'editor ne sarà a conoscenza.

Varianti di piattaforma e file generati

Si potrebbe aver notato che il nostro plugin ha ignorato due argomenti del metodo import. Questi sono argomenti di ritorno (da cui la r all'inizio del loro nome), il che significa che l'editor leggerà da essi dopo aver chiamato il metodo di importazione. Entrambi sono array che si possono riempire con informazioni.

L'argomento r_platform_variants è utilizzato se è necessario importare la risorsa in modo diverso a seconda della piattaforma di destinazione. Sebbene si chiami varianti di piattaforma, si basa sulla presenza delle tag di funzionalità, quindi anche la stessa piattaforma può avere più varianti a seconda della configurazione.

Per importare una variante di piattaforma, è necessario salvarla con il tag di funzionalità prima dell'estensione, quindi inserire il tag nell'array r_platform_variants in modo che l'editor possa sapere che è stato fatto.

Ad esempio, supponiamo di salvare un materiale diverso per una piattaforma mobile. Dovremmo fare qualcosa del genere:

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

L'argomento r_gen_files è per i file aggiuntivi generati durante il processo di importazione che si devono conservare. L'editor lo esaminerà per comprenderne le dipendenze e assicurare che il file aggiuntivo non sia eliminato inavvertitamente.

Anche questo è un array e dovrebbe contenere i percorsi completi dei file salvati. Ad esempio, creiamo un altro materiale per il prossimo passaggio e salviamolo in un file diverso:

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)

Provare l'estensione

Finora è stata tutta teoria, ma ora che l'estensione di importazione è pronta, proviamola. Assicurarsi di aver creato il file di esempio (con i contenuti descritti nella sezione introduttiva) e salvarlo come test.mtxt. Quindi attivare l'estensione nelle Impostazioni del progetto.

Se tutto va bene, l'estensione di importazione viene aggiunto all'editor e il file system viene scansionato, facendo apparire la risorsa personalizzata nel pannello FileSystem. Selezionandola e focalizzando il pannello Importazione, si può vedere l'unica opzione selezionabile.

Creare un nodo MeshInstance3D nella scena e impostare un nuovo SphereMesh per la sua proprietà Mesh. Espandere la sezione Material nell'Ispettore e trascinare il file dal pannello FileSystem alla proprietà material. L'oggetto verrà aggiornato nella viewport con il colore blu del materiale importato.

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

Andare al pannello di importazione, attivare l'opzione "Use Red Anyway" e cliccare su "Reimporta". Ciò aggiornerà il materiale importato e dovrebbe aggiornare automaticamente la vista mostrando il colore rosso.

Ed ecco fatto! La tua prima estensione di importazione è pronta! Ora sfoga la tua creatività e crea estensioni per i tuoi formati preferiti. Possono essere molto utili per scrivere i propri dati in un formato personalizzato e poi utilizzarli in Godot come se fossero risorse native. Dimostra quanto il sistema di importazione sia potente ed estensibile.