Godot do Zero: Fundamentos de GDScript aplicados ao 2D

Capítulo 3

Tempo estimado de leitura: 10 minutos

+ Exercício

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 := 3

Estruturas 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.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

var speed: float = 220.0 var target: Node2D func set_speed(value: float) -> void:     speed = value func get_speed() -> float:     return speed

Boas práticas iniciais:

  • Tipar referências importantes (ex.: Node2D, CharacterBody2D, AnimationPlayer).
  • Tipar valores numéricos usados em física (ex.: float para 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 = true

Passo 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_speed para testar diferentes sensações de movimento.
  • Use debug_enabled para 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 = $AnimationPlayer

Se 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 = $Sprite2D

Usando 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 Node2D

Boas 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 * speed

Ciclo 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.gd ou Enemy.gd.
  • Clique na margem esquerda da linha (ao lado do número) para criar um breakpoint, por exemplo dentro de _ready() ou take_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, velocity e health.

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 @export para valores de ajuste (velocidade, vida, flags de debug).
  • Use @onready para 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.

Agora responda o exercício sobre o conteúdo:

Em um jogo 2D na Godot, qual é a abordagem mais adequada para reduzir acoplamento ao obter referências entre nós (ex.: Player e Enemy)?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Exportar NodePath (injeção via Inspector) e/ou usar sinais com um nó que compõe a cena evita que um nó “cace” outro por caminhos longos, reduzindo dependências rígidas e facilitando manutenção.

Próximo capitúlo

Godot do Zero: Entrada do jogador e mapeamento de controles (Input Map)

Arrow Right Icon
Capa do Ebook gratuito Godot do Zero: Criando seu Primeiro Jogo 2D com GDScript
18%

Godot do Zero: Criando seu Primeiro Jogo 2D com GDScript

Novo curso

17 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.