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.

Importar plugins

Nota

Este tutorial pressupõe que você já sabe como fazer plugins genéricos. Em caso de dúvida, consulte a página Criando plugins. Isso também pressupõe que você está familiarizado com o sistema de importação do Godot.

Introdução

Um plugin de importação é um tipo especial de ferramenta do editor que permite que recursos personalizados sejam importados pelo Godot e tratados como recursos de primeira classe. O próprio editor vem acompanhado de vários plugins de importação para lidar com os recursos comuns, como imagens PNG, modelos Collada e glTF, sons Ogg Vorbis e muitos outros.

Este tutorial mostra como criar um plugin de importação para carregar um arquivo de texto personalizado como um recurso de material. Este arquivo de texto conterá três valores numéricos separados por vírgula, que representam os três canais de uma cor, e a cor resultante será usada como o albedo (cor principal) do material importado. Neste exemplo, ele contém a cor azul pura (zero de vermelho, zero de verde e azul total):

0,0,255

Configuração

Primeiro, precisamos de um plugin genérico que lidará com a inicialização e destruição do nosso plugin de importação. Vamos adicionar o arquivo plugin.cfg primeiro:

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

Depois, precisamos do arquivo material_import.gd para adicionar e remover o plugin de importação quando necessário:

# 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 este plugin for ativado, ele criará uma nova instância do plugin de importação (que faremos em breve) e a adicionará ao editor usando o método add_import_plugin(). Armazenamos uma referência a ela em um membro da classe import_plugin para que possamos nos referir a ela mais tarde ao removê-la. O método remove_import_plugin() é chamado quando o plugin é desativado para limpar a memória e informar ao editor que o plugin de importação não está mais disponível.

Observe que o plugin de importação é um tipo de referência, portanto, não precisa ser explicitamente liberado da memória com a função free(). Ele será liberado automaticamente pelo mecanismo quando sair do escopo.

A classe EditorImportPlugin

O personagem principal do show é a classe EditorImportPlugin. Ela é responsável por implementar os métodos que são chamados pelo Godot quando ele precisa saber como lidar com os arquivos.

Vamos começar a programar nosso plugin, um método de cada vez:

# import_plugin.gd
@tool
extends EditorImportPlugin


func _get_importer_name():
    return "demos.sillymaterial"

O primeiro método é o _get_importer_name(). Este é um nome exclusivo para o seu plugin que é usado pelo Godot para saber qual importador foi usado em um determinado arquivo. Quando os arquivos precisarem ser reimportados, o editor saberá qual plugin chamar.

func _get_visible_name():
    return "Silly Material"

O método _get_visible_name() é responsável por retornar o nome do tipo que ele importa, e ele será mostrado ao usuário no painel de Importação.

Você deve escolher este nome como uma continuação para "Importar como", por exemplo, "Importar como Silly Material". Você pode nomeá-lo como quiser, mas recomendamos um nome descritivo para o seu plugin.

func _get_recognized_extensions():
    return ["mtxt"]

O sistema de importação do Godot detecta os tipos de arquivos por sua extensão. No método _get_recognized_extensions(), você retorna um array de strings para representar cada extensão que este plugin pode entender. Se uma extensão for reconhecida por mais de um plugin, o usuário poderá selecionar qual usar ao importar os arquivos.

Dica

Extensões comuns como .json e .txt podem ser usadas por muitos plugins. Além disso, pode haver arquivos no projeto que são apenas dados para o jogo e não devem ser importados. Você deve ter cuidado ao importar para validar os dados. Nunca espere que o arquivo esteja bem formatado.

func _get_save_extension():
    return "material"

Os arquivos importados são salvos na pasta .import na raiz do projeto. A extensão deles deve corresponder ao tipo de recurso que você está importando, mas como o Godot não pode prever o que você usará (porque pode haver múltiplas extensões válidas para o mesmo recurso), você precisa declarar o que será usado na importação.

Como estamos importando um Material, usaremos a extensão especial para esses tipos de recursos. Se você estiver importando uma cena, pode usar scn. Recursos genéricos podem usar a extensão res. No entanto, isso não é obrigatório de forma alguma pelo motor.

func _get_resource_type():
    return "StandardMaterial3D"

O recurso importado possui um tipo específico, para que o editor possa saber a qual slot de propriedade ele pertence. Isso permite arrastar e soltar do painel do Sistema de Arquivos para uma propriedade no Inspetor.

No nosso caso, é um StandardMaterial3D, que pode ser aplicado a objetos 3D.

Nota

Se você precisar importar diferentes tipos a partir da mesma extensão, terá que criar múltiplos plugins de importação. Você pode abstrair o código de importação em outro arquivo para evitar a duplicação nesse aspecto.

Opções e predefinições

Seu plugin pode fornecer opções diferentes para permitir que o usuário controle como o recurso será importado. Se um conjunto de opções selecionadas for comum, você também pode criar predefinições (presets) diferentes para facilitar o uso pelo usuário. A imagem a seguir mostra como as opções aparecerão no editor:

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

Como pode haver muitas predefinições e elas são identificadas por um número, é uma boa prática usar um enum para que você possa se referir a elas usando nomes.

@tool
extends EditorImportPlugin


enum Presets { DEFAULT }


...

Agora que o enum está definido, vamos continuar examinando os métodos de um plugin de importação:

func _get_preset_count():
    return Presets.size()

O método _get_preset_count() retorna a quantidade de predefinições que este plugin define. Temos apenas uma predefinição agora, mas podemos tornar este método preparado para o futuro retornando o tamanho da nossa enumeração Presets.

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

Aqui temos o método _get_preset_name(), que dá nomes às predefinições conforme elas serão apresentadas ao usuário, portanto certifique-se de usar nomes curtos e claros.

Podemos usar a instrução match aqui para tornar o código mais estruturado. Dessa forma, é fácil adicionar novas predefinições no futuro. Usamos o padrão catch-all para retornar algo também. Embora o Godot não peça predefinições além da contagem definida, é sempre melhor prevenir.

Se você tiver apenas uma predefinição, poderia simplesmente retornar o nome dela diretamente, mas se fizer isso, terá que ter cuidado ao adicionar mais predefinições.

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

Este é o método que define as opções disponíveis. O _get_import_options() retorna um array de dicionários, e cada dicionário contém algumas chaves que são verificadas para personalizar a opção conforme ela é mostrada ao usuário. A tabela a seguir mostra as chaves possíveis:

Chave

Tipo

Descrição

name

String

O nome da opção. Quando exibido, os sublinhados tornam-se espaços e as primeiras letras ficam em maiúsculas.

default_value

Qualquer

O valor padrão da opção para esta predefinição.

property_hint

Valor de enumeração

Um dos valores de PropertyHint para usar como dica.

hint_string

String

O texto de dica da propriedade. O mesmo que você adicionaria na instrução export no GDScript.

usage

Valor de enumeração

Um dos valores de PropertyUsageFlags para definir o uso.

As chaves name e default_value são obrigatórias, o resto é opcional.

Observe que o método _get_import_options recebe o número da predefinição, para que você possa configurar as opções para cada predefinição diferente (especialmente o valor padrão). Neste exemplo, usamos a instrução match, mas se você tiver muitas opções e as predefinições mudarem apenas o valor, você pode preferir criar o array de opções primeiro e depois alterá-lo com base na predefinição.

Aviso

O método _get_import_options é chamado mesmo se você não definir predefinições (fazendo com que _get_preset_count retorne zero). Você deve retornar um array, mesmo que esteja vazio, caso contrário poderá receber erros.

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

Para o método _get_option_visibility(), simplesmente retornamos true porque todas as nossas opções (ou seja, a única que definimos) estão visíveis o tempo todo.

Se você precisar tornar uma determinada opção visível apenas se outra estiver configurada com um determinado valor, poderá adicionar a lógica neste método.

O método import

A parte pesada do processo, responsável por converter os arquivos em recursos, é coberta pelo método _import(). Nosso código de exemplo é um pouco longo, então vamos dividi-lo em algumas partes:

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

A primeira parte do nosso método de importação abre e lê o arquivo de origem. Usamos a classe FileAccess para fazer isso, passando o parâmetro source_file que é fornecido pelo editor.

Se houver um erro ao abrir o arquivo, nós o retornamos para que o editor saiba que a importação não foi bem-sucedida.

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

Este código pega a linha do arquivo lida anteriormente e a divide em partes separadas por uma vírgula. Se houver mais ou menos do que os três valores, ele considera o arquivo inválido e relata um erro.

Em seguida, ele cria uma nova variável Color e define seus valores de acordo com o arquivo de entrada. Se a opção use_red_anyway estiver ativada, ele definirá a cor como vermelho puro.

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

Esta parte cria um novo StandardMaterial3D que é o recurso importado. Criamos uma nova instância dele e definimos sua cor albedo como o valor que obtivemos antes.

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

Esta é a última parte e uma bastante importante, pois é aqui que salvamos o recurso criado no disco. O caminho do arquivo salvo é gerado e informado pelo editor por meio do parâmetro save_path. Observe que este vem sem a extensão, por isso a adicionamos usando a formatação de string. Para isso, chamamos o método _get_save_extension que definimos anteriormente, garantindo que eles não fiquem fora de sincronia.

Também retornamos o resultado do método ResourceSaver.save(), para que, se houver um erro nesta etapa, o editor saiba disso.

Variantes de plataforma e arquivos gerados

Você deve ter notado que nosso plugin ignorou dois argumentos do método import. Esses são argumentos de retorno (daí o r no início do nome), o que significa que o editor lerá a partir deles após chamar seu método de importação. Ambos são arrays que você pode preencher com informações.

O argumento r_platform_variants é usado se você precisar importar o recurso de maneira diferente dependendo da plataforma de destino. Embora seja chamado de variantes de plataforma, ele se baseia na presença de tags de recursos (feature tags), de modo que até mesmo a mesma plataforma pode ter múltiplas variantes dependendo da configuração.

Para importar uma variante de plataforma, você precisa salvá-la com a tag de recurso antes da extensão e, em seguida, enviar a tag para o array r_platform_variants para que o editor saiba que você fez isso.

Por exemplo, digamos que salvamos um material diferente para uma plataforma móvel. Precisaríamos fazer algo parecido com o seguinte:

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

O argumento r_gen_files é destinado a arquivos extras que são gerados durante o seu processo de importação e precisam ser mantidos. O editor olhará para ele para entender as dependências e garantir que o arquivo extra não seja excluído inadvertidamente.

Este também é um array e deve ser preenchido com os caminhos completos dos arquivos que você salvar. Como exemplo, vamos criar outro material para o próximo passe e salvá-lo em um arquivo diferente:

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)

Testando o plugin

Isso tudo foi teórico, mas agora que o plugin de importação está pronto, vamos testá-lo. Certifique-se de ter criado o arquivo de exemplo (com o conteúdo descrito na seção de introdução) e salve-o como test.mtxt. Em seguida, ative o plugin nas Configurações do Projeto.

Se tudo correr bem, o plugin de importação é adicionado ao editor e o sistema de arquivos é verificado, fazendo com que o recurso personalizado apareça no dock FileSystem. Se você selecioná-lo e focar no dock Import, poderá ver a única opção para selecionar lá.

Crie um nó MeshInstance3D na cena e, para sua propriedade Mesh, configure uma nova SphereMesh. Desdobre a seção Material no Inspetor e arraste o arquivo do painel Sistema de Arquivos para a propriedade de material. O objeto será atualizado na janela de visualização com a cor azul do material importado.

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

Vá para o painel de Importação, ative a opção "Use Red Anyway" (Usar Vermelho de Qualquer Forma) e clique em "Reimport" (Reimportar). Isso atualizará o material importado e deve atualizar automaticamente a visualização mostrando a cor vermelha em seu lugar.

E é isso! Seu primeiro plugin de importação está pronto! Agora use a criatividade e faça plugins para os seus próprios formatos favoritos. Isso pode ser muito útil para escrever seus dados em um formato personalizado e depois usá-los no Godot como se fossem recursos nativos. Isso mostra o quão poderoso e extensível é o sistema de importação.