Objetivo do fluxo de jogo
Um jogo 2D bem organizado costuma ter um “ciclo” de telas: Menu → Fase → (Vitória ou Game Over) → Menu, com a possibilidade de Pausar durante a fase. Neste capítulo, você vai montar esse fluxo com transições consistentes (fade), navegação por teclado/mouse/touch e proteção contra cliques múltiplos (double click/tap), além de regras claras para reset de variáveis quando o jogador reinicia.
Layouts recomendados
- Menu: botões “Jogar”, “Fases” (opcional) e “Sair” (opcional).
- SelecaoFase: lista simples (Fase 1, Fase 2…) e botão “Voltar”.
- Fase01 (e outras fases): gameplay.
- OverlayPause: pode ser um layout separado (recomendado) ou uma camada/instância sobre a fase.
- GameOver: “Tentar de novo” e “Menu”.
- Vitoria: “Próxima fase” (se existir) e “Menu”.
Para manter consistência, use um padrão único de transição e um conjunto pequeno de variáveis globais para controlar navegação e bloqueios.
Variáveis globais para controle de fluxo
Crie variáveis globais (em Project) para padronizar o comportamento entre telas:
gNextLayout(string): nome do layout de destino (ex.: "Fase01").gIsTransitioning(boolean ou number 0/1): bloqueia entradas durante transição.gInputLockUntil(number): tempo (em segundos) até liberar clique/toque novamente.gSelectedLevel(number): fase escolhida (ex.: 1, 2, 3).gLastCheckpoint(string opcional): se você usar checkpoints.
Ideia central: toda troca de tela passa por uma função de transição que define gIsTransitioning e só troca de layout ao final do fade.
Camada/objeto de transição (Fade)
Opção A (simples e robusta): objeto “Fade” em todos os layouts
Em cada layout (Menu, SelecaoFase, Fase, GameOver, Vitoria), adicione um sprite chamado sprFade (um retângulo preto do tamanho da tela) numa camada no topo (ex.: Overlay). Configure:
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
- Cor: preto (ou outra).
- Opacidade inicial: 100 no início do layout (para “fade in”).
- Sem colisão, sem interação.
Adicione o behavior Fade ao sprFade (ou use Tween/Set opacity por eventos, se preferir). O objetivo é ter dois estados: fade-in (do preto para transparente) e fade-out (do transparente para preto).
Padrão de eventos para Fade-in ao iniciar qualquer layout
System: On start of layout → Set gIsTransitioning = 1 → sprFade: Set opacity to 100 → sprFade: Start fade to 0 (duration 0.35s) → Wait 0.35s → Set gIsTransitioning = 0Isso garante que o jogador não clique em botões enquanto a tela ainda está aparecendo.
Padrão de eventos para Fade-out e troca de layout
Crie um grupo de eventos (ex.: Transitions) em uma Event Sheet compartilhada (ou copie para as telas). O fluxo:
Function Call: GoToLayout (param: layoutName) → If gIsTransitioning = 0 → Set gIsTransitioning = 1 → Set gNextLayout = layoutName → sprFade: Start fade to 100 (duration 0.35s) → Wait 0.35s → System: Go to layout gNextLayoutSe você não estiver usando Function, pode disparar por um evento comum: ao clicar em um botão, setar gNextLayout e iniciar o fade-out; ao terminar o fade, trocar de layout.
Prevenção de cliques múltiplos (mouse e touch)
Em menus, é comum o jogador clicar duas vezes e disparar duas transições. Use um bloqueio simples por tempo e por estado:
Bloqueio por estado (gIsTransitioning)
Em qualquer evento de clique/toque, adicione a condição gIsTransitioning = 0.
Bloqueio por tempo (gInputLockUntil)
Ao aceitar um clique, defina um pequeno cooldown:
On button clicked → If time >= gInputLockUntil → Set gInputLockUntil = time + 0.25Combine com gIsTransitioning para ficar ainda mais seguro. Para touch, use o objeto Touch e eventos equivalentes (ex.: On tap gesture ou On touched object).
Menu principal: navegação por mouse, touch e teclado
Estrutura de botões
Use sprites para botões (ex.: btnPlay, btnLevels, btnQuit) e aplique feedback visual:
- Ao passar o mouse: mudar animação/opacity/scale.
- Ao pressionar: scale levemente menor.
Eventos de clique/toque
Mouse: On object clicked btnPlay → If gIsTransitioning = 0 → Set gSelectedLevel = 1 → Call GoToLayout("Fase01")Mouse: On object clicked btnLevels → If gIsTransitioning = 0 → Call GoToLayout("SelecaoFase")Para touch:
Touch: On touched object btnPlay → If gIsTransitioning = 0 → Set gSelectedLevel = 1 → Call GoToLayout("Fase01")Navegação por teclado (Enter/Esc e setas)
Uma forma simples é ter um índice de seleção (variável local do layout Menu: menuIndex) e destacar o botão selecionado.
On start of layout → Set menuIndex = 0Keyboard: On Down pressed → If gIsTransitioning = 0 → Set menuIndex = (menuIndex + 1) mod 2Keyboard: On Up pressed → If gIsTransitioning = 0 → Set menuIndex = (menuIndex - 1 + 2) mod 2Every tick → btnPlay: Set highlighted if menuIndex=0; btnLevels: highlighted if menuIndex=1Keyboard: On Enter pressed → If gIsTransitioning = 0 → If menuIndex=0: Set gSelectedLevel=1; Call GoToLayout("Fase01") → If menuIndex=1: Call GoToLayout("SelecaoFase")Se houver mais botões, ajuste o mod para a quantidade total.
Seleção simples de fase
Modelo de seleção minimalista
Crie botões btnLevel1, btnLevel2, btnBack. Ao clicar:
On clicked btnLevel1 → If gIsTransitioning = 0 → Set gSelectedLevel = 1 → Call GoToLayout("Fase01")On clicked btnLevel2 → If gIsTransitioning = 0 → Set gSelectedLevel = 2 → Call GoToLayout("Fase02")On clicked btnBack → If gIsTransitioning = 0 → Call GoToLayout("Menu")Carregar fase com base em gSelectedLevel (opcional)
Se preferir não duplicar eventos, você pode mapear a fase por nome:
On clicked btnLevelX → Set gSelectedLevel = X → Set gNextLayout = "Fase0" & str(X) → Call GoToLayout(gNextLayout)Garanta que os nomes dos layouts sigam o padrão (Fase01, Fase02...).
Pause: congelar jogo e manter UI responsiva
Estratégia recomendada: camada “Pause” dentro da fase
Na fase, crie uma camada Pause acima de tudo, inicialmente invisível. Nela, coloque um painel escuro semitransparente e botões: btnResume, btnRestart, btnMenu.
Variável local de fase
Crie uma variável local no layout da fase: isPaused (0/1).
Eventos de pausar/despausar (teclado e botão)
Keyboard: On Esc pressed → If gIsTransitioning = 0 → Toggle isPausedSystem: isPaused = 1 → Layer "Pause": Set visible → System: Set time scale to 0System: isPaused = 0 → Layer "Pause": Set invisible → System: Set time scale to 1Observação importante: ao usar time scale = 0, alguns comportamentos/esperas podem parar. Por isso, evite usar Wait para lógica do pause. Botões de UI ainda podem funcionar, mas se notar travas, alternativa é pausar apenas grupos de eventos do gameplay (desabilitando o grupo “Gameplay”) e manter a UI ativa.
Botões do pause
On clicked btnResume → Set isPaused = 0On clicked btnRestart → If gIsTransitioning = 0 → Call ResetRunVariables → Call GoToLayout(layoutname)On clicked btnMenu → If gIsTransitioning = 0 → Call ResetRunVariables → Call GoToLayout("Menu")layoutname é o nome do layout atual (reinicia a fase atual).
Regras claras de reset de variáveis
Para evitar bugs (vida não resetar, pontuação acumulando indevidamente, flags de vitória persistindo), defina uma regra: toda vez que iniciar uma fase “do zero”, chame um reset padrão.
O que resetar (exemplos comuns)
- Vida/HP do jogador
- Pontuação da fase (se não for acumulativa)
- Moedas/coletáveis temporários
- Flags:
hasKey,bossDefeated,isInvincible - Tempo de fase (se existir)
Implementação com Function (recomendado)
Crie uma função ResetRunVariables em uma Event Sheet global (ou incluída nas telas que precisam). Exemplo de “esqueleto”:
Function: ResetRunVariables → Set PlayerHP = PlayerHPMax → Set Score = 0 → Set Coins = 0 → Set hasKey = 0 → Set gLastCheckpoint = ""Chame essa função ao:
- Clicar em “Jogar” (se sempre começar do zero)
- Clicar em “Tentar de novo”
- Clicar em “Reiniciar” no pause
- Voltar ao Menu após Game Over/Vitória (se você quiser limpar estado)
Game Over e Vitória: disparo e navegação
Disparando Game Over na fase
Quando a condição de derrota acontecer (ex.: vida chegou a 0), faça a transição:
System: PlayerHP <= 0 → If gIsTransitioning = 0 → Call GoToLayout("GameOver")Se você usa animação de morte, pode esperar o fim da animação antes do fade-out (mas cuidado com múltiplos disparos: use gIsTransitioning como trava).
Disparando Vitória na fase
Quando o objetivo for cumprido (ex.: tocar no portal, coletar item final, derrotar boss):
Player overlaps objGoal → If gIsTransitioning = 0 → Call GoToLayout("Vitoria")Eventos no layout GameOver
- Tentar de novo: reseta e volta para a fase selecionada.
- Menu: reseta e volta ao menu.
On clicked btnRetry → If gIsTransitioning = 0 → Call ResetRunVariables → Call GoToLayout("Fase0" & right("0" & str(gSelectedLevel), 2))On clicked btnMenu → If gIsTransitioning = 0 → Call ResetRunVariables → Call GoToLayout("Menu")O trecho right("0" & str(gSelectedLevel), 2) ajuda a formar “01”, “02” etc. Ajuste ao seu padrão de nomes.
Eventos no layout Vitoria
Você pode oferecer “Próxima fase” se existir. Exemplo:
On clicked btnNext → If gIsTransitioning = 0 → Add 1 to gSelectedLevel → Call ResetRunVariables → Call GoToLayout("Fase0" & right("0" & str(gSelectedLevel), 2))On clicked btnMenu → If gIsTransitioning = 0 → Call ResetRunVariables → Call GoToLayout("Menu")Se não houver próxima fase, esconda o botão “Próxima” ou redirecione para o Menu.
Padronizando entradas: mouse, touch e teclado sem duplicar lógica
Uma forma prática de evitar repetir eventos é criar uma “ação de UI” por função, e chamar essa função a partir de diferentes entradas.
Exemplo: ação “Confirmar” no Menu
Function: MenuConfirm → If menuIndex=0: Set gSelectedLevel=1; Call ResetRunVariables; Call GoToLayout("Fase01") → If menuIndex=1: Call GoToLayout("SelecaoFase")Chamadas:
Keyboard: On Enter pressed → Call MenuConfirmMouse: On clicked btnPlay → Set menuIndex=0 → Call MenuConfirmTouch: On touched btnPlay → Set menuIndex=0 → Call MenuConfirmExercício guiado: ciclo completo com fade e reset
Meta do exercício
Implementar o ciclo: Menu → Fase01 → (Vitória ou GameOver) → Menu, com Pause dentro da fase, fade em todas as trocas e reset consistente.
Passo 1 — Preparar variáveis e função de reset
- Crie
gIsTransitioning,gNextLayout,gInputLockUntil,gSelectedLevel. - Crie a Function
ResetRunVariablese resete tudo que representa “uma tentativa”.
Passo 2 — Adicionar o sprFade e eventos de fade-in
- Em cada layout do ciclo (Menu, Fase01, GameOver, Vitoria), adicione
sprFadena camada Overlay. - No
On start of layout, execute o fade-in e liberegIsTransitioningao final.
Passo 3 — Criar a ação padrão de troca de layout (fade-out)
- Implemente
GoToLayout(layoutName)com travagIsTransitioning. - Garanta que nenhum botão chama
Go to layoutdireto; sempre use a transição.
Passo 4 — Menu → Fase01
- No botão “Jogar”:
gSelectedLevel=1→ResetRunVariables→GoToLayout("Fase01"). - Adicione trava de clique:
time >= gInputLockUntile atualizegInputLockUntil.
Passo 5 — Pause dentro da fase
- Crie camada Pause invisível.
- Tecla Esc alterna
isPaused. - Quando pausado: mostrar camada e pausar gameplay (time scale 0 ou desabilitar grupo Gameplay).
- Botões: Retomar, Reiniciar (reset + recarregar fase), Menu (reset + Menu).
Passo 6 — Condições de vitória e derrota
- Crie um gatilho simples de vitória (ex.: tocar em
objGoal). - Crie um gatilho simples de derrota (ex.: variável
PlayerHPchegar a 0, ou cair fora da tela). - Em ambos: checar
gIsTransitioning=0antes de chamarGoToLayout.
Passo 7 — GameOver/Vitoria → Menu
- Em GameOver: botão Menu chama
ResetRunVariableseGoToLayout("Menu"). - Em Vitória: botão Menu faz o mesmo.
Checklist de validação (teste rápido)
| Teste | Resultado esperado |
|---|---|
| Clicar rapidamente várias vezes em “Jogar” | Apenas uma transição acontece |
| Apertar Enter durante o fade-in do Menu | Nenhuma ação até o fim do fade |
| Pausar e clicar em “Menu” | Volta ao Menu com fade e sem estado “preso” |
| Perder (Game Over) e apertar “Tentar de novo” | Fase reinicia com variáveis resetadas |
| Vencer e voltar ao Menu | Menu abre limpo, sem travas de input |