Como criar um plugin para o editor da Godot 4 (@tool e EditorPlugin)

Aprenda a criar um plugin godot para o editor: scripts @tool, EditorPlugin com dock e botão, plugin.cfg e cuidados com código que roda no editor.
Se você já fez alguns jogos na Godot e começou a repetir a mesma tarefa manual dez vezes por semana (renomear nodes, gerar colisão, posicionar spawns na mão), está na hora de parar de sofrer e escrever um plugin godot para o editor. A engine foi construída em cima da própria API que você usa, então estender o editor não é um hack: é o caminho oficial, feito em GDScript, com as mesmas classes que você já conhece. Neste tutorial eu mostro quando vale a pena, como montar a estrutura de plugin.cfg, como usar @tool no código de jogo e como criar um EditorPlugin com dock e botão sem transformar o editor num campo minado.
A ideia central é simples: existem dois mecanismos diferentes e é fácil confundir. O @tool faz um script de node rodar dentro do editor. O EditorPlugin estende o editor em si, adicionando painéis e ferramentas globais. Você vai usar um, o outro, ou os dois juntos, dependendo do que quer automatizar.
Quando vale a pena criar um plugin
Nem toda automação precisa virar plugin. Antes de abrir o plugin.cfg, faça a pergunta honesta: isso é recorrente e visual? Se é uma tarefa que você faz uma vez por projeto, um script de EditorScript rodado com Arquivo > Executar Script resolve. Plugin vale quando o ganho é contínuo.
Os casos clássicos onde compensa:
- Docks customizados. Um painel lateral para gerenciar dados do jogo, disparar builds, ou listar todos os inimigos da fase.
- Botões de ferramenta na toolbar. Ações rápidas como "gerar navmesh" ou "snap de todos os tiles selecionados".
- Gizmos e desenho no viewport. Visualizar raio de ataque, área de patrulha ou waypoints direto na cena, enquanto edita.
- Importadores customizados. Transformar um
.csvou.jsonexterno em Resource nativo da Godot no momento da importação. - Inspector plugins. Trocar o widget padrão de uma propriedade por um controle mais útil (um seletor de cor, um mapa, um botão).
Se o seu caso é nenhum desses e você só quer que um node reaja enquanto edita a cena, você provavelmente nem precisa de um plugin completo. Um @tool no script daquele node basta. Comece pelo menor mecanismo que resolve.
@tool: rodando código de jogo dentro do editor
Por padrão, o código de um script só executa quando o jogo roda. O editor apenas lê a estrutura da classe (as variáveis @export, os sinais) mas nunca chama _ready nem _process. A anotação @tool no topo do arquivo muda isso: o script passa a executar também dentro do editor.
Aqui está um node que desenha uma prévia do raio de detecção de um inimigo direto no viewport, atualizando enquanto você arrasta o valor no Inspector:
@tool
class_name DetectionArea
extends Node2D
@export var raio: float = 120.0:
set(valor):
raio = valor
queue_redraw()
@export var cor: Color = Color(1.0, 0.3, 0.3, 0.4):
set(valor):
cor = valor
queue_redraw()
func _draw() -> void:
draw_circle(Vector2.ZERO, raio, cor)
draw_arc(Vector2.ZERO, raio, 0.0, TAU, 64, Color.WHITE, 2.0)
Com o @tool presente, mudar raio no Inspector chama o setter, que dispara queue_redraw(), e o círculo se ajusta na cena na hora. Sem @tool, esse _draw só apareceria com o jogo rodando, o que é inútil para posicionar coisas no nível.
O poder do @tool vem com uma armadilha grande, e é a mais comum de todas.
Engine.is_editor_hint(): a trava de segurança obrigatória
Um script @tool roda nos dois mundos: editor e jogo. Isso significa que se o seu _ready instancia um projétil, toca um som, ou acessa um autoload que só existe em runtime, o editor vai tentar fazer isso enquanto você edita, e provavelmente quebrar ou poluir a cena com nodes fantasmas.
A defesa é Engine.is_editor_hint(), que retorna true quando o código está executando dentro do editor. Use como guarda no início de qualquer função que só faz sentido no jogo:
@tool
extends Node2D
@export var vida: int = 100
func _ready() -> void:
if Engine.is_editor_hint():
return
# Daqui pra baixo, só roda no jogo de verdade.
_conectar_sinais()
_iniciar_timers()
func _process(delta: float) -> void:
if Engine.is_editor_hint():
# No editor, talvez só atualizar a aparência.
queue_redraw()
return
_processar_input(delta)
_mover(delta)
A regra prática que evita a maioria dos bugs: no editor, um script @tool só deveria atualizar aparência (desenhar, reposicionar helpers, validar exports). Lógica de gameplay fica atrás da guarda. Quando bater dúvida se algo pode rodar no editor, assuma que não pode e proteja.
plugin.cfg: a estrutura mínima de um EditorPlugin
Quando você precisa de algo global no editor (um dock, um botão na toolbar, um importador), o @tool num node não basta. Aí entra o EditorPlugin. Todo plugin vive numa pasta dentro de res://addons/, e o editor só reconhece a pasta se ela tiver um arquivo plugin.cfg.
A estrutura fica assim:
res://addons/meu_plugin/
├── plugin.cfg
├── plugin.gd
└── dock.tscn
O plugin.cfg é um arquivo de texto simples com uma seção [plugin]:
[plugin]
name="Meu Plugin"
description="Ferramentas internas do projeto."
author="Juan Felipe"
version="1.0.0"
script="plugin.gd"
O campo script aponta para o arquivo que estende EditorPlugin, relativo à pasta do plugin. Depois de criar isso, vá em Projeto > Configurações do Projeto > Plugins e ative o plugin na lista. Se ele não aparecer ali, o problema é quase sempre o plugin.cfg no lugar errado ou com o campo script apontando para um arquivo inexistente.
EditorPlugin com dock e botão
O script principal estende EditorPlugin e trabalha com dois métodos de ciclo de vida: _enter_tree() (chamado quando o plugin é ativado) e _exit_tree() (chamado quando é desativado). A regra de ouro aqui é simétrica: tudo que você adiciona no _enter_tree você tem que remover no _exit_tree. Se esquecer, o editor acumula docks duplicados e vaza memória cada vez que você recarrega o plugin.
Repare que este arquivo não precisa de @tool: um EditorPlugin já roda no contexto do editor por definição.
extends EditorPlugin
var _dock: Control
var _botao: Button
func _enter_tree() -> void:
# Instancia um dock a partir de uma cena e o encaixa na lateral.
_dock = preload("res://addons/meu_plugin/dock.tscn").instantiate()
add_control_to_dock(DOCK_SLOT_LEFT_UR, _dock)
# Adiciona um botão na toolbar do editor.
_botao = Button.new()
_botao.text = "Gerar Spawns"
_botao.pressed.connect(_ao_clicar_gerar)
add_control_to_container(CONTAINER_TOOLBAR, _botao)
func _exit_tree() -> void:
# Desfaz tudo, na ordem inversa, para não deixar lixo no editor.
if _botao:
remove_control_from_container(CONTAINER_TOOLBAR, _botao)
_botao.queue_free()
if _dock:
remove_control_from_docks(_dock)
_dock.queue_free()
func _ao_clicar_gerar() -> void:
var raiz: Node = get_editor_interface().get_edited_scene_root()
if raiz == null:
print("Nenhuma cena aberta.")
return
_gerar_spawns(raiz)
func _gerar_spawns(raiz: Node) -> void:
for i: int in range(5):
var marcador: Marker2D = Marker2D.new()
marcador.name = "Spawn%d" % i
marcador.position = Vector2(i * 64, 0)
raiz.add_child(marcador)
# owner faz o node ser salvo junto com a cena.
marcador.owner = raiz
Dois detalhes fazem esse código funcionar de verdade no dia a dia. Primeiro, get_editor_interface().get_edited_scene_root() te dá acesso à cena que o usuário está editando, que é por onde você lê e escreve o conteúdo. Segundo, o marcador.owner = raiz: sem definir o owner, os nodes que você cria por código somem quando a cena é salva e reaberta, porque a Godot só serializa filhos que pertencem à raiz da cena. Esse é um tropeço clássico de quem começa a mexer com geração de conteúdo pelo editor.
Para uma ferramenta de produção séria, vale ainda registrar as ações no histórico de undo com get_undo_redo(), para que Ctrl+Z desfaça o que o plugin criou. Comece sem isso, valide a ideia, e adicione o undo quando a ferramenta já estiver no fluxo da equipe.
Organizando os dados que o plugin manipula
Plugins de editor quase sempre acabam lendo e escrevendo dados do jogo: listas de itens, tabelas de fase, configurações de inimigo. Em vez de encher o dock de campos soltos, a prática mais limpa é fazer o plugin operar sobre Resources customizados. Assim o mesmo dado que o jogo consome em runtime é o dado que o seu dock edita, sem duplicação. Se você ainda não usa esse padrão, vale entender antes como criar recursos customizados na Godot, porque ele é a base de qualquer ferramenta de dados decente.
Vale também um aviso de linguagem. Todo o ecossistema de plugins da Godot documentado aqui é GDScript, e para trabalho de editor ele costuma ser a escolha mais direta pela integração nativa e pelo ciclo de recarga rápido. Se o seu projeto é em C#, a API de EditorPlugin existe também, mas com algumas fricções a mais. Se você ainda está decidindo, este comparativo entre C# e GDScript na Godot ajuda a pesar os dois lados antes de investir tempo em ferramentas.
Erros comuns e como evitá-los
Alguns tropeços aparecem toda vez que alguém começa a escrever um plugin godot pela primeira vez:
- Esquecer o
@tool. O node não reage no editor e você fica achando que o código está errado, quando ele só nunca rodou. @toolsem guarda. Lógica de jogo executando no editor, criando nodes fantasmas ou travando a engine. Sempre proteja comEngine.is_editor_hint().- Não limpar no
_exit_tree. Docks e botões duplicados a cada recarga. Remova exatamente o que adicionou. - Nodes gerados que somem. Faltou setar
owner. Sem ele, a cena não salva o que o plugin criou. plugin.cfgmal posicionado. O plugin nem aparece na lista. Confira o caminhores://addons/nome/plugin.cfge o camposcript.
Comece pequeno: um único botão que faz uma coisa útil e economiza tempo real. Quando esse primeiro plugin sobreviver a uma semana de uso, você vai ter a intuição de quando escalar para docks, gizmos e importadores. E se quiser uma base sólida em GDScript e na arquitetura da engine antes de partir para ferramentas mais ambiciosas, o melhor curso de Godot cobre o caminho do jogo pronto até a extensão do editor sem pular etapas.
Fechando
Estender o editor da Godot é uma das coisas que mais mudam a produtividade de um dev intermediário, e é acessível justamente porque usa o GDScript que você já domina. Guarde o mapa mental: @tool faz um node rodar no editor, EditorPlugin estende o editor com docks e botões, plugin.cfg registra tudo, e Engine.is_editor_hint() é a trava que impede seu código de jogo de fazer estrago enquanto você edita. Com esses quatro pilares você já consegue automatizar a maior parte do trabalho repetitivo do seu projeto.
Perguntas frequentes
Qual a diferença entre um script @tool e um EditorPlugin?
Um script @tool roda seu código dentro do editor, geralmente preso a um node específico da cena. Um EditorPlugin estende o próprio editor, adicionando docks, botões, importadores e outras ferramentas globais, sem depender de nenhum node em cena.
Preciso de @tool no script do EditorPlugin?
Não. O arquivo que estende EditorPlugin já roda no contexto do editor por definição, então o @tool ali é redundante. Você usa @tool nos scripts de node que precisam executar lógica enquanto você edita a cena, como desenhar gizmos ou atualizar geometria.
Por que meu código de jogo executou dentro do editor e quebrou?
Porque um script @tool roda tanto no editor quanto no jogo. Proteja qualquer lógica que só faz sentido em runtime (física, input, timers, save) com um if Engine.is_editor_hint(): return no começo, ou separe o código em funções que só o _ready chama fora do editor.
Onde fica a pasta do plugin e como o editor a reconhece?
O plugin mora em res://addons/nome_do_plugin/ e precisa de um plugin.cfg com a seção [plugin] apontando o script principal. Depois você habilita em Projeto > Configurações do Projeto > Plugins. Sem o plugin.cfg no lugar certo, ele nem aparece na lista.
Dá para publicar meu plugin na AssetLib da Godot?
Sim. Se a pasta res://addons/seu_plugin/ estiver bem organizada e com um plugin.cfg válido, você pode enviar o repositório para a AssetLib. Vale incluir um README, uma licença e uma versão mínima da engine para evitar dor de cabeça de quem for instalar.


