O que é GDScript na prática (para jogos 2D)
GDScript é a linguagem nativa da Godot, pensada para escrever a lógica dos nós (Nodes) dentro de uma cena. Na prática, você vai anexar scripts a nós como CharacterBody2D, Area2D, Node2D etc., e controlar comportamento, entrada do jogador, movimentação, colisões, sinais e comunicação entre partes do jogo.
Neste capítulo, o foco é aplicar fundamentos essenciais de GDScript diretamente em um contexto 2D: sintaxe, tipagem opcional, variáveis exportadas, @onready, funções, sinais, ciclo de vida (_ready, _process, _physics_process), acesso a nós com $NodePath e get_node(), além de um mini-script de validação no Player e no Enemy para depuração com prints e breakpoints.
Sintaxe essencial: variáveis, constantes e estruturas
Variáveis e constantes
Você declara variáveis com var e constantes com const. Constantes são úteis para valores que não mudam (por exemplo, nomes de grupos, caminhos, limites).
const ENEMY_GROUP := "enemies" var speed := 200.0 var lives := 3Estruturas básicas
Você vai usar if, for, while e match para controle de fluxo. Em jogos, match é ótimo para estados.
var state := "idle" match state: "idle": pass "run": pass _: push_warning("Estado desconhecido")Tipagem opcional: quando e por que usar
GDScript permite tipagem opcional. Tipar ajuda a evitar erros, melhora autocomplete e deixa o código mais legível. Você pode tipar variáveis, parâmetros e retornos.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
var speed: float = 220.0 var target: Node2D func set_speed(value: float) -> void: speed = value func get_speed() -> float: return speedBoas práticas iniciais:
- Tipar referências importantes (ex.:
Node2D,CharacterBody2D,AnimationPlayer). - Tipar valores numéricos usados em física (ex.:
floatpara velocidade). - Evitar tipar demais no começo se isso estiver atrapalhando o fluxo; priorize o que reduz bugs.
Variáveis exportadas: ajustando valores no Inspector
Variáveis exportadas aparecem no Inspector do nó, permitindo ajustar valores sem editar o script. Isso é essencial para iterar rapidamente em gameplay.
@export var move_speed: float = 220.0 @export var max_health: int = 5 @export var debug_enabled: bool = truePasso a passo prático:
- No script do Player, crie
@export var move_speed: float = 220.0. - Selecione o nó do Player na cena e, no Inspector, ajuste
move_speedpara testar diferentes sensações de movimento. - Use
debug_enabledpara ligar/desligar prints sem apagar código.
@onready: pegando referências de nós com segurança
Muitas vezes você precisa acessar nós filhos (por exemplo, um Sprite2D, AnimationPlayer, CollisionShape2D). O problema: durante a declaração do script, a árvore de nós ainda pode não estar pronta. @onready adia a atribuição até o nó entrar na árvore (momento adequado).
@onready var sprite: Sprite2D = $Sprite2D @onready var anim: AnimationPlayer = $AnimationPlayerSe o nó estiver em outro caminho:
@onready var hitbox: Area2D = $"Combat/Hitbox"Acessando nós: $NodePath vs get_node()
Usando $ (atalho)
$ é um atalho para get_node() e é prático para caminhos curtos e estáveis.
var s = $Sprite2DUsando get_node()
get_node() é útil quando o caminho é construído dinamicamente, quando você quer ser explícito, ou quando está trabalhando com NodePath exportado.
var sprite = get_node("Sprite2D") var weapon = get_node("Combat/Weapon")Padrão recomendado para reduzir acoplamento: exportar NodePath
Em vez de “fixar” caminhos no script, você pode exportar um NodePath e configurar no Inspector. Isso reduz acoplamento com a hierarquia da cena.
@export var target_path: NodePath @onready var target: Node2D = get_node(target_path) as Node2DBoas práticas iniciais para evitar acoplamento excessivo:
- Evite scripts que dependem de muitos caminhos fixos para nós distantes (ex.:
../../UI/HUD). - Prefira comunicação por sinais e grupos em vez de “caçar” nós pelo SceneTree.
- Quando precisar de referência, prefira injeção via Inspector (
@export var ...) ou via método setter.
Funções: organizando comportamento e reutilização
Funções encapsulam lógica e deixam o ciclo de vida mais limpo. Em jogos, é comum separar: leitura de input, cálculo de movimento, animação, debug.
func read_input() -> Vector2: var dir := Vector2.ZERO dir.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left") dir.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up") return dir.normalized() func apply_movement(direction: Vector2, speed: float) -> void: velocity = direction * speedCiclo de vida do nó: _ready, _process, _physics_process
_ready()
Chamado uma vez quando o nó entra na árvore e está pronto para acessar filhos. Use para inicialização, validações e conexões de sinais.
func _ready() -> void: print("Player pronto")_process(delta)
Chamado a cada frame (depende do FPS). Use para lógica que não precisa de física determinística: UI, timers simples, efeitos visuais, debug.
func _process(delta: float) -> void: if Input.is_action_just_pressed("ui_accept"): print("Apertou aceitar")_physics_process(delta)
Chamado em passos fixos (tick de física). Use para movimento e colisões, especialmente com CharacterBody2D e move_and_slide().
func _physics_process(delta: float) -> void: var dir := read_input() apply_movement(dir, move_speed) move_and_slide()Regra prática: movimento e colisão em _physics_process; o resto em _process quando fizer sentido.
Sinais (signals): comunicação sem “grudar” scripts
Sinais permitem que um nó avise outro quando algo acontece, sem precisar conhecer detalhes internos do outro. Isso reduz acoplamento e facilita trocar cenas/objetos.
Criando e emitindo um sinal
signal damaged(amount: int) func take_damage(amount: int) -> void: emit_signal("damaged", amount)Conectando sinais via código
Você pode conectar no _ready(). Isso é útil quando instâncias são criadas em runtime.
func _ready() -> void: damaged.connect(_on_damaged) func _on_damaged(amount: int) -> void: print("Recebeu dano:", amount)Conectando sinais via Editor
Também é possível conectar pela aba “Node” do Inspector (sinais do nó selecionado). Use quando a relação é estável e você quer visualizar conexões.
Implementação prática: scripts mínimos no Player e no Enemy para validar cena e depuração
Objetivo: anexar scripts simples ao Player e ao Enemy apenas para confirmar que estão rodando, que o ciclo de vida está claro e que você consegue depurar com prints e breakpoints.
Passo a passo: Player.gd (validação + movimento básico + debug)
1) Selecione o nó do Player (idealmente um CharacterBody2D) e anexe um script chamado Player.gd.
2) Cole o código abaixo. Ele inclui: tipagem, @export, @onready, uso de _ready, _process, _physics_process, acesso a nó com $, e prints controlados por flag.
extends CharacterBody2D @export var move_speed: float = 220.0 @export var debug_enabled: bool = true @onready var sprite: Sprite2D = $Sprite2D func _ready() -> void: if debug_enabled: print("[Player] _ready | name=", name, " | pos=", global_position) print("[Player] Sprite encontrado? ", sprite != null) func _process(delta: float) -> void: if debug_enabled and Input.is_action_just_pressed("ui_accept"): print("[Player] ui_accept pressionado | pos=", global_position) func _physics_process(delta: float) -> void: var dir := Vector2( Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left"), Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up") ).normalized() velocity = dir * move_speed move_and_slide()3) Execute a cena e observe o painel Output:
- Ao iniciar, deve aparecer
[Player] _ready.... - Ao apertar Enter/Espaço (dependendo do mapeamento de
ui_accept), deve aparecer o print do_process. - Ao mover com setas/WASD (dependendo do mapeamento), o Player deve se mover.
Passo a passo: Enemy.gd (validação + sinal simples)
1) Selecione o nó do Enemy (pode ser Node2D para este teste) e anexe um script chamado Enemy.gd.
2) Cole o código abaixo. Ele cria um sinal, emite um evento de “spawn” e permite simular dano com uma chamada.
extends Node2D signal spawned(enemy: Node2D) @export var debug_enabled: bool = true @export var max_health: int = 3 var health: int func _ready() -> void: health = max_health if debug_enabled: print("[Enemy] _ready | name=", name, " | health=", health) emit_signal("spawned", self) func take_damage(amount: int) -> void: health -= amount if debug_enabled: print("[Enemy] take_damage | amount=", amount, " | health=", health) if health <= 0: if debug_enabled: print("[Enemy] morreu") queue_free()Conectando Player e Enemy com baixo acoplamento (exemplo de sinal)
Para validar sinais sem criar dependências fortes, você pode conectar o sinal do Enemy a um método do Player (ou de um nó “gerenciador”) sem o Enemy precisar conhecer o Player.
Exemplo: no Player.gd, adicione um método para reagir ao spawn do Enemy:
func register_enemy(enemy: Node2D) -> void: if debug_enabled: print("[Player] Enemy spawned:", enemy.name)Agora, em algum nó que tenha referência a ambos (por exemplo, um nó pai comum na cena), conecte via código no _ready() desse nó pai:
@onready var player: Node = $Player @onready var enemy: Node = $Enemy func _ready() -> void: enemy.spawned.connect(player.register_enemy)Esse padrão evita que o Enemy procure o Player diretamente por caminho, e mantém a dependência em um ponto de composição (o nó que monta a cena).
Depuração: prints no Output e breakpoints no Debugger
Usando prints de forma controlada
Use prints com prefixos e uma flag debug_enabled exportada. Assim você liga/desliga no Inspector sem alterar código.
if debug_enabled: print("[Player] estado atual...")Alternativas úteis:
push_warning("mensagem")para avisos.push_error("mensagem")para erros (aparece com destaque).
Breakpoints (pontos de parada)
Passo a passo:
- Abra o script
Player.gdouEnemy.gd. - Clique na margem esquerda da linha (ao lado do número) para criar um breakpoint, por exemplo dentro de
_ready()outake_damage(). - Execute o jogo em modo debug.
- Quando a execução atingir a linha, o jogo pausa e o Debugger mostra variáveis locais, pilha de chamadas e permite inspecionar valores como
global_position,velocityehealth.
Dica prática de breakpoint: coloque um breakpoint dentro de _physics_process e mova o personagem; inspecione dir e velocity para confirmar se o input está sendo lido como esperado.
Checklist rápido de boas práticas iniciais
- Use
@exportpara valores de ajuste (velocidade, vida, flags de debug). - Use
@onreadypara referências a nós filhos. - Prefira sinais e composição (um nó “orquestrador”) em vez de um nó procurar outro com caminhos longos.
- Coloque movimento e colisão em
_physics_process. - Padronize prints com tags (
[Player],[Enemy]) e use breakpoints para investigar estados.