Capítulo 7 — Cenas, Menus e Arquitetura do Jogo no MonoGame

Quando o protótipo precisa virar jogo de verdade

Até aqui, você já per­cor­reu uma base essen­cial para cri­ar jogos com MonoGame.

Você enten­deu o que é o frame­work, preparou o ambi­ente, estu­dou o game loop, desen­hou sprites, car­regou tex­turas, cap­tur­ou entra­da do jogador e começou a tra­bal­har com movi­men­tação, col­isão e físi­ca sim­ples.

Ago­ra chega um dos capí­tu­los mais impor­tantes para sair do “pro­tótipo bagunça­do” e entrar em uma estru­tu­ra mais profis­sion­al:

cenas, menus e arquite­tu­ra do jogo.

No começo, é nor­mal colo­car tudo den­tro do Game1.cs. Você colo­ca ali o jogador, o inimi­go, o fun­do, o tecla­do, a col­isão, o score, o menu, o pause, o game over e tudo mais.

Fun­ciona por um tem­po.

Mas, con­forme o jogo cresce, o arqui­vo vira um mon­stro. Fica difí­cil enten­der, tes­tar, cor­ri­gir e evoluir.

É exata­mente aqui que entra a arquite­tu­ra.

No MonoGame, a classe Game é o pon­to de entra­da da maio­r­ia dos jogos e é respon­sáv­el por rodar o game loop, chaman­do Update(GameTime) e Draw(GameTime). Ou seja, o MonoGame entre­ga a base do ciclo, mas a orga­ni­za­ção inter­na do seu jogo fica por sua con­ta.

Isso sig­nifi­ca que você pre­cisa cri­ar sua própria for­ma de orga­ni­zar:

🎮 tela ini­cial;
📜 menu prin­ci­pal;
⏸️ pause;
🧱 fase do jogo;
💀 game over;
🏆 vitória;
⚙️ opções;
💾 car­rega­men­to;
🔄 tran­sições;
🧠 esta­dos do jogo;
🧩 class­es e respon­s­abil­i­dades.

Este capí­tu­lo ensi­na exata­mente isso.


🧠 O problema do Game1.cs gigante

Todo pro­je­to MonoGame começa com uma classe prin­ci­pal pare­ci­da com esta:

public class Game1 : Game
{
protected override void Initialize()
{
base.Initialize();
}

protected override void LoadContent()
{
}

protected override void Update(GameTime gameTime)
{
base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
base.Draw(gameTime);
}
}

No iní­cio, colo­car tudo ali parece nat­ur­al.

Você adi­ciona o jogador:

private Player _player;

Depois o inimi­go:

private Enemy _enemy;

Depois o menu:

private int _selectedMenuIndex;

Depois a pon­tu­ação:

private int _score;

Depois a lista de moedas:

private List<Coin> _coins;

Depois o pause:

private bool _isPaused;

Depois o game over:

private bool _isGameOver;

Depois a tela de vitória:

private bool _hasWon;

E quan­do você percebe, o Game1.cs virou um arqui­vo enorme com cen­te­nas ou mil­hares de lin­has.

O prob­le­ma não é ape­nas o taman­ho. O prob­le­ma é que tudo fica mis­tu­ra­do:

  • lóg­i­ca de menu;
  • lóg­i­ca de game­play;
  • lóg­i­ca de pause;
  • lóg­i­ca de game over;
  • desen­ho do HUD;
  • input;
  • col­isão;
  • car­rega­men­to de assets;
  • regras do jogo;
  • tran­sições;
  • ini­cial­iza­ção;
  • reiní­cio de fase.

Isso tor­na o pro­je­to difí­cil de evoluir.

A arquite­tu­ra serve para impedir esse caos.


🧩 O que é arquitetura de jogo?

Arquite­tu­ra de jogo é a for­ma como você orga­ni­za o códi­go, os sis­temas, as respon­s­abil­i­dades e o fluxo do pro­je­to.

Em um jogo sim­ples, você pode até sobre­viv­er com um arqui­vo prin­ci­pal grande.

Mas em um jogo real, você pre­cisa sep­a­rar respon­s­abil­i­dades.

A per­gun­ta cen­tral da arquite­tu­ra é:

Quem faz o quê den­tro do jogo?

Por exem­p­lo:

Respon­s­abil­i­dadeClasse ou sis­tema ide­al
Ler tecla­do, mouse e con­t­roleInputManager
Con­tro­lar cenasSceneManager
Tela ini­cialMenuScene
Game­playGameScene
PausePauseScene
Game overGameOverScene
JogadorPlayer
Inimi­gosEnemy
BotõesButton
Pon­tu­ação e vidaHud
Assets globaisContentManager ou serviço próprio
Con­fig­u­raçõesGameSettings

Arquite­tu­ra não é com­plicar. É orga­ni­zar para crescer.


🎬 O que são cenas em um jogo?

Uma cena é uma parte ou tela do jogo com com­por­ta­men­to próprio.

Exem­p­los:

🎮 MenuScene — tela ini­cial com botões;
🧱 GameScene — fase prin­ci­pal do jogo;
⏸️ Paus­eScene — tela de pausa;
💀 GameOver­Scene — tela de fim de jogo;
🏆 Vic­to­ryScene — tela de vitória;
⚙️ Option­sS­cene — con­fig­u­rações;
📜 Cred­itsS­cene — crédi­tos.

Cada cena pos­sui sua própria lóg­i­ca de atu­al­iza­ção e desen­ho.

Por exem­p­lo, no menu:

  • setas mudam a opção sele­ciona­da;
  • Enter con­fir­ma;
  • mouse cli­ca em botões;
  • o fun­do pode ter ani­mação leve.

Na fase do jogo:

  • jogador anda;
  • inimi­gos se movem;
  • col­isões acon­te­cem;
  • moedas são cole­tadas;
  • vida e pon­tu­ação mudam.

No game over:

  • o jogador não se move mais;
  • aparece men­sagem de der­ro­ta;
  • Enter reini­cia;
  • Escape vol­ta ao menu.

Cada uma dessas telas é uma cena difer­ente.


🧠 Por que usar cenas?

Porque cada tela do jogo tem regras difer­entes.

Sem cenas, você começa a cri­ar muitos if den­tro do Update:

if (_currentState == GameState.Menu)
{
// lógica do menu
}
else if (_currentState == GameState.Playing)
{
// lógica do jogo
}
else if (_currentState == GameState.Paused)
{
// lógica do pause
}
else if (_currentState == GameState.GameOver)
{
// lógica do game over
}

Isso fun­ciona no começo.

Mas logo fica difí­cil de man­ter.

Com cenas, você pode orga­ni­zar assim:

_currentScene.Update(gameTime);
_currentScene.Draw(spriteBatch);

O Game1.cs não pre­cisa saber todos os detal­h­es da tela atu­al. Ele ape­nas del­e­ga.

Essa é uma mudança impor­tante: o Game1.cs deixa de ser o lugar onde tudo acon­tece e pas­sa a ser o coor­de­nador ger­al do jogo.


🧭 Primeira abordagem: GameState simples

Antes de cri­ar um sis­tema com­ple­to de cenas, vale enten­der a abor­dagem mais sim­ples: usar um enum de esta­dos.

public enum GameState
{
MainMenu,
Playing,
Paused,
GameOver,
Victory
}

No Game1.cs:

private GameState _currentState = GameState.MainMenu;

No Update:

protected override void Update(GameTime gameTime)
{
switch (_currentState)
{
case GameState.MainMenu:
UpdateMainMenu(gameTime);
break;

case GameState.Playing:
UpdateGameplay(gameTime);
break;

case GameState.Paused:
UpdatePause(gameTime);
break;

case GameState.GameOver:
UpdateGameOver(gameTime);
break;

case GameState.Victory:
UpdateVictory(gameTime);
break;
}

base.Update(gameTime);
}

No Draw:

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);

_spriteBatch.Begin();

switch (_currentState)
{
case GameState.MainMenu:
DrawMainMenu();
break;

case GameState.Playing:
DrawGameplay();
break;

case GameState.Paused:
DrawGameplay();
DrawPauseOverlay();
break;

case GameState.GameOver:
DrawGameOver();
break;

case GameState.Victory:
DrawVictory();
break;
}

_spriteBatch.End();

base.Draw(gameTime);
}

Essa abor­dagem é boa para pro­je­tos pequenos.

Mas con­forme o jogo cresce, ela ain­da deixa mui­ta coisa den­tro do Game1.cs.

Por isso, o próx­i­mo pas­so é cri­ar cenas como class­es sep­a­radas.


🧱 Criando uma interface IScene

A for­ma mais limpa de orga­ni­zar cenas é cri­ar uma inter­face.

public interface IScene
{
void LoadContent();
void Update(GameTime gameTime);
void Draw(SpriteBatch spriteBatch);
}

Essa inter­face define que toda cena pre­cisa saber:

  • car­regar con­teú­do;
  • atu­alizar lóg­i­ca;
  • desen­har na tela.

Depois, cada cena imple­men­ta essa inter­face.

Exem­p­lo:

public class MenuScene : IScene
{
public void LoadContent()
{
// carregar fontes, texturas, botões
}

public void Update(GameTime gameTime)
{
// lógica do menu
}

public void Draw(SpriteBatch spriteBatch)
{
// desenhar menu
}
}

E a fase prin­ci­pal:

public class GameScene : IScene
{
public void LoadContent()
{
// carregar jogador, inimigos, mapa
}

public void Update(GameTime gameTime)
{
// atualizar gameplay
}

public void Draw(SpriteBatch spriteBatch)
{
// desenhar fase
}
}

Ago­ra cada tela tem sua própria classe.


🎛️ Criando um SceneManager

O SceneManager é o respon­sáv­el por con­tro­lar qual cena está ati­va.

public class SceneManager
{
private IScene _currentScene;

public void ChangeScene(IScene newScene)
{
_currentScene = newScene;
_currentScene.LoadContent();
}

public void Update(GameTime gameTime)
{
_currentScene?.Update(gameTime);
}

public void Draw(SpriteBatch spriteBatch)
{
_currentScene?.Draw(spriteBatch);
}
}

No Game1.cs, você teria:

private SceneManager _sceneManager;

No Initialize:

protected override void Initialize()
{
_sceneManager = new SceneManager();
base.Initialize();
}

No LoadContent:

protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);

var menuScene = new MenuScene();
_sceneManager.ChangeScene(menuScene);
}

No Update:

protected override void Update(GameTime gameTime)
{
_sceneManager.Update(gameTime);
base.Update(gameTime);
}

No Draw:

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);

_spriteBatch.Begin();
_sceneManager.Draw(_spriteBatch);
_spriteBatch.End();

base.Draw(gameTime);
}

O Game1.cs ficou muito mais limpo.

Essa é a ideia prin­ci­pal da arquite­tu­ra: cada parte faz sua função.


⚠️ Um problema: como a cena troca para outra cena?

A MenuScene pre­cisa con­seguir ini­ciar o jogo.

Mas ela não deve cri­ar bagunça aces­san­do tudo dire­ta­mente.

Uma solução sim­ples é pas­sar uma função para a cena.

Exem­p­lo:

public class MenuScene : IScene
{
private Action _startGame;

public MenuScene(Action startGame)
{
_startGame = startGame;
}

public void LoadContent()
{
}

public void Update(GameTime gameTime)
{
KeyboardState keyboard = Keyboard.GetState();

if (keyboard.IsKeyDown(Keys.Enter))
{
_startGame.Invoke();
}
}

public void Draw(SpriteBatch spriteBatch)
{
// desenhar menu
}
}

No Game1.cs:

protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);

_sceneManager.ChangeScene(new MenuScene(() =>
{
_sceneManager.ChangeScene(new GameScene());
}));
}

Ago­ra o menu con­segue ini­ciar o jogo sem con­hecer detal­h­es inter­nos do Game1.


🧠 Cenas precisam de acesso a conteúdo

Na práti­ca, uma cena pre­cisa car­regar ima­gens, fontes e sons.

O MonoGame usa ContentManager para car­regar assets proces­sa­dos pelo Con­tent Pipeline. A doc­u­men­tação ofi­cial define ContentManager como o com­po­nente em tem­po de exe­cução que car­rega obje­tos geren­ci­a­dos a par­tir de arquiv­os .xnb pro­duzi­dos pelo MonoGame Con­tent Builder e tam­bém geren­cia o ciclo de vida dess­es con­teú­dos car­rega­dos.

Então, em vez de cri­ar cenas vazias, podemos pas­sar o ContentManager para elas.

public interface IScene
{
void LoadContent(ContentManager content);
void Update(GameTime gameTime);
void Draw(SpriteBatch spriteBatch);
}

Ago­ra o SceneManager pode guardar o ContentManager:

public class SceneManager
{
private IScene _currentScene;
private ContentManager _content;

public SceneManager(ContentManager content)
{
_content = content;
}

public void ChangeScene(IScene newScene)
{
_currentScene = newScene;
_currentScene.LoadContent(_content);
}

public void Update(GameTime gameTime)
{
_currentScene?.Update(gameTime);
}

public void Draw(SpriteBatch spriteBatch)
{
_currentScene?.Draw(spriteBatch);
}
}

No Game1.cs:

_sceneManager = new SceneManager(Content);

Isso deixa o car­rega­men­to mais orga­ni­za­do.


🎨 Criando uma MenuScene simples

Vamos cri­ar uma cena de menu ini­cial.

public class MenuScene : IScene
{
private SpriteFont _font;
private int _selectedIndex = 0;

private string[] _options =
{
"Jogar",
"Opções",
"Sair"
};

private Action _startGame;
private Action _exitGame;

public MenuScene(Action startGame, Action exitGame)
{
_startGame = startGame;
_exitGame = exitGame;
}

public void LoadContent(ContentManager content)
{
_font = content.Load<SpriteFont>("fonts/default");
}

public void Update(GameTime gameTime)
{
KeyboardState keyboard = Keyboard.GetState();

if (keyboard.IsKeyDown(Keys.Down))
{
_selectedIndex++;
if (_selectedIndex >= _options.Length)
_selectedIndex = 0;
}

if (keyboard.IsKeyDown(Keys.Up))
{
_selectedIndex--;
if (_selectedIndex < 0)
_selectedIndex = _options.Length - 1;
}

if (keyboard.IsKeyDown(Keys.Enter))
{
if (_selectedIndex == 0)
_startGame.Invoke();

if (_selectedIndex == 2)
_exitGame.Invoke();
}
}

public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.DrawString(_font, "MonoGame Curso Completo", new Vector2(420, 180), Color.White);

for (int i = 0; i < _options.Length; i++)
{
Color color = i == _selectedIndex ? Color.Yellow : Color.White;

spriteBatch.DrawString(
_font,
_options[i],
new Vector2(560, 280 + i * 50),
color
);
}
}
}

Esse menu já tem:

✅ títu­lo;
✅ opções;
✅ seleção;
✅ entra­da por tecla­do;
✅ ação de ini­ciar;
✅ ação de sair.

Mas existe um detal­he impor­tante: usan­do IsKeyDown, a seleção pode mudar rápi­do demais se o jogador segu­rar a tecla.

O ide­al é usar o InputManager cri­a­do no capí­tu­lo ante­ri­or.


⌨️ Menu com InputManager

A inter­face da cena pode rece­ber tam­bém um InputManager.

public interface IScene
{
void LoadContent(ContentManager content);
void Update(GameTime gameTime, InputManager input);
void Draw(SpriteBatch spriteBatch);
}

Ago­ra o MenuScene fica mais cor­re­to:

public void Update(GameTime gameTime, InputManager input)
{
if (input.IsKeyPressed(Keys.Down))
{
_selectedIndex++;
if (_selectedIndex >= _options.Length)
_selectedIndex = 0;
}

if (input.IsKeyPressed(Keys.Up))
{
_selectedIndex--;
if (_selectedIndex < 0)
_selectedIndex = _options.Length - 1;
}

if (input.IsConfirmPressed())
{
if (_selectedIndex == 0)
_startGame.Invoke();

if (_selectedIndex == 2)
_exitGame.Invoke();
}
}

Ago­ra o menu responde ape­nas a toques úni­cos.

Isso cria uma exper­iên­cia muito mais profis­sion­al.


🧱 Criando uma GameScene

A GameScene é onde fica a fase prin­ci­pal.

public class GameScene : IScene
{
private Player _player;
private List<Enemy> _enemies;
private List<Coin> _coins;

private Texture2D _playerTexture;
private Texture2D _enemyTexture;
private Texture2D _coinTexture;

private int _score;
private int _lives = 3;

private Action _goToGameOver;
private Action _goToVictory;
private Action _pauseGame;

public GameScene(
Action goToGameOver,
Action goToVictory,
Action pauseGame
)
{
_goToGameOver = goToGameOver;
_goToVictory = goToVictory;
_pauseGame = pauseGame;
}

public void LoadContent(ContentManager content)
{
_playerTexture = content.Load<Texture2D>("sprites/player");
_enemyTexture = content.Load<Texture2D>("sprites/enemy");
_coinTexture = content.Load<Texture2D>("items/coin");

_player = new Player(_playerTexture, new Vector2(100, 300));

_enemies = new List<Enemy>();
_coins = new List<Coin>();

_score = 0;
_lives = 3;
}

public void Update(GameTime gameTime, InputManager input)
{
if (input.IsPausePressed())
{
_pauseGame.Invoke();
return;
}

_player.Update(gameTime, input);

foreach (Enemy enemy in _enemies)
enemy.Update(gameTime);

foreach (Coin coin in _coins)
{
if (!coin.Collected && _player.Bounds.Intersects(coin.Bounds))
{
coin.Collected = true;
_score += 10;
}
}

if (_lives <= 0)
_goToGameOver.Invoke();

if (AllCoinsCollected())
_goToVictory.Invoke();
}

public void Draw(SpriteBatch spriteBatch)
{
_player.Draw(spriteBatch);

foreach (Enemy enemy in _enemies)
enemy.Draw(spriteBatch);

foreach (Coin coin in _coins)
coin.Draw(spriteBatch);

// desenhar HUD aqui
}

private bool AllCoinsCollected()
{
return _coins.All(c => c.Collected);
}
}

Ago­ra a lóg­i­ca do jogo está den­tro da cena de game­play, não no Game1.cs.


⏸️ Como criar pause corretamente

Exis­tem duas for­mas comuns de cri­ar pausa.

1. Pause como estado dentro da GameScene

A GameScene tem um booleano:

private bool _isPaused;

No Update:

if (input.IsPausePressed())
{
_isPaused = !_isPaused;
}

if (_isPaused)
{
return;
}

// atualizar gameplay

No Draw:

DrawGameplay(spriteBatch);

if (_isPaused)
{
DrawPauseOverlay(spriteBatch);
}

Essa abor­dagem é sim­ples.

2. Pause como cena separada

A PauseScene fica por cima ou sub­sti­tui a cena atu­al.

Exem­p­lo:

_sceneManager.ChangeScene(new PauseScene(() =>
{
_sceneManager.ChangeScene(gameScene);
}));

Essa abor­dagem é mais arquite­tur­al, mas exige cuida­do para preser­var o esta­do da fase.

Para ini­ciantes, o mais sim­ples é man­ter o pause den­tro da GameScene.

Para pro­je­tos maiores, pode faz­er sen­ti­do cri­ar uma pil­ha de cenas.


🧱 Scene Stack: pilha de cenas

Uma arquite­tu­ra mais avança­da é usar uma pil­ha de cenas.

Fun­ciona assim:

GameScene
PauseScene por cima

A GameScene con­tin­ua existin­do, mas para de atu­alizar. A PauseScene aparece sobre ela.

A estru­tu­ra seria:

public class SceneManager
{
private Stack<IScene> _scenes = new Stack<IScene>();

public void PushScene(IScene scene)
{
_scenes.Push(scene);
}

public void PopScene()
{
if (_scenes.Count > 0)
_scenes.Pop();
}

public void Update(GameTime gameTime, InputManager input)
{
if (_scenes.Count > 0)
_scenes.Peek().Update(gameTime, input);
}

public void Draw(SpriteBatch spriteBatch)
{
foreach (var scene in _scenes.Reverse())
{
scene.Draw(spriteBatch);
}
}
}

Esse mod­e­lo per­mite:

✅ pause sobre game­play;
✅ inven­tário sobre game­play;
✅ diál­o­go sobre fase;
✅ modal de con­fir­mação;
✅ menu de opções sobre o menu prin­ci­pal.

Mas ele tam­bém é mais com­plexo. Use ape­nas quan­do fiz­er sen­ti­do.


🧠 GameComponent e DrawableGameComponent: alternativa do MonoGame

O MonoGame tam­bém pos­sui class­es próprias para com­po­nentes.

A doc­u­men­tação ofi­cial descreve GameComponent como um obje­to que pode ser anex­a­do a um Game e ter seu méto­do Update(GameTime) chama­do quan­do o Update do jogo é chama­do.

DrawableGameComponent é descrito como um obje­to desen­háv­el que, quan­do adi­ciona­do à coleção Game.Components, tem seu méto­do Draw(GameTime) chama­do quan­do Game.Draw(GameTime) é chama­do.

A doc­u­men­tação sobre game loop tam­bém expli­ca que com­po­nentes podem ofer­e­cer uma for­ma mod­u­lar de adi­cionar fun­cional­i­dade ao jogo, reg­is­tran­do-os em Game.Components.Add, para que seus méto­dos de ini­cial­iza­ção, atu­al­iza­ção e desen­ho sejam chama­dos pelo ciclo prin­ci­pal do jogo.

Isso sig­nifi­ca que existe uma alter­na­ti­va ofi­cial basea­da em com­po­nentes.

Porém, para quem está con­stru­in­do um cur­so pro­gres­si­vo, é mais didáti­co começar com IScene, SceneManager, InputManager e class­es próprias. Depois, você pode estu­dar GameComponent e DrawableGameComponent como uma abor­dagem adi­cional.


📜 Menus: o que um bom menu precisa ter?

Um menu parece sim­ples, mas ele pre­cisa ser claro, respon­si­vo e fácil de nave­g­ar.

Um bom menu nor­mal­mente pos­sui:

✅ títu­lo do jogo;
✅ opção sele­ciona­da visív­el;
✅ nave­g­ação por tecla­do;
✅ nave­g­ação por con­t­role;
✅ clique por mouse, se fiz­er sen­ti­do;
✅ feed­back visu­al;
✅ som de seleção;
✅ opção de ini­ciar;
✅ opção de sair;
✅ opção de con­fig­u­rações;
✅ retorno fácil.

Um menu ruim dá sen­sação amado­ra, mes­mo que a game­play seja boa.


🎨 Criando uma classe Button

Para menus com mouse, crie um botão reuti­lizáv­el.

public class Button
{
public Rectangle Bounds { get; private set; }
public string Text { get; private set; }

private SpriteFont _font;
private bool _isHovered;

public Button(Rectangle bounds, string text, SpriteFont font)
{
Bounds = bounds;
Text = text;
_font = font;
}

public bool Update(InputManager input)
{
_isHovered = Bounds.Contains(input.MousePosition);

return _isHovered && input.IsLeftMouseClicked();
}

public void Draw(SpriteBatch spriteBatch)
{
Color color = _isHovered ? Color.Yellow : Color.White;

spriteBatch.DrawString(
_font,
Text,
new Vector2(Bounds.X, Bounds.Y),
color
);
}
}

Na MenuScene:

private Button _playButton;
private Button _exitButton;

No LoadContent:

_playButton = new Button(
new Rectangle(500, 300, 220, 50),
"Jogar",
_font
);

_exitButton = new Button(
new Rectangle(500, 370, 220, 50),
"Sair",
_font
);

No Update:

if (_playButton.Update(input))
_startGame.Invoke();

if (_exitButton.Update(input))
_exitGame.Invoke();

No Draw:

_playButton.Draw(spriteBatch);
_exitButton.Draw(spriteBatch);

Ago­ra você tem botões reuti­lizáveis.


🧩 Organização de pastas recomendada

Uma estru­tu­ra saudáv­el para o pro­je­to seria:

MeuJogo/
├── Core/
│ ├── InputManager.cs
│ ├── SceneManager.cs
│ ├── IScene.cs
│ └── GameSettings.cs

├── Scenes/
│ ├── MenuScene.cs
│ ├── GameScene.cs
│ ├── PauseScene.cs
│ ├── GameOverScene.cs
│ └── VictoryScene.cs

├── Entities/
│ ├── Player.cs
│ ├── Enemy.cs
│ ├── Coin.cs
│ └── Projectile.cs

├── UI/
│ ├── Button.cs
│ ├── Hud.cs
│ └── TextLabel.cs

├── World/
│ ├── Level.cs
│ ├── Tile.cs
│ └── CollisionMap.cs

├── Content/
│ ├── sprites/
│ ├── backgrounds/
│ ├── audio/
│ ├── fonts/
│ └── ui/

├── Game1.cs
└── Program.cs

Essa estru­tu­ra facili­ta muito a evolução.

Você sabe onde cada coisa mora.


🧱 O papel do Game1.cs em uma arquitetura melhor

Depois que você orga­ni­za cenas, o Game1.cs deve ficar mais limpo.

Ele deve cuidar prin­ci­pal­mente de:

✅ cri­ar GraphicsDeviceManager;
✅ cri­ar SpriteBatch;
✅ ini­cializar sis­temas cen­trais;
✅ atu­alizar input;
✅ atu­alizar cena atu­al;
✅ desen­har cena atu­al;
✅ tro­car cenas quan­do necessário.

Exem­p­lo:

public class Game1 : Game
{
private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;

private InputManager _input;
private SceneManager _sceneManager;

public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
}

protected override void Initialize()
{
_input = new InputManager();
_sceneManager = new SceneManager(Content);

base.Initialize();
}

protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);

_sceneManager.ChangeScene(new MenuScene(
startGame: () => _sceneManager.ChangeScene(new GameScene()),
exitGame: () => Exit()
));
}

protected override void Update(GameTime gameTime)
{
_input.Update();

_sceneManager.Update(gameTime, _input);

base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);

_spriteBatch.Begin();
_sceneManager.Draw(_spriteBatch);
_spriteBatch.End();

base.Draw(gameTime);
}
}

Perce­ba a difer­ença.

O Game1.cs não con­hece mais todos os detal­h­es da game­play. Ele ape­nas coor­de­na.


🧠 Separação de responsabilidades

Essa é uma das ideias mais impor­tantes da arquite­tu­ra.

Cada classe deve ter uma respon­s­abil­i­dade clara.

Exemplo ruim

Game1.cs
- lê input
- move player
- desenha menu
- calcula colisão
- toca música
- cria inimigos
- desenha HUD
- controla game over
- salva jogo
- carrega fase

Exemplo melhor

InputManager → lê entrada
SceneManager → troca cenas
MenuScene → controla menu
GameScene → controla gameplay
Player → controla jogador
Enemy → controla inimigo
Hud → desenha vida e score
Level → controla fase
Button → controla botão

Essa divisão tor­na o pro­je­to mais fácil de man­ter.


📊 Tabela: arquitetura simples vs arquitetura organizada

Aspec­toTudo no Game1Com cenas e arquite­tu­ra
Começo rápi­do✅ Sim⚠️ Um pouco mais lento
Orga­ni­za­ção❌ Baixa✅ Alta
Manutenção❌ Difí­cil✅ Mel­hor
Cresci­men­to❌ Bagunça rápi­do✅ Escaláv­el
Menus⚠️ Mis­tu­ra­dos✅ Sep­a­ra­dos
Game Over⚠️ Vira if/else✅ Cena própria
Testes❌ Difí­cil✅ Mais fácil
Reuso❌ Baixo✅ Alto
Clareza❌ Menor✅ Maior

💀 Criando uma GameOverScene

public class GameOverScene : IScene
{
private SpriteFont _font;
private Action _restartGame;
private Action _goToMenu;

public GameOverScene(Action restartGame, Action goToMenu)
{
_restartGame = restartGame;
_goToMenu = goToMenu;
}

public void LoadContent(ContentManager content)
{
_font = content.Load<SpriteFont>("fonts/default");
}

public void Update(GameTime gameTime, InputManager input)
{
if (input.IsConfirmPressed())
_restartGame.Invoke();

if (input.IsCancelPressed())
_goToMenu.Invoke();
}

public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.DrawString(
_font,
"Game Over",
new Vector2(520, 260),
Color.Red
);

spriteBatch.DrawString(
_font,
"Enter para tentar novamente",
new Vector2(430, 330),
Color.White
);

spriteBatch.DrawString(
_font,
"Esc para voltar ao menu",
new Vector2(450, 380),
Color.White
);
}
}

Ago­ra o game over não está mis­tu­ra­do com a game­play.

Ele tem sua própria cena.


🏆 Criando uma VictoryScene

public class VictoryScene : IScene
{
private SpriteFont _font;
private Action _goToMenu;

public VictoryScene(Action goToMenu)
{
_goToMenu = goToMenu;
}

public void LoadContent(ContentManager content)
{
_font = content.Load<SpriteFont>("fonts/default");
}

public void Update(GameTime gameTime, InputManager input)
{
if (input.IsConfirmPressed())
_goToMenu.Invoke();
}

public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.DrawString(
_font,
"Vitória!",
new Vector2(540, 260),
Color.Gold
);

spriteBatch.DrawString(
_font,
"Você completou a fase.",
new Vector2(460, 330),
Color.White
);

spriteBatch.DrawString(
_font,
"Pressione Enter para voltar ao menu.",
new Vector2(380, 390),
Color.White
);
}
}

Essa cena pode ser usa­da quan­do o jogador cole­ta todos os itens, der­ro­ta o chefe ou ter­mi­na a fase.


⚙️ OptionsScene: preparando configurações

Uma tela de opções pode con­tro­lar:

  • vol­ume da músi­ca;
  • vol­ume dos efeitos;
  • tela cheia;
  • res­olução;
  • difi­cul­dade;
  • con­troles;
  • idioma.

Exem­p­lo ini­cial:

public class OptionsScene : IScene
{
private SpriteFont _font;
private Action _goBack;

public OptionsScene(Action goBack)
{
_goBack = goBack;
}

public void LoadContent(ContentManager content)
{
_font = content.Load<SpriteFont>("fonts/default");
}

public void Update(GameTime gameTime, InputManager input)
{
if (input.IsCancelPressed())
_goBack.Invoke();
}

public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.DrawString(
_font,
"Opções",
new Vector2(540, 200),
Color.White
);

spriteBatch.DrawString(
_font,
"Volume: 100%",
new Vector2(500, 300),
Color.White
);

spriteBatch.DrawString(
_font,
"Esc para voltar",
new Vector2(500, 400),
Color.Yellow
);
}
}

Mes­mo que no iní­cio seja sim­ples, cri­ar a cena já prepara o pro­je­to para crescer.


🎼 Arquitetura também inclui áudio

Cenas difer­entes podem ter músi­cas difer­entes:

  • menu prin­ci­pal;
  • fase;
  • chefe;
  • vitória;
  • game over.

Você pode cri­ar um AudioManager:

public class AudioManager
{
public void PlayMusic(string name)
{
// tocar música
}

public void PlaySound(string name)
{
// tocar efeito
}

public void StopMusic()
{
// parar música
}
}

Assim, a MenuScene pode tocar músi­ca de menu e a GameScene músi­ca de fase.

O impor­tante é não espal­har chamadas de áudio por todo lado sem con­t­role.


🧱 HUD separado da GameScene

O HUD mostra infor­mações como:

❤️ vida;
⭐ pon­tu­ação;
⏱️ tem­po;
🪙 moedas;
🔋 ener­gia;
🎯 obje­ti­vo.

Crie uma classe:

public class Hud
{
private SpriteFont _font;

public Hud(SpriteFont font)
{
_font = font;
}

public void Draw(SpriteBatch spriteBatch, int score, int lives)
{
spriteBatch.DrawString(
_font,
$"Score: {score}",
new Vector2(20, 20),
Color.White
);

spriteBatch.DrawString(
_font,
$"Vidas: {lives}",
new Vector2(20, 55),
Color.White
);
}
}

Na GameScene:

_hud.Draw(spriteBatch, _score, _lives);

Isso evi­ta que a GameScene fique respon­sáv­el por tudo.


🌍 Level: separando a fase da lógica principal

A fase tam­bém pode virar classe.

public class Level
{
public List<Rectangle> Platforms { get; private set; }
public List<Coin> Coins { get; private set; }
public List<Enemy> Enemies { get; private set; }

public Level()
{
Platforms = new List<Rectangle>();
Coins = new List<Coin>();
Enemies = new List<Enemy>();
}

public void Load()
{
Platforms.Add(new Rectangle(0, 500, 1280, 80));
Platforms.Add(new Rectangle(300, 420, 200, 30));
Platforms.Add(new Rectangle(650, 350, 200, 30));
}

public void Update(GameTime gameTime)
{
foreach (Enemy enemy in Enemies)
enemy.Update(gameTime);
}

public void Draw(SpriteBatch spriteBatch)
{
foreach (Coin coin in Coins)
coin.Draw(spriteBatch);

foreach (Enemy enemy in Enemies)
enemy.Draw(spriteBatch);
}
}

Ago­ra a fase pode cuidar dos ele­men­tos do mun­do.

A GameScene coor­de­na, mas não pre­cisa saber cada detal­he.


🔄 Transições entre cenas

Uma tro­ca brus­ca de cena fun­ciona, mas pode pare­cer sim­ples demais.

Você pode cri­ar tran­sições:

  • fade in;
  • fade out;
  • tela pre­ta;
  • slide;
  • zoom;
  • corti­na;
  • load­ing.

Exem­p­lo con­ceitu­al:

public enum TransitionState
{
None,
FadingOut,
FadingIn
}

Cam­pos:

private float _alpha = 0f;
private TransitionState _transitionState = TransitionState.None;
private IScene _nextScene;

Durante o fade out:

_alpha += deltaTime;

if (_alpha >= 1f)
{
ChangeSceneImmediately(_nextScene);
_transitionState = TransitionState.FadingIn;
}

Durante o fade in:

_alpha -= deltaTime;

if (_alpha <= 0f)
{
_alpha = 0f;
_transitionState = TransitionState.None;
}

No Draw, você desen­ha um retân­gu­lo pre­to por cima com transparên­cia.

Esse tipo de poli­men­to deixa o jogo mais profis­sion­al.


💾 Arquitetura pensando em salvar e carregar

Mes­mo que seu jogo ain­da não ten­ha sis­tema de save, a arquite­tu­ra deve per­mi­tir isso no futuro.

Você pode cri­ar:

public class SaveData
{
public int CurrentLevel { get; set; }
public int Score { get; set; }
public int Lives { get; set; }
}

E um serviço:

public class SaveManager
{
public void Save(SaveData data)
{
// salvar em arquivo
}

public SaveData Load()
{
// carregar de arquivo
return new SaveData();
}
}

Se o jogo está bem orga­ni­za­do, adi­cionar save depois fica muito mais fácil.

Se tudo está no Game1.cs, fica muito mais difí­cil.


🧠 Arquitetura boa não nasce perfeita

Um erro comum é quer­er cri­ar uma arquite­tu­ra per­fei­ta antes de cri­ar o jogo.

Isso tam­bém pode virar prob­le­ma.

Você pas­sa sem­anas crian­do sis­temas e não cria game­play.

O cam­in­ho ide­al é equi­líbrio:

  1. crie o pro­tótipo sim­ples;
  2. perce­ba o que está fican­do repeti­do;
  3. extra­ia class­es;
  4. crie cenas;
  5. orga­nize sis­temas;
  6. mel­hore aos poucos.

Arquite­tu­ra deve servir ao jogo.

Não o con­trário.


⚠️ Erros comuns ao criar cenas e menus

1. Criar arquitetura complexa demais cedo

Se o jogo ain­da é pequeno, não pre­cisa de 50 class­es.

Comece sim­ples.

2. Deixar tudo no Game1 até tarde demais

O opos­to tam­bém é ruim. Se o jogo já tem menu, game­play, pause e game over, está na hora de sep­a­rar.

3. Misturar lógica de menu com lógica de gameplay

Menu e game­play têm respon­s­abil­i­dades difer­entes.

4. Recarregar assets toda vez sem necessidade

Tro­cas de cena pre­cisam ser pen­sadas. Assets podem ser car­rega­dos uma vez ou geren­ci­a­dos com cuida­do.

5. Não preservar estado ao pausar

Se você tro­ca para PauseScene e destrói a GameScene, perde o pro­gres­so da fase.

6. Usar IsKeyDown em menus

Isso pode faz­er o cur­sor pular várias opções rap­i­da­mente. Use tecla recém-pres­sion­a­da.

7. Criar dependências circulares

Evite que todas as class­es con­heçam todas as out­ras.

8. Não separar HUD

HUD é inter­face. Game­play é regra. São coisas difer­entes.

9. Não pensar em transições

Tro­cas brus­cas fun­cionam, mas tran­sições mel­ho­ram a exper­iên­cia.

10. Fazer o SceneManager virar “deus”

O SceneManager deve tro­car e atu­alizar cenas, não con­tro­lar toda a lóg­i­ca do jogo.


📊 Tabela prática: qual classe deve fazer o quê?

ClasseRespon­s­abil­i­dade
Game1Ini­cializar sis­temas e chamar cena atu­al
SceneManagerCon­tro­lar cena ati­va
ISceneCon­tra­to bási­co de cena
MenuSceneMenu prin­ci­pal
GameSceneGame­play
PauseScenePausa
GameOverSceneFim de jogo
VictorySceneVitória
InputManagerEntra­da do jogador
PlayerLóg­i­ca do jogador
EnemyLóg­i­ca dos inimi­gos
HudInter­face da fase
ButtonBotões clicáveis
LevelPlatafor­mas, inimi­gos e itens da fase
AudioManagerMúsi­ca e efeitos
SaveManagerSal­var e car­regar pro­gres­so

✅ Checklist do Capítulo 7

Ao final deste capí­tu­lo, você deve enten­der:

✅ por que o Game1.cs não deve con­cen­trar tudo;
✅ o que são cenas;
✅ o que é arquite­tu­ra de jogo;
✅ como usar GameState;
✅ quan­do evoluir para SceneManager;
✅ como cri­ar uma inter­face IScene;
✅ como cri­ar MenuScene;
✅ como cri­ar GameScene;
✅ como cri­ar GameOverScene;
✅ como cri­ar VictoryScene;
✅ como cri­ar menus naveg­áveis;
✅ como sep­a­rar HUD;
✅ como orga­ni­zar pas­tas;
✅ como pen­sar em pause;
✅ o que é scene stack;
✅ quan­do usar GameComponent;
✅ como evi­tar dependên­cias con­fusas;
✅ como preparar o pro­je­to para crescer.


🏁 Conclusão: arquitetura transforma protótipo em projeto

Cenas, menus e arquite­tu­ra são o pon­to em que seu jogo começa a gan­har estru­tu­ra profis­sion­al.

Antes, você tin­ha sis­temas fun­cio­nan­do sep­a­rada­mente: input, sprites, col­isão, movi­men­tação e físi­ca sim­ples. Ago­ra, ess­es sis­temas pre­cisam viv­er den­tro de uma orga­ni­za­ção maior.

O menu não deve se mis­tu­rar com a fase.
O pause não deve bagunçar a game­play.
O game over não deve estar per­di­do no meio do códi­go do jogador.
O HUD não deve ser respon­s­abil­i­dade do inimi­go.
O Game1.cs não deve car­regar o jogo inteiro nas costas.

A arquite­tu­ra resolve isso.

Ela dis­tribui respon­s­abil­i­dades, mel­ho­ra a manutenção e per­mite que o jogo cresça sem virar caos.

No MonoGame, essa orga­ni­za­ção não vem pronta como em engines visuais. Mas isso é parte do apren­diza­do. Você entende como um jogo é estru­tu­ra­do de ver­dade e cria sua própria base.

A par­tir daqui, seu pro­je­to deixa de ser ape­nas um con­jun­to de códi­gos fun­cio­nan­do e começa a se tornar um jogo com telas, fluxo, nave­g­ação, esta­dos, menus e estru­tu­ra.

Um pro­tótipo pro­va uma ideia. Uma boa arquite­tu­ra per­mite trans­for­mar essa ideia em jogo com­ple­to.


❓ FAQ — Cenas, Menus e Arquitetura no MonoGame

1. O que são cenas no MonoGame?

Cenas são telas ou esta­dos prin­ci­pais do jogo, como menu, fase, pause, game over e vitória. MonoGame não impõe uma estru­tu­ra úni­ca de cenas, então é comum cri­ar sua própria inter­face IScene e um SceneManager.

2. Preciso criar um SceneManager?

Não no iní­cio, mas ele se tor­na muito útil quan­do o jogo pas­sa a ter múlti­plas telas, como menu, game­play, pause e game over.

3. Posso usar apenas um enum GameState?

Sim. Para jogos pequenos, GameState com switch pode ser sufi­ciente. Para jogos maiores, cenas sep­a­radas são mais orga­ni­zadas.

4. O que deve ficar no Game1.cs?

O ide­al é que o Game1.cs ini­cial­ize sis­temas cen­trais, atu­al­ize input, chame a cena atu­al e desen­he a cena atu­al. Ele não deve con­ter toda a lóg­i­ca do jogo.

5. O que é GameComponent?

GameComponent é uma classe do MonoGame que pode ser anex­a­da ao jogo e ter seu Update(GameTime) chama­do pelo ciclo do jogo.

6. O que é DrawableGameComponent?

DrawableGameComponent é um com­po­nente desen­háv­el que pode ter seu Draw(GameTime) chama­do quan­do o Draw do jogo é exe­cu­ta­do.

7. Menu deve ser uma cena?

Em pro­je­tos orga­ni­za­dos, sim. Um menu tem lóg­i­ca própria, input próprio e desen­ho próprio.

8. Pause deve ser uma cena separada?

Depende. Para jogos sim­ples, pode ser um esta­do den­tro da GameScene. Para jogos maiores, pode ser uma cena sobre­pos­ta usan­do uma pil­ha de cenas.

9. Como evitar código bagunçado?

Sep­a­re respon­s­abil­i­dades: InputManager para input, SceneManager para cenas, Player para jogador, Hud para inter­face, Level para fase e assim por diante.

10. Qual frase resume este capítulo?

Arquite­tu­ra é o que per­mite seu jogo crescer sem desmoronar.

Capí­tu­lo 1 — O que é MonoGame e por que usar para cri­ar jogos

Capí­tu­lo 2 — Preparan­do o Ambi­ente de Desen­volvi­men­to

Capí­tu­lo 3 — Fun­da­men­tos do Game Loop: Update e Draw

Capí­tu­lo 4 — Sprites, Tex­turas e Con­tent Pipeline

Capí­tu­lo 5 — Entra­da do Jogador: Tecla­do, Mouse e Con­t­role

Capí­tu­lo 6 — Col­isão, Físi­ca Sim­ples e Movi­men­tação

Capí­tu­lo 7 — Cenas, Menus e Arquite­tu­ra do Jogo

Capí­tu­lo 8 — Áudio, Partícu­las, Ani­mações e Poli­men­to

Capí­tu­lo 9 — Shaders, Câmera, Mapas e Per­for­mance

Capí­tu­lo 10 — Pub­li­cação, Mon­e­ti­za­ção e Pro­je­to Final no MonoGame

Posts Similares

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *