@export no Godot: variaveis no Inspector explicadas

Domine o @export no Godot 4: range, enum, grupos, NodePath, PackedScene e Resource. Ajuste gameplay pelo Inspector sem abrir codigo. Guia de export godot.
@export no Godot: variaveis no Inspector explicadas
Se tem uma anotação que muda a forma de trabalhar no Godot, é o @export. O export godot pega uma variável do seu script e transforma num campo editável no Inspector: velocidade vira um número que você muda com o jogo aberto do lado, vida máxima vira um slider, o tipo de inimigo vira um dropdown. E o melhor: cada instância da cena pode ter um valor diferente, sem duplicar uma linha de código.
Eu demorei pra dar o devido valor a isso quando comecei. Achava que era só conveniência, um jeito chique de evitar abrir o script. Não é. O @export é a fronteira entre programar o jogo e ajustar o jogo, e separar essas duas coisas é o que permite balancear gameplay rápido, reaproveitar a mesma cena em dez contextos e até deixar alguém que não programa mexer nos números. Nesse artigo eu passo pelo básico, pelos hints mais úteis (@export_range, @export_enum, @export_group) e pelos exports de NodePath, PackedScene e Resource. Tudo em GDScript do Godot 4.x.
O export godot básico: tipo define o campo
A forma mínima é uma anotação antes da declaração da variável:
extends CharacterBody2D
@export var speed: float = 200.0
@export var max_health: int = 100
@export var player_name: String = "Heroi"
@export var can_double_jump: bool = false
Salvou o script, o Inspector do node já mostra os quatro campos. O tipo da variável define o widget: float e int viram campos numéricos, String vira caixa de texto, bool vira checkbox. Por isso vale sempre tipar a variável exportada, ou com a anotação de tipo (: float) ou com um valor inicial que deixe o tipo óbvio. @export var speed sem tipo nem valor não funciona, o Godot precisa saber o que desenhar no Inspector.
Dois comportamentos que confundem todo iniciante:
O valor do Inspector vence o valor do script. O = 200.0 é só o padrão. Se você mudou pra 350 no Inspector, é 350 que vale, mesmo que depois edite o script pra 500. Quando um número "não muda de jeito nenhum", olhe o Inspector: provavelmente tem um valor sobrescrito ali (ele aparece com um ícone de reverter ao lado).
Cada instância guarda o próprio valor. Esse é o superpoder. Uma cena enemy.tscn com @export var speed vira inimigo lento, médio e rápido só arrastando três instâncias pra fase e mudando o número de cada uma. Mesma cena, mesmo script, três comportamentos.
E o uso é o de qualquer variável:
func _physics_process(delta):
var direction = Input.get_axis("move_left", "move_right")
velocity.x = direction * speed
move_and_slide()
Se você ainda está se acostumando com variáveis e tipos no GDScript, o guia de GDScript do zero cobre essa base antes de chegar aqui.
@export_range: slider com limites
Número solto no Inspector é convite pra valor absurdo. O @export_range transforma o campo num slider com mínimo e máximo:
@export_range(0, 500) var speed: float = 200.0
@export_range(0.0, 1.0, 0.05) var drop_chance: float = 0.25
@export_range(1, 10, 1) var enemy_level: int = 1
O terceiro argumento é o passo do slider. No drop_chance, cada arrastada anda de 0.05 em 0.05, o que evita aquele 0.2499999 que ninguém pediu.
Tem dois sufixos que eu uso direto. O "or_greater" deixa digitar acima do máximo (o slider para no limite, mas o campo aceita mais):
@export_range(0, 100, 1, "or_greater") var max_health: int = 100
E o "suffix:..." mostra a unidade no campo, o que parece detalhe mas evita confusão de quem balanceia:
@export_range(0, 1000, 10, "suffix:px/s") var speed: float = 300.0
A regra que eu sigo: todo número exportado que tem limite lógico ganha range. Chance de drop nunca passa de 1.0, velocidade nunca é negativa. O range documenta a intenção e impede o erro antes dele acontecer.
@export_enum: dropdown em vez de string mágica
Quando a variável só aceita um conjunto fixo de opções, exportar uma String solta é pedir typo. O @export_enum vira um dropdown:
@export_enum("Melee", "Ranged", "Magic") var attack_type: String = "Melee"
No Inspector aparece uma lista com as três opções, impossível digitar "Rangd" sem querer. Com int, cada opção vira um índice (Melee é 0, Ranged é 1, Magic é 2), e dá pra fixar os valores com dois pontos:
@export_enum("Lento:10", "Normal:25", "Rapido:50") var speed_tier: int = 25
Se você já tem um enum declarado no script, melhor ainda, exporta direto:
enum EnemyState { PATROL, CHASE, ATTACK }
@export var initial_state: EnemyState = EnemyState.PATROL
O Godot monta o dropdown com os nomes do enum sozinho. É o meu formato favorito: o código compara contra EnemyState.CHASE em vez de string, e o Inspector continua amigável.
@export_group: Inspector organizado escala melhor
Um script de player de verdade exporta facilmente quinze variáveis. Sem organização, o Inspector vira uma lista plana onde ninguém acha nada. O @export_group cria seções recolhíveis:
extends CharacterBody2D
@export_group("Movimento")
@export var speed: float = 300.0
@export var acceleration: float = 1500.0
@export var friction: float = 1200.0
@export_group("Pulo")
@export var jump_velocity: float = -400.0
@export var coyote_time: float = 0.1
@export_range(1, 3) var max_jumps: int = 1
@export_group("Combate")
@export var max_health: int = 100
@export var invincibility_time: float = 1.0
Tudo que vem depois de um @export_group entra naquele grupo, até o próximo grupo aparecer. Pra subdivisões dentro de um grupo existe o @export_subgroup, e pra um título sem recolhimento, o @export_category. Na prática, @export_group resolve 95% dos casos.
Parece cosmético, e em script de cinco variáveis é mesmo. Mas o ponto do export é justamente outra pessoa (ou você daqui a três meses) abrir o Inspector e ajustar o jogo sem ler o código. Um Inspector agrupado é a interface desse trabalho.
Exportando NodePath, PackedScene e Resource
Aqui o @export deixa de ser "número editável" e vira ferramenta de arquitetura.
Node e NodePath. No Godot 4 dá pra exportar uma referência de node direto, e o Inspector deixa você arrastar o node da cena pro campo:
@export var health_bar: ProgressBar
@export var muzzle: Marker2D
func shoot():
var bullet = BULLET_SCENE.instantiate()
bullet.global_position = muzzle.global_position
get_tree().current_scene.add_child(bullet)
Isso substitui o @onready var health_bar = $UI/HUD/HealthBar com caminho longo e frágil. Se a UI for reorganizada, o caminho hardcoded quebra; a referência exportada o editor atualiza sozinho. Quando você precisa do caminho em si (pra passar adiante, por exemplo), exporta NodePath e resolve com get_node():
@export var target_path: NodePath
@onready var target = get_node(target_path)
PackedScene. Exportar uma cena é o padrão de todo spawner genérico:
extends Node2D
@export var enemy_scene: PackedScene
@export_range(0.5, 10.0, 0.5) var spawn_interval: float = 2.0
func _on_spawn_timer_timeout():
var enemy = enemy_scene.instantiate()
enemy.global_position = global_position
get_tree().current_scene.add_child(enemy)
Esse spawner não sabe o que spawna. Você arrasta slime.tscn numa instância, bat.tscn em outra, e o mesmo script vira dois spawners diferentes. Sem @export, isso seria um preload() hardcoded e um script novo por tipo de inimigo.
Resource. O nível seguinte: exportar um Resource customizado e transformar dados de gameplay em arquivos:
@export var weapon_data: WeaponData
func attack():
deal_damage(weapon_data.damage)
Onde WeaponData é uma classe que estende Resource com damage, fire_rate e o que mais a arma precisar. Cada arma vira um arquivo .tres editável no Inspector, e balancear o jogo inteiro vira editar arquivos de dados, não código. Esse padrão merece artigo próprio, e tem: Resources customizados no Godot.
Outros hints que valem conhecer de passagem: @export_file("*.json") pra caminho de arquivo, @export_multiline pra texto longo (diálogo, descrição), @export_color_no_alpha pra cor sem canal alfa, e exportar Array[PackedScene] quando o spawner precisa sortear entre várias cenas.
Por que isso muda o jeito de fazer jogo
O argumento que eu dei na introdução merece ser fechado. Balancear um jogo é um trabalho de iteração: muda o número, joga trinta segundos, muda de novo. Se cada ajuste exige abrir script, achar a constante e salvar, a iteração desacelera e você ajusta menos do que devia. Com tudo exportado, você roda o jogo, ajusta no Inspector da cena e testa de novo em segundos.
Tem um efeito colateral importante em equipe: designer ajusta gameplay sem encostar em código. O programador define o que é ajustável (e os limites, via range), e quem balanceia trabalha só no Inspector e nos arquivos .tres. Cada um no seu terreno, ninguém quebra o do outro.
Meu critério pra decidir o que exportar: se é um valor que eu mudaria durante o balanceamento, exporta. Se é detalhe interno de implementação (um acumulador, uma flag de estado), fica privado. Exportar tudo indiscriminadamente polui o Inspector e convida a mexer onde não devia; exportar nada engessa a iteração. O meio termo é exportar o que descreve o comportamento, não o que implementa ele.
Fechando
O @export básico cobre número, texto e booleana; @export_range impõe limites e vira slider; @export_enum troca string mágica por dropdown; @export_group mantém o Inspector legível quando o script cresce. E os exports de Node, PackedScene e Resource são onde a anotação vira arquitetura: cenas genéricas configuradas por instância e gameplay descrito em dados.
Pra fixar, pegue um script seu que tenha constantes de gameplay hardcoded e converta uma a uma em variáveis exportadas, com range e grupo. Depois rode o jogo e balanceie só pelo Inspector. A diferença na velocidade de iteração aparece na primeira sessão, e a partir daí é difícil voltar atrás.


