Aeca

Capítulo 16 — Nosso primeiro mini Game Loop no console

Série: Do zero ao jogo comercial com C++ e SFML

Capítulo 16 — Nosso primeiro mini Game Loop no console
aeca

Pela primeira vez, nosso programa vai deixar de executar uma sequência curta e começar a se comportar como uma aplicação de jogo em execução contínua

Até agora construímos várias peças fundamentais.

Aprendemos a armazenar estado:

  int playerHealth = 100;
int playerCoins = 0;

Representamos valores que não deveriam mudar:

  const int DAMAGE_PER_FRAME = 5;

Criamos decisões:

  if (playerHealth <= 0)
{
    // ...
}

Representamos estados nomeados:

  enum class GameState
{
    Menu,
    Playing,
    GameOver
};

E aprendemos a repetir ações:

  while (condition)
{
    // ...
}

e:

  for (int index = 0; index < 5; index++)
{
    // ...
}

Cada conceito foi estudado separadamente.

Agora chegou o momento de começar a combiná-los.

Ainda não teremos:

  janela
sprites
teclado
mouse
áudio
SFML
renderização

Mas construiremos algo muito importante:

uma primeira aproximação do ciclo contínuo que mantém um jogo executando.

Nosso primeiro mini Game Loop no console.


1. O problema do nosso programa atual

Considere:

  #include <iostream>

int main()
{
    int playerHealth = 100;

    playerHealth -= 10;

    std::cout << "Health: " << playerHealth << std::endl;

    return 0;
}

O fluxo é:

  iniciar
↓
criar estado
↓
alterar Health
↓
mostrar Health
↓
encerrar

Tudo acontece uma vez.

Um jogo real precisa de algo muito diferente.

Enquanto estiver executando, ele precisa continuar realizando trabalho.

Conceitualmente:

  iniciar jogo
↓
processar
↓
atualizar
↓
mostrar estado
↓
verificar se continua
↓
repetir
↓
repetir
↓
repetir

Precisamos de um ciclo.


2. O coração de uma aplicação de jogo

Podemos imaginar algo muito simplificado:

  ENQUANTO o jogo estiver rodando

    processar
    atualizar
    mostrar
    verificar

Em C++:

  while (isRunning)
{
    // Process
    // Update
    // Show
    // Check
}

Isso ainda não é um Game Loop profissional.

Mas já contém a ideia central:

o jogo continua executando enquanto uma condição de continuidade permanecer verdadeira.


3. Precisamos representar se o jogo continua rodando

Podemos utilizar algo que já conhecemos:

  bool

Então:

  bool isRunning = true;

Agora:

  while (isRunning)
{
}

significa:

enquanto isRunning for verdadeiro, continue executando o loop.

Se em algum momento fizermos:

  isRunning = false;

na próxima verificação da condição o loop termina.


4. Nossa primeira versão mínima

Vamos começar com:

  #include <iostream>

int main()
{
    bool isRunning = true;
    int frame = 0;

    while (isRunning)
    {
        frame++;

        std::cout << "Frame: " << frame << std::endl;

        if (frame >= 5)
        {
            isRunning = false;
        }
    }

    std::cout << "Game loop finished." << std::endl;

    return 0;
}

Resultado:

  Frame: 1
Frame: 2
Frame: 3
Frame: 4
Frame: 5
Game loop finished.

Pela primeira vez temos um programa que permanece dentro de um ciclo até uma condição determinar seu encerramento.


5. Por que chamamos a variável de frame?

Neste exemplo, frame é apenas um contador de iterações.

Estamos utilizando a palavra para começar a aproximar nosso vocabulário do desenvolvimento de jogos.

Mas atenção:

essas iterações ainda não correspondem a frames gráficos reais.

Não temos:

  renderização
monitor
GPU
sincronização
deltaTime
FPS

Então, neste capítulo:

  int frame

é apenas um contador didático das passagens pelo loop.


6. O ciclo completo da primeira versão

Começamos:

  isRunning = true
frame = 0

A condição:

  while (isRunning)

é verdadeira.

Entramos.

Executamos:

  frame++;

Agora:

  frame = 1

Mostramos:

  Frame: 1

Testamos:

  frame >= 5

Resultado:

  false

Voltamos para:

  while (isRunning)

Como:

  isRunning = true

continuamos.


7. O momento do encerramento

Na quinta iteração:

  frame = 5

Então:

  if (frame >= 5)

produz:

  true

Executamos:

  isRunning = false;

Importante:

o programa não desaparece imediatamente naquele ponto.

Ele termina o restante da iteração atual.

Depois volta para:

  while (isRunning)

Agora:

  isRunning = false

e o loop termina.


8. Uma condição de continuidade

Essa ideia aparece em praticamente toda aplicação interativa:

  devo continuar?

Em nosso exemplo:

  bool isRunning

representa exatamente isso.

Podemos pensar:

  true
→ continuar

false
→ encerrar

Ainda estamos definindo artificialmente quando o jogo termina.

Mais tarde essa condição poderá depender de eventos reais, como fechar uma janela.


9. Vamos adicionar um estado do jogo

No capítulo de enum class, aprendemos a representar estados nomeados.

Podemos reutilizar isso:

  enum class GameState
{
    Menu,
    Playing,
    GameOver
};

Agora:

  GameState currentState = GameState::Menu;

Nosso programa possui:

  estado da aplicação + condição de execução

São conceitos diferentes.


10. GameState e isRunning não representam a mesma coisa

Podemos ter:

  GameState currentState = GameState::Playing;
bool isRunning = true;

O primeiro responde:

  Em qual estado o jogo está?

O segundo:

  A aplicação deve continuar executando?

Por exemplo:

  GameState::GameOver

não precisa significar necessariamente que a aplicação deve fechar imediatamente.

Um jogo poderia mostrar uma tela de Game Over e continuar rodando.

Nosso exemplo será mais simples, mas essa distinção conceitual importa.


11. Uma primeira transição de estado

Podemos começar em:

  GameState currentState = GameState::Menu;

Depois, durante o primeiro ciclo, simular:

  currentState = GameState::Playing;

Não existe entrada real do jogador ainda.

Estamos simulando uma transição apenas para observar o funcionamento do loop.


12. Montando nosso estado inicial

Vamos utilizar:

  bool isRunning = true;
int frame = 0;
int playerHealth = 100;
int playerCoins = 0;

GameState currentState = GameState::Menu;

Temos:

  isRunning → continuidade da aplicação

frame → iteração atual

playerHealth → estado do jogador

playerCoins → estado econômico simples

currentState → estado geral do jogo

Nosso pequeno programa começa a possuir diversas informações evoluindo ao longo do tempo.


13. Uma primeira organização mental do Game Loop

Vamos adotar quatro etapas didáticas:

  1. PROCESS
2. UPDATE
3. SHOW STATE
4. CHECK

Ainda não são módulos.

Não são classes.

Não são funções.

São apenas responsabilidades conceituais dentro do loop.


14. Etapa 1 — Process

Em um jogo real, Process poderá envolver:

  teclado
mouse
controle
eventos
comandos
rede

Nós ainda não temos nada disso.

Então simularemos uma entrada.

Por exemplo, quando estivermos no menu:

  if (currentState == GameState::Menu)
{
    std::cout << "Starting game." << std::endl;

    currentState = GameState::Playing;
}

É uma entrada simulada.

O objetivo é observar uma mudança de estado.


15. Etapa 2 — Update

Se o jogo estiver:

  GameState::Playing

vamos alterar o estado.

Por exemplo:

  playerCoins += 1;
playerHealth -= 5;

Isso simula uma atualização extremamente simples.

A cada iteração:

  Coins aumenta
Health diminui

Não estamos criando regras finais de gameplay.

São transformações didáticas para tornar o ciclo visível.


16. Etapa 3 — Show State

Precisamos observar o que está acontecendo.

Podemos mostrar:

  std::cout << "Frame: " << frame << std::endl;
std::cout << "Health: " << playerHealth << std::endl;
std::cout << "Coins: " << playerCoins << std::endl;

Isso funciona como nossa forma provisória de visualização.

No futuro essa responsabilidade deixará de ser texto no console e passará por sistemas gráficos.


17. Etapa 4 — Check

Finalmente verificamos se alguma condição exige mudança.

Por exemplo:

  if (playerHealth <= 0)
{
    playerHealth = 0;
    currentState = GameState::GameOver;
}

Depois:

  if (currentState == GameState::GameOver)
{
    isRunning = false;
}

Assim:

  Health chega a zero
↓
GameState vira GameOver
↓
isRunning vira false
↓
loop termina

18. Nosso primeiro mini Game Loop completo

Agora podemos reunir tudo:

  #include <iostream>

enum class GameState
{
    Menu,
    Playing,
    GameOver
};

int main()
{
    bool isRunning = true;
    int frame = 0;
    int playerHealth = 100;
    int playerCoins = 0;

    GameState currentState = GameState::Menu;

    while (isRunning)
    {
        frame++;

        std::cout << std::endl;
        std::cout << "=== FRAME " << frame << " ===" << std::endl;

        // 1. Process
        if (currentState == GameState::Menu)
        {
            std::cout << "Starting game." << std::endl;

            currentState = GameState::Playing;
        }

        // 2. Update
        if (currentState == GameState::Playing)
        {
            playerCoins += 1;
            playerHealth -= 5;
        }

        // 3. Show State
        std::cout << "Health: " << playerHealth << std::endl;
        std::cout << "Coins: " << playerCoins << std::endl;

        // 4. Check
        if (playerHealth <= 0)
        {
            playerHealth = 0;
            currentState = GameState::GameOver;
        }

        if (currentState == GameState::GameOver)
        {
            std::cout << "Player defeated." << std::endl;

            isRunning = false;
        }
    }

    std::cout << std::endl;
    std::cout << "Game loop finished." << std::endl;

    return 0;
}

Agora temos uma pequena aplicação executando continuamente até uma regra encerrá-la.


19. Existe um detalhe na primeira iteração

Começamos:

  currentState = GameState::Menu;

No Process:

  if (currentState == GameState::Menu)
{
    currentState = GameState::Playing;
}

Logo depois, na mesma iteração, chegamos a:

  if (currentState == GameState::Playing)

Agora a condição já é verdadeira.

Portanto a atualização ocorre no primeiro ciclo.

Esse comportamento é consequência da ordem das instruções.


20. Ordem dentro do loop importa

Temos:

  Process
↓
Update
↓
Show
↓
Check

Se mudássemos essa ordem, poderíamos mudar o comportamento.

Por exemplo:

  Check
↓
Update

não é necessariamente equivalente a:

  Update
↓
Check

Imagine Health:

  5

e Update:

  -5

Se verificarmos antes:

  5 <= 0 → false

Depois Health chega a 0.

A detecção só ocorreria em outro momento.

Ordem de responsabilidades importa.


21. Nosso loop ainda tem um pequeno problema visual

No código anterior, fazemos:

  playerHealth -= 5;

Depois mostramos:

  Health

Só então verificamos:

  playerHealth <= 0

Na última iteração, o valor pode já estar:

  0

o que funciona.

Mas se o dano fosse:

  playerHealth -= 30;

poderíamos mostrar temporariamente:

  Health: -20

antes de corrigir para zero.

Isso revela que talvez a validação deva acontecer antes da apresentação do estado final daquela iteração.


22. Refinando a ordem

Uma sequência melhor para nosso exemplo pode ser:

  PROCESS
↓
UPDATE
↓
CHECK
↓
SHOW STATE

Agora:

  1. simulamos entrada;

  2. atualizamos;

  3. garantimos consistência;

  4. mostramos o estado validado.

Isso evita mostrar Health negativo.

Nosso mini loop pode evoluir.


23. Refatorando a ordem conceitual

Dentro do while:

  // 1. Process

// 2. Update

// 3. Check

// 4. Show State

Observe:

ainda não estamos criando funções.

Estamos apenas tornando as responsabilidades mais explícitas.


24. Um limite de segurança para o exercício

Nosso loop já possui uma condição natural de saída:

  Health chega a zero

Com:

  playerHealth -= 5;

ele terminará.

Mas durante aprendizado é útil evitar que um erro acidental produza uma execução indefinida.

Podemos utilizar temporariamente:

  const int MAX_FRAMES = 100;

e:

  if (frame >= MAX_FRAMES)
{
    isRunning = false;
}

Isso funciona como uma proteção didática.

Não é uma arquitetura de produção.


25. Por que uma proteção pode ajudar?

Imagine que por engano removamos:

  playerHealth -= 5;

Agora Health nunca chega a zero.

Então:

  currentState

nunca chega a:

  GameState::GameOver

e:

  isRunning

pode permanecer verdadeiro indefinidamente.

Durante um exercício, MAX_FRAMES ajuda a impedir isso.


26. Isso não significa que jogos reais terminam após 100 frames

É importante separar:

  proteção de exercício

de:

  regra real do jogo

Um jogo real não terá:

  if (frame >= 100)
{
    closeGame();
}

apenas para evitar loop infinito.

Mais tarde nossa continuidade será controlada por eventos e pelo lifecycle da aplicação.


27. Código final refinado

Vamos utilizar:

  #include <iostream>

enum class GameState
{
    Menu,
    Playing,
    GameOver
};

int main()
{
    const int DAMAGE_PER_FRAME = 5;
    const int COINS_PER_FRAME = 1;
    const int MAX_FRAMES = 100;

    bool isRunning = true;
    int frame = 0;
    int playerHealth = 100;
    int playerCoins = 0;

    GameState currentState = GameState::Menu;

    std::cout << "=== MINI GAME LOOP START ===" << std::endl;

    while (isRunning)
    {
        frame++;

        // 1. Process
        if (currentState == GameState::Menu)
        {
            std::cout << "Starting game." << std::endl;

            currentState = GameState::Playing;
        }

        // 2. Update
        if (currentState == GameState::Playing)
        {
            playerHealth -= DAMAGE_PER_FRAME;
            playerCoins += COINS_PER_FRAME;
        }

        // 3. Check
        if (playerHealth <= 0)
        {
            playerHealth = 0;
            currentState = GameState::GameOver;
        }

        if (frame >= MAX_FRAMES)
        {
            isRunning = false;
        }

        // 4. Show State
        std::cout << std::endl;
        std::cout << "Frame: " << frame << std::endl;
        std::cout << "Health: " << playerHealth << std::endl;
        std::cout << "Coins: " << playerCoins << std::endl;

        if (currentState == GameState::Playing)
        {
            std::cout << "State: Playing" << std::endl;
        }

        if (currentState == GameState::GameOver)
        {
            std::cout << "State: GameOver" << std::endl;
            std::cout << "Player defeated." << std::endl;

            isRunning = false;
        }
    }

    std::cout << std::endl;
    std::cout << "=== MINI GAME LOOP FINISHED ===" << std::endl;
    std::cout << "Frames: " << frame << std::endl;
    std::cout << "Final Health: " << playerHealth << std::endl;
    std::cout << "Final Coins: " << playerCoins << std::endl;

    return 0;
}

Agora temos nossa primeira implementação que realmente merece ser chamada de:

mini Game Loop didático.


28. Qual será o resultado?

Começamos:

  Health = 100
Coins = 0

A cada frame:

  Health -= 5
Coins += 1

Então:

  Frame 1
Health 95
Coins 1

Frame 2
Health 90
Coins 2

Frame 3
Health 85
Coins 3

E assim por diante.

Depois de vinte atualizações:

  Health = 0
Coins = 20

Então:

  GameState::GameOver

e finalmente:

  isRunning = false

29. Podemos prever o número de iterações

Temos:

  Initial Health = 100
Damage per Frame = 5

Então:

  100 / 5 = 20

Esperamos aproximadamente vinte atualizações até Health chegar a zero.

Isso nos dá um resultado esperado que podemos utilizar na validação.


30. Resultado final esperado

Ao término:

  Frames: 20
Final Health: 0
Final Coins: 20

Por quê?

Porque a cada uma das vinte iterações:

  Health perde 5
Coins ganha 1

Temos:

  20 × 5 = 100 de dano
20 × 1 = 20 moedas

Nosso mini loop produz um estado previsível.


31. Isso é importante para testes

Podemos definir:

  Entrada inicial

Health = 100
Coins = 0
Damage = 5
Coins per Frame = 1

Resultado esperado:

  Frames = 20
Health = 0
Coins = 20
State = GameOver

Agora executamos e comparamos.

Esse pensamento será essencial quando começarmos a automatizar testes.


32. Ainda não temos um verdadeiro conceito de tempo

Observe:

  playerHealth -= DAMAGE_PER_FRAME;

Estamos dizendo:

  por iteração

e não:

  por segundo

Nossa execução depende da velocidade com que o computador percorre o loop.

Não existe:

  deltaTime
elapsedTime
clock
frame time
fixed timestep

ainda.

Portanto:

frame neste capítulo não é uma unidade de tempo confiável.

Essa distinção será fundamental quando chegarmos ao verdadeiro Game Loop gráfico.


33. Não coloque regras reais dependentes da velocidade do loop

Imagine futuramente:

  playerPosition += 1.0f;

a cada iteração.

Em uma máquina mais rápida, isso poderia acontecer mais vezes por segundo.

Em outra, menos.

O movimento ficaria dependente da velocidade de execução.

Esse problema é uma das razões pelas quais sistemas de tempo são fundamentais em games.

Mas ainda não precisamos resolver isso.

Primeiro estamos aprendendo a estrutura.


34. O mini Game Loop tem quatro responsabilidades conceituais

Nossa versão atual mostra:

  PROCESS
↓
UPDATE
↓
CHECK
↓
SHOW

Mais tarde essas ideias serão refinadas.

Talvez vejamos estruturas como:

  Process Events
Handle Input
Update
Render

ou divisões ainda mais sofisticadas.

Mas o princípio já começou a aparecer:

um loop central coordena diferentes responsabilidades do jogo.


35. Por enquanto são apenas comentários

Temos:

  // 1. Process

// 2. Update

// 3. Check

// 4. Show State

Isso não cria arquitetura.

São marcadores visuais.

Mais tarde provavelmente veremos algo parecido com:

  ProcessInput();
Update();
Render();

Mas ainda não aprendemos funções.

Não vamos utilizá-las antes de explicar o problema que elas resolvem.


36. E o problema está começando a aparecer

Observe nosso main().

Ele agora possui:

  constantes
variáveis
estado
while
processamento
atualização
validação
exibição
encerramento

Tudo dentro de um único método.

O código ainda é compreensível.

Mas já começa a crescer.

Esse crescimento é importante.

Ele está preparando naturalmente nosso próximo assunto.


37. Não vamos resolver isso antecipadamente

Seria tentador escrever imediatamente:

  Process();
Update();
Check();
Render();

Mas até agora ainda não estudamos:

  declaração de funções
definição
assinatura
parâmetros
retorno
chamada

Se fizéssemos isso agora, estaríamos apenas escondendo o problema atrás de uma sintaxe não explicada.

Primeiro precisamos sentir a necessidade.

Agora estamos começando a senti-la.


38. Erro proposital — nunca encerrar o loop

Remova temporariamente:

  playerHealth -= DAMAGE_PER_FRAME;

e também remova ou aumente bastante a proteção de MAX_FRAMES.

O que acontece?

playerHealth continua:

  100

Então:

  playerHealth <= 0

nunca será verdadeiro.

GameState::GameOver não será atingido.

Logo:

  isRunning

permanece verdadeiro.

Podemos criar uma repetição indefinida.

Interrompa a execução e investigue.


39. Método de investigação

Pergunte:

  Qual condição mantém o while executando?

isRunning

Depois:

  Onde isRunning deveria mudar?

E:

  Qual condição leva até essa mudança?

Por exemplo:

  Health <= 0

Depois:

  Health está sendo atualizado?

Essa cadeia de perguntas é mais útil do que alterar código aleatoriamente.


40. Erro proposital — encerrar cedo demais

Agora faça:

  bool isRunning = false;

antes do loop.

Temos:

  while (isRunning)

que equivale a:

  while (false)

Resultado:

  0 iterações

O programa pula diretamente para:

  MINI GAME LOOP FINISHED

Isso confirma novamente:

while testa antes de executar.


41. Erro proposital — dano muito alto

Altere:

  const int DAMAGE_PER_FRAME = 60;

Temos:

  Frame 1
Health = 40

Frame 2
Health = -20

Mas nosso Check faz:

  if (playerHealth <= 0)
{
    playerHealth = 0;
    currentState = GameState::GameOver;
}

Portanto mostramos:

  Health: 0

e não:

  Health: -20

Nossa validação protege a representação final.


42. Testando diferentes cenários

Use:

Cenário A

  const int DAMAGE_PER_FRAME = 10;

Esperado:

  10 frames

Cenário B

  const int DAMAGE_PER_FRAME = 25;

Esperado:

  4 frames

Cenário C

  const int DAMAGE_PER_FRAME = 100;

Esperado:

  1 frame

Verifique se os resultados correspondem à regra.


43. Um cenário interessante: dano que não divide exatamente a vida

Use:

  const int DAMAGE_PER_FRAME = 30;

Temos:

  100
↓
70
↓
40
↓
10
↓
-20

Depois corrigimos para:

  0

Logo esperamos:

  4 frames

Não:

  3.333...

Estamos contando iterações discretas do loop.


44. Tabela de cenários

Podemos organizar:

Health inicial

Dano por frame

Frames esperados

Health final

1005200100101001002540100304010010010

Agora já temos uma pequena especificação de comportamento.


45. Observe como vários conhecimentos estão trabalhando juntos

Temos constantes:

  const int DAMAGE_PER_FRAME = 5;

Variáveis:

  int playerHealth = 100;

Boolean:

  bool isRunning = true;

Enumeração:

  GameState currentState = GameState::Menu;

Loop:

  while (isRunning)

Decisões:

  if (...)

Operadores:

  -=
+=
++
<=
>=
==

Tudo aquilo que aprendemos está começando a formar um comportamento maior.


46. Esse é um marco importante da série

Nosso programa não é mais apenas:

  algumas instruções isoladas

Agora temos:

  estado
+
repetição
+
decisão
+
transformação
+
encerramento

Isso começa a se parecer com uma aplicação interativa, mesmo que ainda não exista interação real.

É nossa primeira base conceitual de Game Loop.


47. Mas ainda não é o Game Loop que teremos na Engine

Precisamos deixar isso muito claro.

Nosso loop atual não possui:

  event polling
input real
window lifecycle
renderer
time step
delta time
fixed update
variable update
frame limiting
vertical synchronization
resource lifecycle
scene management
audio
physics

Não devemos pegar este código e tratá-lo como arquitetura final.

Ele existe para ensinar a ideia central:

  continuar
↓
processar
↓
atualizar
↓
validar
↓
mostrar
↓
repetir

48. A Engine nascerá de problemas reais

Quando tivermos uma janela, surgirá um problema de lifecycle.

Quando tivermos input, surgirá processamento de eventos.

Quando tivermos movimento, surgirá o problema de tempo.

Quando tivermos múltiplas telas, surgirá o problema de cenas.

Quando tivermos recursos, surgirá o problema de ownership e gerenciamento.

Só então abstrações como:

  Application
Game
Scene
Renderer
Input
Time

começarão a fazer sentido.

Hoje não precisamos delas.


49. Estrutura atual do projeto

Continuamos:

  CppGameEngine/
└── main.cpp

Sim.

Mesmo com nosso primeiro mini Game Loop.

Ainda não vamos criar pastas apenas para antecipar arquitetura futura.

O código ainda cabe em um único arquivo.

Mas algo está mudando.

main.cpp já está começando a acumular responsabilidades.

Essa será nossa próxima dor real.


50. Validando o código final

Salve:

  Ctrl + S

Faça Build:

  Ctrl + Shift + B

Confirme:

  Build succeeded

Execute:

  Ctrl + F5

Verifique especialmente:

  Starting game.

Depois as atualizações sucessivas.

E no final:

  State: GameOver
Player defeated.

seguido por:

  MINI GAME LOOP FINISHED

51. Checklist de compreensão

Antes de avançar, tente responder sem consultar o artigo:

  • O que estamos chamando de mini Game Loop?

  • Qual papel isRunning possui?

  • Por que utilizamos while?

  • Qual diferença existe entre GameState e isRunning?

  • O que significa Process?

  • O que significa Update?

  • Por que precisamos validar o estado depois de atualizá-lo?

  • Por que a ordem das etapas importa?

  • Por que nosso frame não representa ainda um frame gráfico real?

  • Por que não devemos relacionar gameplay real à velocidade deste loop?

  • Para que serve MAX_FRAMES neste exercício?

  • Por que essa proteção não representa uma regra real de produção?

  • Como um loop infinito poderia surgir?

  • Por que nosso main.cpp começou a ficar mais difícil de organizar?

  • Por que ainda não criamos funções?

Se consegue explicar essas respostas, estamos prontos para a próxima evolução.


52. Git

Depois da validação:

  git status

Adicione:

  git add CppGameEngine/main.cpp

Ajuste o caminho caso sua estrutura local seja diferente.

Commit sugerido:

  git commit -m "feat: add first console mini game loop"

Esse commit marca um ponto importante do projeto:

nosso programa passou a executar atualizações repetidas enquanto uma condição de execução permanece ativa.


53. Atualizando o CHANGELOG

Em [Unreleased]:

  ### Added

- Added the first console-based mini Game Loop.
- Added continuous execution controlled by an `isRunning` state.
- Added basic `GameState` transitions between menu, playing, and game over.
- Added repeated player state updates for health and coins.
- Added loop termination based on gameplay state.
- Added a temporary maximum-frame safeguard for learning scenarios.

Depois:

  git add CHANGELOG.md
git commit -m "docs: update changelog with first mini game loop"

Neste ponto ainda não precisamos alterar a arquitetura descrita no ROADMAP técnico.


54. O que aprendemos?

Antes deste capítulo sabíamos repetir:

  while (condition)
{
}

Mas hoje utilizamos essa repetição para construir algo maior:

  while (isRunning)
{
    // Process
    // Update
    // Check
    // Show State
}

Aprendemos que:

  • uma aplicação de jogo precisa de um mecanismo de execução contínua;

  • isRunning pode representar a condição de continuidade;

  • estado do jogo e lifecycle da aplicação são conceitos distintos;

  • cada iteração pode transformar o estado;

  • a ordem Process → Update → Check → Show pode alterar o comportamento;

  • um loop precisa possuir uma condição real de término;

  • erros na atualização podem gerar loops infinitos;

  • frame ainda é apenas um contador didático;

  • não possuímos controle temporal real;

  • este mini loop não é nossa arquitetura final;

  • conceitos anteriores começaram a trabalhar juntos dentro de um único fluxo;

  • nosso main.cpp começou a acumular responsabilidades.


Conclusão

Este capítulo representa uma mudança importante em nossa jornada.

Começamos a série com:

  std::cout << "Game Engineering com C++ começou!" << std::endl;

Depois nosso programa aprendeu:

  armazenar estado
↓
representar tipos
↓
proteger constantes
↓
realizar operações
↓
avaliar expressões
↓
tomar decisões
↓
escolher alternativas
↓
representar estados
↓
repetir ações

Agora finalmente juntamos tudo em:

  INICIAR
↓
ENQUANTO ESTIVER RODANDO
│
├── PROCESSAR
├── ATUALIZAR
├── VALIDAR
└── MOSTRAR
│
└───────────────┐
                │
                ▼
             REPETIR

Nosso programa passou a ter um ciclo de execução.

Ainda é pequeno.

Ainda é console.

Ainda não existe SFML.

Mas conceitualmente demos um dos passos mais importantes da série.

Temos pela primeira vez algo que se aproxima da estrutura central de um jogo.

E exatamente por isso um novo problema ficou muito visível.

Nosso main() agora faz tudo:

  cria variáveis
define estado
controla loop
processa
atualiza
valida
mostra
encerra

Se continuarmos adicionando comportamento dessa maneira, ele crescerá rapidamente.

Imagine adicionar:

  player
enemy
inventory
farm
time
weather
NPC
input
collision
rendering

dentro do mesmo main().

Claramente isso não vai escalar.

Precisamos começar a separar comportamentos em unidades menores.

E é exatamente daí que nasce nosso próximo assunto.


Próximo capítulo

Capítulo 17 — Funções: nosso main.cpp começou a crescer

Série: Do zero ao jogo comercial com C++ e SFML

Partiremos do mini Game Loop que acabamos de construir.

Hoje temos algo conceitualmente parecido com:

  while (isRunning)
{
    // Process

    // Update

    // Check

    // Show State
}

Mas todo o comportamento continua dentro de:

  int main()

No próximo capítulo vamos observar o problema real:

  main.cpp cresce
↓
responsabilidades se misturam
↓
código fica difícil de navegar
↓
precisamos separar comportamentos

Só então introduziremos funções.

Começaremos entendendo:

  • o que uma função realmente é;

  • por que ela precisa existir;

  • declaração e definição;

  • nome da função;

  • tipo de retorno;

  • parênteses;

  • corpo;

  • chamada;

  • fluxo de execução;

  • como o programa entra e retorna de uma função;

  • por que funções não existem apenas para “diminuir código”;

  • como separar responsabilidades sem criar abstrações prematuras;

  • como nosso mini Game Loop ficará mais legível;

  • e por que esse será nosso primeiro passo real para tirar responsabilidades de dentro do main().

Vamos evoluir de algo como:

  while (isRunning)
{
    // várias instruções
}

para uma estrutura progressivamente mais clara, conceitualmente próxima de:

  while (isRunning)
{
    Process();
    Update();
    Check();
    ShowState();
}

Mas desta vez não vamos apenas copiar essas chamadas.

Vamos compreender como uma função nasce, por que ela existe e o que realmente acontece quando ela é chamada.

Nosso Game Loop finalmente existe.

Agora nosso próximo problema é torná-lo organizado o suficiente para continuar crescendo.

Subscreve "Aeca" para receber atualizações diretamente na tua caixa de entrada
aeca

Subscreve aeca para reagir

Subscrever

Comentários

Ainda não há comentários. Sê o primeiro a comentar!

Subscreve Aeca para receber atualizações diretamente na tua caixa de entrada