Como Fazer um Jogo da Memória no Godot 4: Tutorial Completo

Aprenda como fazer um jogo da memória no Godot 4: cartas com sinais, GridContainer, comparação de pares e vitória, tudo em GDScript tipado passo a passo.
Jogo da memória parece brincadeira de criança, mas é um dos melhores exercícios de lógica de estado que existem para quem está começando na engine. Neste tutorial você vai aprender como fazer um jogo da memória no Godot 4 completo: uma grade de cartas embaralhadas, o jogador vira duas por vez, pares corretos ficam abertos, erros desviram sozinhos depois de uma pausa, e um contador de tentativas acompanha tudo até a tela de vitória. Todo o código é GDScript com tipagem estática e APIs reais da Godot 4.
Por que o jogo da memória é um ótimo segundo projeto
Se o seu primeiro projeto foi algo como o jogo da velha na Godot, o jogo da memória é o passo natural seguinte. Ele adiciona exatamente as três coisas que faltavam:
- Estado com espera: o jogo precisa lembrar qual foi a primeira carta virada, esperar a segunda, comparar e reagir. Isso te obriga a pensar em máquina de estados de verdade, ainda que pequena.
- Sinais customizados: cada carta é uma cena própria que avisa o jogo principal quando é clicada. É a arquitetura que você vai repetir em praticamente todo projeto Godot daqui para frente.
- UI dinâmica: o tabuleiro é gerado por código, não montado à mão no editor. Dezesseis cartas criadas num loop, embaralhadas e conectadas em segundos.
E se você já montou a versão de blocos no jogo da memória no Scratch, este tutorial é a tradução direta daquela lógica para código de verdade. Os conceitos são os mesmos, mas aqui você controla cada detalhe.
O projeto inteiro usa dois scripts e duas cenas. Nada de sprites obrigatórios: vamos usar Button com emoji como símbolo, o que deixa o tutorial focado na lógica. Trocar por arte depois é trivial.
A cena da carta: Button, valor e sinal
Crie uma cena nova com um Button como raiz e salve como carta.tscn. A carta precisa saber três coisas sobre si mesma: qual símbolo ela esconde, se está virada e se está travada (quando o par já foi encontrado). E precisa de um jeito de avisar o mundo que foi clicada: um sinal customizado com parâmetro tipado.
Anexe este script à raiz:
extends Button
class_name Carta
signal carta_virada(carta: Carta)
const SIMBOLO_FECHADA: String = "?"
var valor: int = 0
var simbolo: String = ""
var virada: bool = false
var travada: bool = false
func _ready() -> void:
custom_minimum_size = Vector2(96, 128)
add_theme_font_size_override("font_size", 40)
pressed.connect(_ao_clicar)
fechar()
func _ao_clicar() -> void:
if virada or travada:
return
carta_virada.emit(self)
func virar() -> void:
virada = true
text = simbolo
func fechar() -> void:
virada = false
text = SIMBOLO_FECHADA
func travar_aberta() -> void:
travada = true
disabled = true
Repare em uma decisão importante: a carta não vira sozinha quando clicada. Ela só emite o sinal carta_virada passando a si mesma como argumento. Quem decide se a carta pode virar é o script principal. Esse detalhe vai resolver de graça o problema clássico do jogo da memória: o jogador esperto que tenta virar uma terceira carta enquanto as duas erradas ainda estão abertas.
Os três métodos públicos cobrem os três estados da carta. virar() mostra o símbolo, fechar() volta para a interrogação, e travar_aberta() marca a carta como par encontrado e desabilita o botão, o que também dá um feedback visual de "resolvida" sem nenhum código extra.
O class_name Carta registra o tipo globalmente. É isso que permite tipar o sinal como carta: Carta e declarar variáveis var primeira_carta: Carta no script principal, com autocomplete e erro de compilação se você passar a coisa errada.
Como fazer um jogo da memória no Godot: montando o tabuleiro
Agora a cena principal. Crie uma cena com um Control como raiz e monte esta estrutura:
Main (Control)
├── Tentativas (Label)
├── Grade (GridContainer)
└── Vitoria (Label)
No Inspector do Grade, defina columns = 4. Com 16 cartas, isso gera um tabuleiro 4x4. Posicione o Tentativas no topo e centralize o Vitoria na tela (ele começa invisível).
A geração dos pares é o coração desta parte. A técnica é simples e à prova de erro: crie um Array[int] com cada valor repetido duas vezes, embaralhe com shuffle() e instancie uma carta para cada valor da lista. Como todo valor entra duas vezes no array, é impossível existir carta sem par.
Anexe este script ao Main:
extends Control
const CENA_CARTA: PackedScene = preload("res://carta.tscn")
const SIMBOLOS: Array[String] = ["🐶", "🐱", "🦊", "🐸", "🐼", "🦉", "🐙", "🐝"]
const TOTAL_PARES: int = 8
@onready var grade: GridContainer = $Grade
@onready var rotulo_tentativas: Label = $Tentativas
@onready var rotulo_vitoria: Label = $Vitoria
var primeira_carta: Carta = null
var segunda_carta: Carta = null
var pode_clicar: bool = true
var tentativas: int = 0
var pares_encontrados: int = 0
func _ready() -> void:
rotulo_vitoria.visible = false
montar_tabuleiro()
atualizar_placar()
func montar_tabuleiro() -> void:
var valores: Array[int] = []
for i: int in range(TOTAL_PARES):
valores.append(i)
valores.append(i)
valores.shuffle()
for valor: int in valores:
var carta: Carta = CENA_CARTA.instantiate() as Carta
carta.valor = valor
carta.simbolo = SIMBOLOS[valor]
carta.carta_virada.connect(_ao_virar_carta)
grade.add_child(carta)
Cada carta instanciada recebe seu valor (o índice do par, de 0 a 7), o simbolo correspondente da lista, e tem seu sinal conectado ao método _ao_virar_carta do jogo principal. Dezesseis conexões feitas num loop de cinco linhas.
O shuffle() é um método nativo de Array na Godot 4 e usa o gerador aleatório global, que já vem inicializado automaticamente. Cada partida sai com uma distribuição diferente sem você configurar nada.
O cérebro do jogo: guardar, comparar e reagir
Toda a regra do jogo da memória cabe em duas funções. A primeira recebe os cliques e decide o que fazer com eles. A segunda compara o par e resolve o resultado. Adicione ao script do Main:
func _ao_virar_carta(carta: Carta) -> void:
if not pode_clicar:
return
carta.virar()
if primeira_carta == null:
primeira_carta = carta
return
segunda_carta = carta
tentativas += 1
atualizar_placar()
await comparar_cartas()
func comparar_cartas() -> void:
pode_clicar = false
if primeira_carta.valor == segunda_carta.valor:
primeira_carta.travar_aberta()
segunda_carta.travar_aberta()
pares_encontrados += 1
verificar_vitoria()
else:
await get_tree().create_timer(0.8).timeout
primeira_carta.fechar()
segunda_carta.fechar()
primeira_carta = null
segunda_carta = null
pode_clicar = true
Vamos destrinchar o fluxo, porque é aqui que a maioria dos tutoriais por aí escorrega:
- Primeiro clique:
primeira_cartaestá vazia, então o jogo vira a carta, guarda a referência e para. Nada mais acontece. - Segundo clique: agora existe um par para comparar. O jogo vira a segunda carta, conta a tentativa e chama a comparação.
- Trava de input: a primeira coisa que
comparar_cartas()faz é derrubarpode_clicar. Como quem manda virar carta é oMain, qualquer clique durante a espera bate noreturndo topo de_ao_virar_cartae morre ali. A carta clicada nem chega a virar. - Acertou: os valores batem, as duas cartas são travadas abertas e o contador de pares sobe. Sem espera nenhuma, o feedback é imediato.
- Errou: o jogo espera 0.8 segundos com
await get_tree().create_timer(0.8).timeoutpara o jogador memorizar as posições, e só então desvira as duas. É essa pausa que faz o jogo ser um jogo de memória e não de reflexo. - Limpeza: nos dois casos, as referências voltam para
nulle o input é liberado para a próxima dupla.
O await é a parte mais elegante. Sem ele, você precisaria de um nó Timer na cena, uma conexão de sinal e uma função separada para terminar a jogada, com o estado espalhado em três lugares. Com await, a função pausa no meio, o resto do jogo continua rodando, e a execução retoma exatamente daquela linha quando o tempo acaba. O código lê na ordem em que as coisas acontecem.
Um detalhe de Godot 4: como comparar_cartas() contém um await, ela é uma corrotina, e por isso a chamada em _ao_virar_carta também usa await. Isso evita o aviso de corrotina descartada e deixa explícito que a jogada só termina quando a comparação inteira termina.
Contador de tentativas e detecção de vitória
Faltam as duas funções de placar, que são as mais curtas do projeto:
func atualizar_placar() -> void:
rotulo_tentativas.text = "Tentativas: %d" % tentativas
func verificar_vitoria() -> void:
if pares_encontrados == TOTAL_PARES:
rotulo_vitoria.text = "Você venceu em %d tentativas!" % tentativas
rotulo_vitoria.visible = true
A detecção de vitória é uma comparação direta: quando pares_encontrados alcança TOTAL_PARES, acabou. Não precisa varrer a grade checando carta por carta, porque o contador já carrega essa informação. Manter contadores em vez de varrer estado é um hábito que escala bem para jogos maiores.
O contador de tentativas conta duplas viradas, não cliques. É a métrica clássica do jogo da memória: um jogador perfeito fecha o tabuleiro 4x4 em pouco mais de 8 tentativas, um jogador distraído passa de 20. Esse número na tela é o que dá vontade de jogar de novo.
Rode a cena. Você tem um jogo da memória completo: embaralha, vira, compara, trava pares, bloqueia cliques apressados, conta tentativas e anuncia a vitória. Tudo em menos de 100 linhas tipadas.
Ideias para expandir o projeto
O esqueleto que você montou aceita upgrades sem refatoração:
- Níveis progressivos: transforme
TOTAL_PARESem variável, aumente a cada vitória e ajustegrade.columnspara manter o tabuleiro retangular. Um 6x6 com 18 pares já é desafio sério. - Arte de verdade: troque o
ButtonporTextureButtonna cena da carta e definatexture_normalcom o verso e a face nos métodosvirar()efechar(). A lógica não muda. - Cronômetro: adicione um nó
Timercomwait_timede 1 segundo eautostartligado, conectandotimeouta um contador de segundos no HUD. Ranquear por tempo e tentativas dobra a rejogabilidade. - Sons: dois
AudioStreamPlayer, um para acerto e outro para erro, tocados dentro doifdecomparar_cartas(). Feedback sonoro transforma a sensação do jogo por quase zero esforço. - Modo dois jogadores: alterne o jogador ativo a cada erro e some pares por jogador. Se essa ideia te animou, o caminho natural depois deste projeto é montar um jogo de tabuleiro digital completo, com turnos e regras de verdade.
O jogo da memória fecha aquele trio de fundamentos que todo iniciante precisa dominar na Godot: cenas reutilizáveis, sinais customizados e estado com espera assíncrona. Depois dele, um deck builder, um jogo de cartas colecionáveis ou um quebra-cabeça de lógica são só variações do que você acabou de escrever.
Perguntas frequentes
Preciso de sprites prontos para fazer um jogo da memória no Godot?
Não. Você consegue fazer a versão completa usando apenas Button com texto ou emoji como símbolo da carta. Quando a lógica estiver funcionando, basta trocar o Button por TextureButton e apontar texture_normal para as suas artes, sem mudar uma linha da lógica de comparação.
Como embaralhar as cartas no Godot 4?
Monte um Array[int] com cada valor de par repetido duas vezes e chame o método shuffle() do próprio array. No Godot 4 o gerador aleatório global já é inicializado automaticamente, então você não precisa chamar randomize() antes. Cada execução do jogo gera uma ordem diferente.
Por que usar sinais em vez de checar os cliques direto no script principal?
Com um sinal customizado, cada carta avisa quem foi clicada e o script principal só reage, sem precisar conhecer a grade inteira. Isso mantém a carta reutilizável em qualquer tabuleiro e concentra as regras do jogo em um único lugar. É o mesmo padrão que você vai usar em inventários, menus e qualquer UI mais séria.
Como impedir o jogador de virar uma terceira carta durante a comparação?
Use uma variável de trava, como pode_clicar, que fica false enquanto o jogo espera o timer de 0.8 segundos para desvirar o par errado. Como é o script principal que manda a carta virar, basta ignorar os sinais recebidos enquanto a trava estiver ativa. Sem essa trava, o jogador vira três cartas e a lógica de pares quebra.
Como transformar o jogo da memória em um jogo com níveis?
Guarde o número de pares em uma variável e aumente a cada vitória, ajustando a propriedade columns do GridContainer para a grade continuar retangular. Comece com 2x2, suba para 4x4 e depois 6x6. Você também pode adicionar um Timer regressivo por nível para criar pressão de tempo.


