Sebenta · Videojogos com Godot (UC02841)
- Introdução
- 1. Game dev landscape
- 2. Setup
- 3. Conceitos fundamentais
- 4. GDScript
- 5. Sinais
- 6. Player movement — CharacterBody2D
- 7. Colisões
- 8. Cenas e instâncias
- 9. Animação
- 10. UI
- 11. Autoloads (Singletons)
- 12. Áudio
- 13. Save / Load
- 14. Export e distribuição
- 15. Boas práticas
- 16. Recursos para aprender
- 17. Próximos passos
- Glossário
Introdução
Esta UC é a tua entrada no mundo do desenvolvimento de jogos. Vamos usar Godot 4, o motor open source que cresceu rapidamente para se tornar a alternativa Indie séria a Unity e Unreal. Em 50 horas vais criar jogos 2D funcionais — platformer, top-down shooter, arcade — e dominar conceitos fundamentais que se aplicam a qualquer engine.
Os jogos vivem na intersecção de tudo: programação, design, arte, som, física, narrativa, UX. É das áreas mais multidisciplinares que existem. E é também das mais gratificantes — ver alguém divertir-se com algo que tu fizeste é único.
Esta sebenta cobre Godot 4 com GDScript (a linguagem nativa, parecida com Python). Versão alternativa em C# disponível para Unity ou Godot .NET.
1. Game dev landscape
Motores principais em 2026
Unity - C#, ecossistema gigante, Asset Store. - Royalties controvérsia (2023) afastou muitos indies. - Continua dominante mobile e algumas indie.
Unreal Engine - C++ + Blueprints (visual scripting). - AAA: Fortnite, Tekken, Lies of P. - Royalties 5% após $1M. - Excelente para 3D foto-realista.
Godot - Open source MIT (zero royalties). - GDScript, C#, ou C++. - Mais leve, mais rápido a arrancar. - Comunidade em forte crescimento.
GameMaker - GML (proprietária). - 2D-first. - Hyper Light Drifter, Undertale.
Outras: Construct (no-code), Bevy (Rust), Defold, Ren'Py (visual novels), Phaser (HTML5).
Esta UC: Godot
Por que Godot: - Gratuito sem catch. - Editor leve (50MB). - GDScript fácil para iniciantes (Python-like). - Excelente 2D. - Cross-platform (PC, mobile, web, console). - Comunidade activa.
Se preferires Unity ou Unreal, conceitos transferíveis 90%.
2. Setup
Download
godotengine.org → Download Godot 4.x: - Standard — GDScript. - .NET — também C# (se preferes).
Executável único (~50MB). Não precisa instalador na maioria dos SOs.
Project Manager
Ao abrir Godot, vês a lista de projectos. Cria novo: - Project Path: pasta vazia. - Renderer: Forward+ (PC) ou Mobile (perf inferior, mobile-friendly). - Version Control: Git (recomendado).
Editor
Áreas principais: - 2D / 3D / Script views (topo). - Scene tree (esquerda) — hierarquia dos nodes. - Inspector (direita) — propriedades do node seleccionado. - FileSystem (esquerda inferior) — ficheiros do projecto. - Bottom panels — Output, Debugger, Audio, Animation, etc.
3. Conceitos fundamentais
Scene
Uma scene é uma colecção de nodes organizados em árvore — pode ser: - Um nível inteiro. - Um menu. - Um player (reutilizável em vários níveis). - Uma UI.
Composition over inheritance — combinas scenes pequenas em scenes maiores.
Node
Tudo em Godot é um Node. Cada node tem: - Nome. - Posição/transform (se aplicável). - Filhos (outros nodes). - Tipo (centenas, hierarquia).
Tipos comuns:
- Node — base abstracta.
- Node2D — base para 2D (transform).
- Node3D — base para 3D.
- Control — base para UI.
- CanvasLayer — camada sobre o mundo (HUD).
Tree hierárquica
MainScene (Node2D)
├── World (Node2D)
│ ├── Background (TextureRect)
│ ├── Player (CharacterBody2D)
│ │ ├── Sprite2D
│ │ └── CollisionShape2D
│ ├── Enemies (Node2D)
│ │ ├── Enemy (CharacterBody2D)
│ │ └── ...
│ └── Coins (Node2D)
│ └── ...
├── UI (CanvasLayer)
│ ├── ScoreLabel (Label)
│ └── LivesIcons (HBoxContainer)
└── Audio (AudioStreamPlayer)
Children herdam transform do parent (mover MainScene move tudo).
Componentes vs Composition
Godot não tem "Component pattern" tradicional (como Unity). Em vez disso, compor com nodes filhos:
Player (CharacterBody2D)
├── Sprite2D # visual
├── CollisionShape2D # física
├── HitBox (Area2D) # detectar dano
├── Hurtbox (Area2D) # causar dano
└── AnimationPlayer # animações
4. GDScript
GDScript é a linguagem nativa do Godot. Inspirada em Python, mas otimizada para games.
Hello world
extends Node2D # estende o tipo de node
func _ready():
print("Cena pronta!")
_ready() é chamado automaticamente quando o node entra na cena.
Variáveis
var nome = "Player" # tipo inferido
var vida: int = 100 # explícito
var velocidade: float = 200.0
var ativo: bool = true
var posicao: Vector2 = Vector2(100, 50)
var inimigos: Array = []
var stats: Dictionary = {"hp": 100, "mp": 50}
const MAX_VIDA = 100 # constante
@export var velocidade_max: float = 300.0 # editável no Inspector
@export_range(0, 100) var dano: int = 10 # com range slider
Funções
func somar(a: int, b: int) -> int:
return a + b
# Sem retorno
func morrer():
print("Morri")
queue_free()
# Default args
func atacar(alvo, dano: int = 10):
alvo.sofrer_dano(dano)
Funções especiais (lifecycle)
func _ready():
# node entrou na cena
pass
func _process(delta: float):
# cada frame; delta = segundos desde último frame
pass
func _physics_process(delta: float):
# cada physics tick (60Hz por defeito), fixo
# USAR ISTO para movimento e física
pass
func _input(event: InputEvent):
# qualquer input
pass
func _unhandled_input(event):
# input não consumido por UI
pass
func _exit_tree():
# antes de sair da cena
pass
Estruturas de controlo
# If
if vida <= 0:
morrer()
elif vida < 30:
avisar()
else:
continuar()
# Match (mais poderoso que switch)
match estado:
"idle":
idle()
"moving", "running": # múltiplos
mover()
var x when x > 5:
print("Maior que 5")
_: # default
print("Desconhecido")
# For
for i in range(10):
print(i)
for inimigo in inimigos:
inimigo.atacar()
for chave in stats:
print(chave, " = ", stats[chave])
# While
while vida > 0:
sofrer_dano(1)
Tipos importantes
Vector2 (e Vector3 para 3D):
var v = Vector2(100, 50)
v.x = 200
var u = Vector2(10, 20)
var soma = v + u # (210, 70)
var distancia = v.distance_to(u)
var normalizado = v.normalized() # comprimento 1
Color:
var c = Color(1, 0, 0) # RGB 0-1
var c2 = Color("red")
var c3 = Color("#ff0000")
c.a = 0.5 # alpha
Array e Dictionary: como Python.
5. Sinais
Signals = eventos. Mecanismo principal de comunicação entre nodes.
Built-in signals
Muitos nodes emitem signals nativos:
# Button
func _ready():
$StartButton.pressed.connect(_on_start_pressed)
func _on_start_pressed():
print("Start!")
# Area2D
func _ready():
body_entered.connect(_on_body_entered)
func _on_body_entered(body):
print("Algo entrou: ", body.name)
Custom signals
# Definir
signal vida_mudou(nova_vida)
signal morreu
# Emitir
func sofrer_dano(d):
vida -= d
vida_mudou.emit(vida)
if vida <= 0:
morreu.emit()
# Conectar (de outro node)
$Player.vida_mudou.connect(_atualizar_hud)
$Player.morreu.connect(_game_over)
Sinais são loose coupling — quem emite não sabe quem ouve.
6. Player movement — CharacterBody2D
Base
CharacterBody2D é o node para player com física manual (queremos controlo total).
extends CharacterBody2D
const SPEED = 300.0
const JUMP_VELOCITY = -400.0
const GRAVITY = 980.0
func _physics_process(delta):
# Gravidade
if not is_on_floor():
velocity.y += GRAVITY * delta
# Jump
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Movimento horizontal
var direction = Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()
Input Map
Project → Project Settings → Input Map:
- ui_left ← left arrow + A
- ui_right ← right arrow + D
- ui_accept ← space + enter
- attack ← X
- jump ← space (se quiseres separar de ui_accept)
Em código:
Input.is_action_pressed("ui_left") # enquanto premido
Input.is_action_just_pressed("attack") # frame do click
Input.is_action_just_released("attack") # frame do release
Input.get_axis("ui_left", "ui_right") # -1, 0 ou 1
Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") # Vector2
Top-down 4-direcções
extends CharacterBody2D
const SPEED = 250
func _physics_process(delta):
var input_dir = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
velocity = input_dir * SPEED
move_and_slide()
7. Colisões
CollisionShape2D
Cada body de física precisa de uma CollisionShape2D filho:
- RectangleShape2D — caixa.
- CircleShape2D — círculo.
- CapsuleShape2D — pílula.
- ConvexPolygonShape2D — polígono custom.
Em geral, círculos e caixas são mais rápidos que polígonos.
Tipos de body
- StaticBody2D: imóvel (chão, paredes).
- CharacterBody2D: controlado por código (player, inimigos lógicos).
- RigidBody2D: simulado pela física (caixas, bolas).
- Area2D: detecta entrada/saída, sem sólido (triggers, coletáveis).
Layers e masks
Project → Project Settings → Layer Names → 2D Physics. Define layers semânticas:
- Layer 1: World
- Layer 2: Player
- Layer 3: Enemies
- Layer 4: Bullets
Cada body tem: - Collision Layer: onde está. - Collision Mask: o que detecta.
Player no layer 2, masks com 1+3 (vê world e enemies). Bullet no layer 4, mask com 3 (só vê enemies).
Area2D para coletáveis
extends Area2D
func _ready():
body_entered.connect(_on_body_entered)
func _on_body_entered(body):
if body.name == "Player":
Game.score += 10
queue_free()
8. Cenas e instâncias
Cena reutilizável
Cria scene "Enemy.tscn":
Enemy (CharacterBody2D)
├── Sprite2D
├── CollisionShape2D
└── HealthBar (ProgressBar)
Instanciar em código
const EnemyScene = preload("res://Enemy.tscn")
func spawn_enemy(pos: Vector2):
var e = EnemyScene.instantiate()
e.position = pos
add_child(e)
Spawn com timer
@onready var timer = $SpawnTimer
func _ready():
timer.timeout.connect(_on_spawn_timer)
timer.start(2.0) # cada 2s
func _on_spawn_timer():
spawn_enemy(get_random_position())
9. Animação
AnimatedSprite2D
- Criar SpriteFrames resource (no Inspector).
- Adicionar animações: "idle", "run", "jump", "die".
- Importar frames (de spritesheet ou imagens individuais).
extends CharacterBody2D
@onready var sprite = $AnimatedSprite2D
func _physics_process(delta):
# ... movimento ...
if not is_on_floor():
sprite.play("jump")
elif velocity.x != 0:
sprite.play("run")
sprite.flip_h = velocity.x < 0
else:
sprite.play("idle")
AnimationPlayer
Para animações mais complexas (transições, tweens, callbacks):
- Criar tracks de propriedades (posição, modulate, etc).
- Keyframes em tempos específicos.
- Pode chamar funções via track "Call Method".
Tweens (programáticas)
# Fade out + free
func morrer():
var tween = create_tween()
tween.tween_property(self, "modulate:a", 0.0, 1.0)
tween.tween_callback(queue_free)
# Movimento suave
tween.tween_property($Sprite, "position", Vector2(500, 300), 2.0)
.set_trans(Tween.TRANS_SINE)
.set_ease(Tween.EASE_IN_OUT)
10. UI
Control nodes
UI usa hierarquia Control (em vez de Node2D):
UI (CanvasLayer)
├── HUD (Control)
│ ├── ScoreLabel (Label)
│ ├── LivesLabel (Label)
│ └── HealthBar (ProgressBar)
└── PauseMenu (Panel)
├── Title (Label)
├── ResumeButton (Button)
└── QuitButton (Button)
CanvasLayer renderiza acima do mundo, sem ser afectado pela camera.
Containers para layout
HBoxContainer— horizontal.VBoxContainer— vertical.GridContainer— grid.CenterContainer— centra filho.
Sem precisar setar posições manualmente.
Connectar botões
extends Control
func _ready():
$ResumeButton.pressed.connect(_on_resume)
$QuitButton.pressed.connect(_on_quit)
func _on_resume():
get_tree().paused = false
hide()
func _on_quit():
get_tree().quit()
Pausar o jogo
# Pausar
get_tree().paused = true
# Continuar
get_tree().paused = false
Cada node tem process_mode:
- INHERIT — herda do parent.
- PAUSABLE — pausa.
- WHEN_PAUSED — só corre quando pausado.
- ALWAYS — nunca pausa (para UI de pause).
11. Autoloads (Singletons)
Para estado global (score, settings, save data).
game.gd:
extends Node
signal score_mudou(novo)
signal vida_mudou(nova)
var score = 0:
set(value):
score = value
score_mudou.emit(value)
var vida = 3:
set(value):
vida = value
vida_mudou.emit(value)
func reset():
score = 0
vida = 3
Project → Project Settings → Autoload → adicionar game.gd como "Game".
Em qualquer node:
Game.score += 10
Game.vida -= 1
12. Áudio
Reproduzir efeito
AudioStreamPlayer (UI/música) ou AudioStreamPlayer2D (posicionado).
@onready var pulo_sfx = $PuloSfx
@onready var moeda_sfx = $MoedaSfx
func pular():
velocity.y = JUMP_VELOCITY
pulo_sfx.play()
func apanhar_moeda():
moeda_sfx.play()
score += 1
Música em loop
AudioStreamPlayer com Stream setado para AudioStreamOggVorbis, com Loop = true no Inspector do recurso.
@onready var musica = $Musica
func _ready():
musica.play()
Volume
# Volume em dB (-80 a 0, 0 = volume original)
pulo_sfx.volume_db = -10
musica.volume_db = -20
Buses (mixer)
Audio panel (bottom): definir buses (Music, SFX, Master) e controlar volumes globais.
13. Save / Load
JSON simples
const SAVE_PATH = "user://savegame.json"
func save_game():
var data = {
"score": Game.score,
"level": Game.level,
"unlocked": Game.unlocked_levels,
}
var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
file.store_string(JSON.stringify(data))
file.close()
func load_game():
if not FileAccess.file_exists(SAVE_PATH):
return
var file = FileAccess.open(SAVE_PATH, FileAccess.READ)
var data = JSON.parse_string(file.get_as_text())
file.close()
Game.score = data["score"]
Game.level = data["level"]
user:// = pasta específica da app (cross-platform).
14. Export e distribuição
Setup export
Project → Export → Add… (escolhe plataforma): - Windows Desktop (precisa template; download nas Editor Settings). - Linux/X11. - macOS (precisa Mac para code signing). - Web (HTML5 + WASM). - Android (precisa SDK). - iOS (precisa Mac + Xcode).
Export Path: ex. build/jogo.exe.
Export do jogo
Clica "Export Project" → escolhe destino. Ficheiro standalone (com PCK + executável).
CLI
godot --headless --export-release "Windows Desktop" build/jogo.exe
Útil para CI/CD.
itch.io
Plataforma indie. Cria conta gratuita, faz upload do .zip, define preço (pode ser PWYW). Pronto a partilhar!
Steam
Mais sério. $100 fee, mas pode chegar a milhões.
15. Boas práticas
Estrutura de projecto
projecto/
├── scenes/
│ ├── main.tscn
│ ├── player.tscn
│ └── enemies/
├── scripts/
│ └── (se preferes separar de scenes)
├── assets/
│ ├── sprites/
│ ├── audio/
│ ├── fonts/
│ └── tilesets/
├── ui/
└── project.godot
Performance
- Reusar nodes (object pooling para tiros, inimigos).
- Áreas pequenas (não criar 1000 nodes invisíveis).
- Sprite atlases em vez de imagens individuais.
_processapenas se necessário — desativa quando não precisas.
Debugging
print()ainda é útil.- Breakpoints no script editor.
- Remote scene tree (vê nodes em runtime).
- Profiler para identificar bottlenecks.
Version control
Git é essencial. .gitignore minimum:
.godot/
.import/
*.translation
export.cfg
Commits frequentes — game dev = experimentação.
16. Recursos para aprender
Tutoriais
- GDQuest (gdquest.com, YouTube) — qualidade top.
- HeartBeast (YouTube).
- Brackeys (YouTube, ex-Unity, agora Godot).
- Documentação oficial (docs.godotengine.org).
Assets gratuitos
- OpenGameArt — sprites, audio gratuitos.
- Kenney.nl — packs completos CC0.
- itch.io/game-assets — muitos free.
- freesound.org — sons.
Comunidade
- r/godot (Reddit).
- Godot Discord.
- Godot Forums (forum.godotengine.org).
17. Próximos passos
- Tilemap para níveis baseados em tiles.
- State machines para AI (FiniteStateMachine).
- Shaders (GLSL-like) para efeitos visuais.
- Multiplayer (high-level networking incluído).
- 3D em Godot — 80% transferível dos conceitos 2D.
- Procedural generation.
- C# em Godot se preferes tipo estático.
Glossário
- Node: bloco fundamental do Godot.
- Scene: árvore de nodes guardada em .tscn.
- Resource: dados partilháveis (.tres), como SpriteFrames, AudioStream.
- Signal: evento emitido por node.
- Autoload: singleton global acessível em todo o lado.
- CharacterBody2D: corpo com física controlado por código.
- Area2D: detecta entrada/saída sem colisão sólida.
- Tween: animação programática.
- Delta: segundos desde o último frame.
Game dev é uma maratona. O primeiro jogo é horrível — está tudo bem. O segundo é mau. O terceiro começa a ter ideias. Não te desanimes — todos os devs experientes passaram por isto. Faz scope pequeno (Pong, Snake, Breakout), termina, publica em itch.io. Depois itera.