UC02841

Desenvolvimento de videojogos

Unity / Godot — primeiros jogos 2D/3D

Curso profissional · 50h · Game dev

Plano

  1. Game dev — landscape
  2. Setup Unity / Godot
  3. Cena, GameObjects, componentes
  4. Scripting (C# em Unity, GDScript em Godot)
  5. Física e colisões
  6. Input do jogador
  7. UI e sistemas de jogo
  8. Audio
  9. Build e distribuição

Bloco 1 · Game dev hoje

Motor de jogo escolhido

  • Unity — C#, indústria standard, todos os dispositivos.
  • Godot — GDScript (Python-like), open source, leve.
  • Unreal — C++, AAA, blueprints visuais.
  • GameMaker — GML, 2D, indie.

Esta UC: Godot (gratuito, fácil) ou Unity (industry standard). Vamos focar Godot.

Por que Godot

  • Open source (MIT) — sem royalties.
  • Editor leve (50MB vs 1GB+ Unity).
  • GDScript simples (Python-like).
  • 2D first-class (não 3D adaptado a 2D).
  • Cross-platform (Win/Mac/Linux/Android/iOS/Web).
  • Cresceu muito desde Unity drama de 2023.

Setup

Download em godotengine.org.

  • Godot 4.x (versão moderna).
  • Standard ou .NET (se queres C#).

Executável único — abre e usa.

IDE integrada com editor de script e debugger.

Bloco 2 · Conceitos fundamentais

Cena e Nodes

  • Scene: organização de uma "cena" (menu, nível, HUD).
  • Node: bloco fundamental — tudo é um Node.
  • Tree: hierarquia parent-child de nodes.
Main (Node2D)
├── Player (CharacterBody2D)
│   ├── Sprite2D
│   └── CollisionShape2D
├── Coins (Node2D)
│   ├── Coin1 (Area2D)
│   └── Coin2 (Area2D)
└── UI (CanvasLayer)
    └── Score (Label)

Node types principais (2D)

  • Node2D: base 2D (posição, rotação, escala).
  • Sprite2D: imagem.
  • AnimatedSprite2D: spritesheet animado.
  • CharacterBody2D: corpo com física controlado.
  • RigidBody2D: corpo físico simulado.
  • StaticBody2D: corpo fixo (chão, paredes).
  • Area2D: detecta entrada/saída (sem colisão sólida).
  • CollisionShape2D: forma da colisão.
  • Label, Button: UI.

Bloco 3 · GDScript

Hello GDScript

extends Node2D   # tipo de node que estende

func _ready():
    print("Cena pronta!")

func _process(delta):
    # chamado todos os frames
    pass

Atalhos:

  • _ready(): chamado quando node entra na cena.
  • _process(delta): cada frame (delta = segundos desde último).
  • _physics_process(delta): cada physics tick (60Hz fixo).
  • _input(event): input.

Variáveis e tipos

var nome = "Player"           # auto-tipo
var vida: int = 100           # explicit
var velocidade: float = 200.0
var ativo: bool = true
var posicao: Vector2 = Vector2(100, 200)

const MAX_VIDA = 100          # constante

@export var velocidade_max: float = 300.0   # editável no editor

@export torna variável visível e editável no inspector do Godot.

Controlo

# If
if vida <= 0:
    morrer()
elif vida < 30:
    avisar()
else:
    continuar()

# Match (switch)
match estado:
    "idle": idle()
    "moving": mover()
    _: pass    # default

# Loops
for i in range(10):
    print(i)

for inimigo in inimigos:
    inimigo.atacar()

while vida > 0:
    sofrer_dano(1)

Bloco 4 · Player movement

CharacterBody2D + movimento

extends CharacterBody2D

const SPEED = 300.0
const JUMP_VELOCITY = -400.0

func _physics_process(delta):
    # Gravidade
    if not is_on_floor():
        velocity.y += 980 * 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 actions

No Project Settings → Input Map:

  • ui_left ← seta esquerda + A
  • ui_right ← seta direita + D
  • ui_accept ← espaço + Enter

Vantagem: dissocia teclas do código (gamepad, rebinding).

Input.is_action_pressed("ui_left")        # enquanto premido
Input.is_action_just_pressed("ui_accept") # apenas no frame
Input.is_action_just_released("attack")   # apenas no frame

Bloco 5 · Colisões e sinais

Area2D detecta entrada

extends Area2D
# Coin que desaparece quando player entra

func _ready():
    body_entered.connect(_on_body_entered)

func _on_body_entered(body):
    if body.name == "Player":
        Game.score += 10
        queue_free()   # apaga o coin

Sinais = sistema de eventos do Godot (como callbacks).

Sinais customizados

extends Node

signal vida_mudou(nova_vida)
signal morreu

var vida = 100

func sofrer_dano(d):
    vida -= d
    vida_mudou.emit(vida)
    if vida <= 0:
        morreu.emit()

# Em outro node:
player.vida_mudou.connect(_atualizar_hud)
player.morreu.connect(_game_over)

Bloco 6 · Audio

Reproduzir som

# AudioStreamPlayer node na cena
@onready var som_pulo = $SomPulo

func pular():
    velocity.y = JUMP_VELOCITY
    som_pulo.play()

Tipos:

  • AudioStreamPlayer: sem posicionamento (UI, música).
  • AudioStreamPlayer2D: com posição (sons 2D).

Formatos: WAV (sem compressão), OGG (compressão).

Bloco 7 · UI

Canvas Layer + Labels

UI (CanvasLayer)
├── ScoreLabel (Label)
├── LivesLabel (Label)
└── GameOverPanel (Panel)
    ├── Title (Label)
    └── RestartButton (Button)
extends CanvasLayer

@onready var score_label = $ScoreLabel

func _ready():
    Game.score_mudou.connect(_atualizar_score)

func _atualizar_score(novo):
    score_label.text = "Score: " + str(novo)

Singletons (Autoload)

Para estado global (score, settings, save game):

game.gd:

extends Node

signal score_mudou(novo)

var score = 0:
    set(value):
        score = value
        score_mudou.emit(value)

var vida = 3

Project Settings → Autoload → adicionar game.gd como Game.

Agora Game.score acessível de qualquer lado.

Bloco 8 · Animação

AnimatedSprite2D + spritesheet

  1. Carregar imagem com várias frames.
  2. SpriteFrames resource — definir animações ("idle", "run", "jump").
  3. Em código:
@onready var sprite = $AnimatedSprite2D

func _physics_process(delta):
    if velocity.x != 0:
        sprite.play("run")
        sprite.flip_h = velocity.x < 0
    else:
        sprite.play("idle")

AnimationPlayer (Tweens)

# Animação programática
var tween = create_tween()
tween.tween_property(self, "modulate:a", 0.0, 1.0)   # fade out 1s
tween.tween_callback(queue_free)                      # apaga depois

Útil para UI animations, transições.

Bloco 9 · Build

Export

Project → Export → adicionar preset:

  • Windows Desktop (.exe)
  • Linux/X11 (.x86_64)
  • macOS (.app)
  • HTML5 (browser)
  • Android (.apk)
# Ou CLI
godot --export-release "Windows Desktop" build/jogo.exe

itch.io = melhor plataforma para distribuir jogos indie gratuitos / pay-what-you-want.

Otimização básica

  • Texturas apropriadas (não maiores que precisas).
  • Audio compressed (.ogg) para música.
  • Reusar resources (não criar 100 nodes iguais).
  • Pool de objects para tiros/inimigos.
  • queue_free() ao apagar, não free().

UC02841 · resumo

  • Godot = motor open source, leve, perfeito para começar.
  • Node tree = arquitectura fundamental.
  • GDScript = Python-like, fácil de aprender.
  • Signals = eventos para comunicação entre nodes.
  • CharacterBody2D + move_and_slide() para player.
  • Area2D + body_entered para colectáveis.
  • Autoloads para estado global.
  • AnimatedSprite2D para animações.
  • Export para múltiplas plataformas com 1 clique.
  • itch.io para distribuir o primeiro jogo!

NOTAS DO PROFESSOR 📖 Escolher o motor certo para o objectivo A escolha do motor não é uma questão de "melhor" ou "pior", mas de adequação ao projecto e à equipa. Para um primeiro jogo 2D, o critério decisivo é a curva de aprendizagem e o custo, não a capacidade técnica máxima. Convém desmontar o mito de que é preciso o Unreal para fazer algo a sério. 🗣️ Pontos a desenvolver oralmente • Relacionar cada motor com jogos conhecidos do mercado para tornar a comparação concreta • Explicar o modelo de royalties e porque pesa na decisão de estúdios indie • Justificar a opção pedagógica por Godot nesta UC sem desvalorizar o Unity

NOTAS DO PROFESSOR 📖 As vantagens do open source no game dev Ser open source e sem royalties significa que o que se cria é inteiramente do autor, sem surpresas de licenciamento no futuro. O peso leve do editor é uma vantagem prática real para quem trabalha em máquinas modestas, como as de uma sala de formação. O facto de o 2D ser nativo evita as gambiarras típicas de motores pensados primeiro para 3D. 🗣️ Pontos a desenvolver oralmente • Contextualizar o "Unity drama de 2023" e o impacto na confiança da comunidade • Mostrar a diferença prática de abrir 50MB contra 1GB numa sala de aula • Distinguir 2D nativo de 2D simulado num motor 3D, com exemplos visuais

NOTAS DO PROFESSOR 📖 Ambiente único, sem fricção de instalação Uma das maiores barreiras para iniciantes é a configuração do ambiente de desenvolvimento. O Godot resolve isto sendo um executável único que já traz editor de cena, editor de código e debugger integrados. Vale a pena reforçar a diferença entre a versão Standard e a .NET, para evitar que escolham mal e não consigam usar C#. 🗣️ Pontos a desenvolver oralmente • Demonstrar ao vivo o download e abertura, mostrando que não há instalador • Explicar quando faz sentido a versão .NET e quando a Standard chega • Apresentar as três áreas do editor antes de avançar para o código

NOTAS DO PROFESSOR 📖 Tudo é um Node organizado em árvore O conceito mais importante do Godot é que tudo é um Node e que estes se organizam numa árvore parent-child. Compreender esta hierarquia é a chave para tudo o resto: posições relativas, propagação de transformações e organização do projecto. A separação em cenas reutilizáveis (Player, menu, nível) é o que torna o trabalho escalável. 🗣️ Pontos a desenvolver oralmente • Mostrar como mover o nó pai arrasta todos os filhos com ele • Explicar a vantagem de guardar o Player como cena reutilizável • Ligar a árvore de nodes à organização lógica de um jogo real

NOTAS DO PROFESSOR 📖 Escolher o tipo de corpo certo para cada objecto A distinção entre CharacterBody2D, RigidBody2D e StaticBody2D é fundamental e fonte de muitos erros de iniciante. Um personagem controlado pelo jogador não deve ser RigidBody, e uma parede não deve ter física simulada. O Area2D é frequentemente esquecido, mas é a ferramenta certa para detectar entradas sem provocar colisão sólida. 🗣️ Pontos a desenvolver oralmente • Contrastar movimento controlado (CharacterBody) com física simulada (RigidBody) • Dar exemplos de cada corpo num jogo de plataformas concreto • Sublinhar que CollisionShape2D é obrigatório para haver colisão

NOTAS DO PROFESSOR 📖 As funções de ciclo de vida do Godot As funções com underscore são chamadas automaticamente pelo motor em momentos específicos do ciclo de vida. Distinguir _process de _physics_process é crucial: o primeiro corre por frame (variável), o segundo a uma cadência fixa, ideal para física. O parâmetro delta é o que garante que o jogo se comporta igual independentemente da taxa de frames. 🗣️ Pontos a desenvolver oralmente • Explicar porque se multiplica movimento por delta para independência de framerate • Mostrar a diferença prática entre _process e _physics_process • Comparar a sintaxe de GDScript com Python para tranquilizar quem já conhece

NOTAS DO PROFESSOR 📖 Tipagem opcional e o poder do @export O GDScript permite tipar ou não as variáveis, mas tipar traz validação e melhor autocomplete. O @export é uma ferramenta poderosa que liga código e editor: permite ajustar valores como velocidade no inspector, sem tocar no código. Isto separa o trabalho de programador do de designer de jogo. 🗣️ Pontos a desenvolver oralmente • Demonstrar como uma variável @export aparece e se edita no inspector • Discutir quando vale a pena tipar e o ganho em projectos maiores • Distinguir const de variável e o seu papel na legibilidade

NOTAS DO PROFESSOR 📖 Estruturas de controlo aplicadas à lógica de jogo As estruturas de controlo do GDScript são as habituais, mas o match é particularmente útil para máquinas de estados, muito comuns em jogos (idle, a correr, a saltar). O for sobre coleções permite percorrer todos os inimigos ou itens de forma limpa. Convém alertar para os perigos do while sem condição de saída clara, que congela o jogo. 🗣️ Pontos a desenvolver oralmente • Mostrar o match como base de uma máquina de estados de personagem • Percorrer uma lista de inimigos com for num exemplo prático • Avisar para o risco de ciclos while infinitos a bloquear o frame

NOTAS DO PROFESSOR 📖 O movimento de plataformas, passo a passo Este é o coração de um jogo de plataformas e merece ser dissecado linha a linha. A gravidade aplicada manualmente, a verificação is_on_floor antes do salto e o move_and_slide que resolve as colisões são as três peças essenciais. O move_toward dá uma desaceleração suave em vez de uma paragem brusca, detalhe que faz a diferença na sensação de controlo. 🗣️ Pontos a desenvolver oralmente • Explicar porque a gravidade é somada a velocity.y a cada frame • Justificar a condição is_on_floor para impedir saltos no ar • Comparar paragem instantânea com a desaceleração do move_toward

NOTAS DO PROFESSOR 📖 Separar a intenção da tecla concreta O Input Map é uma boa prática profissional: o código pergunta por "saltar", não pela tecla Espaço. Isto desacopla a lógica das teclas físicas e permite suportar gamepad e remapeamento sem reescrever nada. A distinção entre is_action_pressed (contínuo) e just_pressed (um único frame) é uma fonte clássica de bugs. 🗣️ Pontos a desenvolver oralmente • Configurar uma acção no Input Map ao vivo, associando teclas e gamepad • Explicar quando usar pressed contínuo e quando just_pressed • Mostrar como o rebinding fica trivial graças a esta abstracção

NOTAS DO PROFESSOR 📖 Detecção de colectáveis com Area2D e sinais O Area2D é a forma idiomática de detectar quando algo entra numa zona sem provocar uma colisão física. O sinal body_entered é emitido automaticamente e o código apenas reage. O queue_free é a forma segura de eliminar um node, agendando a remoção para o fim do frame em vez de o destruir a meio. 🗣️ Pontos a desenvolver oralmente • Explicar a diferença entre detectar (Area2D) e colidir (corpos físicos) • Mostrar a ligação do sinal body_entered no editor e por código • Justificar queue_free em vez de free para evitar crashes

NOTAS DO PROFESSOR 📖 Sinais customizados para desacoplar o código Definir sinais próprios é o que permite que partes do jogo comuniquem sem dependências rígidas. O Player não precisa de conhecer o HUD: limita-se a emitir "vida_mudou" e quem quiser que reaja. Este padrão de observador é a base de uma arquitectura limpa e mantém os sistemas independentes. 🗣️ Pontos a desenvolver oralmente • Contrastar chamar directamente o HUD com emitir um sinal • Explicar a vantagem de o emissor não conhecer os receptores • Relacionar este padrão com o conceito geral de eventos em software

NOTAS DO PROFESSOR 📖 Som como reforço da experiência de jogo O áudio é muitas vezes negligenciado por iniciantes, mas é o que dá vida e feedback ao jogo. A distinção entre AudioStreamPlayer e a sua variante 2D é prática: efeitos posicionados no espaço soam diferentes de música de fundo. A escolha de formato, WAV para efeitos curtos e OGG comprimido para música, é uma decisão de optimização concreta. 🗣️ Pontos a desenvolver oralmente • Mostrar o impacto de adicionar som de salto a um jogo silencioso • Distinguir áudio posicionado (2D) de áudio global de interface • Justificar WAV para efeitos e OGG para faixas longas de música

NOTAS DO PROFESSOR 📖 Separar a interface do mundo do jogo O CanvasLayer mantém a interface fixa no ecrã, independentemente do movimento da câmara no mundo. Organizar os elementos de UI numa hierarquia clara (score, vidas, painel de game over) facilita a manutenção. Ligar o HUD ao estado do jogo por sinais evita ter de o actualizar manualmente a cada frame. 🗣️ Pontos a desenvolver oralmente • Explicar porque a UI vive num CanvasLayer e não se move com a câmara • Mostrar a actualização do score reagindo a um sinal, sem polling • Reforçar a separação entre lógica de jogo e apresentação visual

NOTAS DO PROFESSOR 📖 Estado global com Autoloads (singletons) Os Autoloads resolvem o problema de partilhar estado entre cenas, como pontuação ou definições, sem o passar de mão em mão. O setter no exemplo mostra um padrão elegante: sempre que o score muda, emite-se automaticamente um sinal. Convém alertar para não abusar de globais, que podem tornar o código difícil de seguir. 🗣️ Pontos a desenvolver oralmente • Demonstrar a configuração de um Autoload no Project Settings • Explicar o setter que dispara um sinal ao alterar o valor • Discutir o equilíbrio entre conveniência e excesso de estado global

NOTAS DO PROFESSOR 📖 Dar vida ao personagem com animações O AnimatedSprite2D com SpriteFrames permite organizar várias animações (idle, run, jump) a partir de uma folha de sprites. O truque de inverter flip_h conforme a direcção evita ter de desenhar o personagem virado para os dois lados. Ligar a animação ao estado de movimento torna o personagem responsivo e credível. 🗣️ Pontos a desenvolver oralmente • Mostrar a configuração do recurso SpriteFrames com várias animações • Explicar o flip_h para reaproveitar a mesma arte em ambas as direcções • Relacionar a troca de animação com a máquina de estados do personagem

NOTAS DO PROFESSOR 📖 Animação por código com Tweens Os Tweens permitem animar propriedades ao longo do tempo de forma programática, sem desenhar frame a frame. São ideais para transições de interface, fades e efeitos de aparecimento ou desaparecimento. O encadeamento com tween_callback mostra como executar uma acção, como apagar o node, no fim da animação. 🗣️ Pontos a desenvolver oralmente • Demonstrar um fade out suave de um elemento com tween_property • Explicar o encadeamento de passos e o callback final • Contrastar Tweens (transições) com AnimatedSprite (animação de arte)

NOTAS DO PROFESSOR 📖 Exportar e publicar o primeiro jogo A exportação multiplataforma com presets é um dos grandes argumentos do Godot: o mesmo projecto gera executável para Windows, Linux, macOS, Android e Web. O export HTML5 é especialmente motivador, porque permite partilhar o jogo por um simples link. Publicar no itch.io é o passo final que transforma um exercício de aula num produto real e visível. 🗣️ Pontos a desenvolver oralmente • Mostrar a criação de um preset de exportação e o ficheiro gerado • Realçar o valor do export Web para mostrar o jogo a amigos e família • Incentivar a publicação no itch.io como objectivo concreto da UC

NOTAS DO PROFESSOR 📖 Hábitos de optimização desde o início A optimização não é só para projectos grandes: bons hábitos desde cedo evitam problemas de desempenho mais tarde. Texturas do tamanho certo e áudio comprimido poupam memória; reutilizar recursos e usar object pools para tiros ou inimigos evita criar e destruir nodes constantemente. São princípios transferíveis para qualquer motor. 🗣️ Pontos a desenvolver oralmente • Explicar o conceito de object pool com o exemplo de tiros repetidos • Mostrar o custo de criar e destruir nodes em vez de os reaproveitar • Reforçar que estes hábitos valem em qualquer engine, não só no Godot