Partilhar: WhatsApp
aulify · Sebenta
UC · Unidade de Competência · UC02841

Sebenta · Videojogos com Godot (UC02841)

Game dev 2D moderno
50h · 4.5 pontos crédito Curso: T. Desenv. Software ↗ Referencial oficial SNQ
Índice

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:

Em geral, círculos e caixas são mais rápidos que polígonos.

Tipos de body

Layers e masks

Project → Project Settings → Layer Names → 2D Physics. Define layers semânticas:

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

  1. Criar SpriteFrames resource (no Inspector).
  2. Adicionar animações: "idle", "run", "jump", "die".
  3. 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):

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

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

Debugging

Version control

Git é essencial. .gitignore minimum:

.godot/
.import/
*.translation
export.cfg

Commits frequentes — game dev = experimentação.

16. Recursos para aprender

Tutoriais

Assets gratuitos

Comunidade

17. Próximos passos

Glossário

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.