Capítulo 6 — Colisão, Física Simples e Movimentação no MonoGame

Quando o jogo deixa de apenas se mover e começa a “sentir” o mundo

Nos capí­tu­los ante­ri­ores, você apren­deu a preparar o ambi­ente, enten­der o game loop, desen­har sprites, car­regar tex­turas e cap­turar entra­da do jogador com tecla­do, mouse e con­t­role.

Ago­ra cheg­amos a um pon­to deci­si­vo: faz­er o mun­do do jogo rea­gir.

Um per­son­agem andan­do na tela é inter­es­sante. Mas um per­son­agem que bate em pare­des, pisa em platafor­mas, cole­ta moedas, sofre impacto de inimi­gos, pula com gravi­dade e respei­ta os lim­ites do cenário começa a pare­cer um jogo de ver­dade.

É aqui que entram três pilares fun­da­men­tais:

🎮 movi­men­tação — como o per­son­agem anda, corre, pula ou cai;
🧱 col­isão — como obje­tos detec­tam con­ta­to entre si;
⚙️ físi­ca sim­ples — como veloci­dade, gravi­dade, acel­er­ação e forças bási­cas afe­tam o jogo.

No MonoGame, você não recebe um sis­tema com­ple­to de físi­ca pron­to como em algu­mas engines visuais. Isso pode pare­cer uma desvan­tagem no começo, mas tam­bém é uma exce­lente opor­tu­nidade para apren­der os fun­da­men­tos. Você con­tro­la a lóg­i­ca, decide como os obje­tos se com­por­tam e con­strói exata­mente o tipo de movi­men­tação que seu jogo pre­cisa.

A doc­u­men­tação ofi­cial do MonoGame descreve Vector2 como uma estru­tu­ra para rep­re­sen­tar e manip­u­lar vetores 2D, nor­mal­mente usa­dos para direção, mag­ni­tude, coor­de­nadas e out­ros dados bidi­men­sion­ais. Isso é essen­cial para movi­men­tação, veloci­dade, posição e acel­er­ação em jogos 2D.

Em ter­mos sim­ples:

Movi­men­tação dá vida ao per­son­agem. Col­isão dá regras ao mun­do. Físi­ca sim­ples dá sen­sação ao jogo.


🎮 O que é movimentação em um jogo 2D?

Movi­men­tação é a for­ma como um obje­to muda de posição ao lon­go do tem­po.

No MonoGame, geral­mente usamos:

Vector2 position;
Vector2 velocity;

A posição indi­ca onde o obje­to está.

A veloci­dade indi­ca para onde e com que inten­si­dade ele está se moven­do.

Exem­p­lo:

Vector2 playerPosition = new Vector2(100, 200);
Vector2 playerVelocity = Vector2.Zero;

Se você altera dire­ta­mente a posição:

playerPosition.X += 5;

o per­son­agem se move 5 pix­els para a dire­i­ta.

Mas em jogos mais orga­ni­za­dos, o ide­al é pen­sar assim:

playerPosition += playerVelocity * deltaTime;

Essa abor­dagem per­mite cri­ar movi­men­tos mais suaves, con­sis­tentes e fáceis de con­tro­lar.


🧠 O papel do tempo na movimentação

Movi­men­tação sem tem­po cos­tu­ma ser incon­sis­tente.

Se você faz:

playerPosition.X += 5;

o per­son­agem anda 5 pix­els por atu­al­iza­ção. Mas e se o jogo rodar mais rápi­do em uma máquina e mais lento em out­ra? A veloci­dade perce­bi­da pode mudar.

Por isso usamos o tem­po decor­ri­do entre frames, nor­mal­mente chama­do de delta time.

No MonoGame, GameTime fornece infor­mações como TotalGameTime e ElapsedGameTime. A doc­u­men­tação ofi­cial define ElapsedGameTime como o tem­po decor­ri­do des­de a últi­ma chama­da de atu­al­iza­ção.

Exem­p­lo:

float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

Depois:

playerPosition += playerVelocity * deltaTime;

Isso trans­for­ma a veloci­dade em algo mais estáv­el, nor­mal­mente medi­do em pix­els por segun­do.

A própria doc­u­men­tação intro­dutória do MonoGame recomen­da exper­i­men­tar o efeito de mul­ti­plicar a veloci­dade por gameTime.ElapsedGameTime.TotalSeconds, mostran­do como isso muda o com­por­ta­men­to do movi­men­to.


🏃 Movimento básico com teclado

Vamos começar com o movi­men­to mais sim­ples: per­son­agem andan­do para os lados.

Cam­pos:

private Vector2 _playerPosition;
private Vector2 _playerVelocity;
private float _playerSpeed = 250f;

No Initialize:

protected override void Initialize()
{
_playerPosition = new Vector2(100, 300);
_playerVelocity = Vector2.Zero;

base.Initialize();
}

No Update:

protected override void Update(GameTime gameTime)
{
float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

KeyboardState keyboard = Keyboard.GetState();

_playerVelocity = Vector2.Zero;

if (keyboard.IsKeyDown(Keys.D) || keyboard.IsKeyDown(Keys.Right))
_playerVelocity.X = _playerSpeed;

if (keyboard.IsKeyDown(Keys.A) || keyboard.IsKeyDown(Keys.Left))
_playerVelocity.X = -_playerSpeed;

_playerPosition += _playerVelocity * deltaTime;

base.Update(gameTime);
}

Esse códi­go faz o per­son­agem andar para dire­i­ta e esquer­da.

A lóg­i­ca é sim­ples:

  • se aper­tar dire­i­ta, veloci­dade pos­i­ti­va no eixo X;
  • se aper­tar esquer­da, veloci­dade neg­a­ti­va no eixo X;
  • posição recebe veloci­dade mul­ti­pli­ca­da pelo tem­po.

🧭 Movimento em quatro direções

Para jogos top-down, como RPGs, shoot­ers 2D ou jogos de explo­ração, o per­son­agem pode se mover para cima, baixo, esquer­da e dire­i­ta.

protected override void Update(GameTime gameTime)
{
float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

KeyboardState keyboard = Keyboard.GetState();

Vector2 movement = Vector2.Zero;

if (keyboard.IsKeyDown(Keys.D) || keyboard.IsKeyDown(Keys.Right))
movement.X += 1;

if (keyboard.IsKeyDown(Keys.A) || keyboard.IsKeyDown(Keys.Left))
movement.X -= 1;

if (keyboard.IsKeyDown(Keys.W) || keyboard.IsKeyDown(Keys.Up))
movement.Y -= 1;

if (keyboard.IsKeyDown(Keys.S) || keyboard.IsKeyDown(Keys.Down))
movement.Y += 1;

if (movement != Vector2.Zero)
movement.Normalize();

_playerPosition += movement * _playerSpeed * deltaTime;

base.Update(gameTime);
}

O detal­he impor­tante é:

movement.Normalize();

Sem nor­malizar, o per­son­agem pode andar mais rápi­do na diag­o­nal, porque estaria soman­do movi­men­to hor­i­zon­tal e ver­ti­cal ao mes­mo tem­po.


🧱 O que é colisão?

Col­isão é a detecção de con­ta­to entre dois obje­tos.

Exem­p­los:

  • jogador encos­ta em uma parede;
  • per­son­agem pisa em uma platafor­ma;
  • tiro atinge inimi­go;
  • jogador cole­ta moe­da;
  • inimi­go encos­ta no jogador;
  • bola bate na raque­te;
  • per­son­agem chega ao lim­ite da tela.

Sem col­isão, os obje­tos atrav­es­sam tudo.

Com col­isão, o jogo gan­ha regras.

Em MonoGame, uma das for­mas mais sim­ples de detec­tar col­isão 2D é usan­do Rectangle.

A doc­u­men­tação ofi­cial define Rectangle como uma estru­tu­ra que descreve um retân­gu­lo 2D, com posição, largu­ra e altura. Ela tam­bém ofer­ece méto­dos como Contains, Intersect e Intersects, sendo Intersects(Rectangle) usa­do para ver­i­ficar se out­ro retân­gu­lo inter­sec­ta o retân­gu­lo atu­al.


📦 Colisão AABB: a mais simples e útil para começar

A for­ma mais comum para ini­ciantes é a col­isão AABB.

AABB sig­nifi­ca:

Axis-Aligned Bounding Box

Em por­tuguês: caixa delim­i­ta­do­ra alin­ha­da aos eixos.

Na práti­ca, é um retân­gu­lo em vol­ta do obje­to.

Exem­p­lo:

Rectangle playerBounds = new Rectangle(
(int)_playerPosition.X,
(int)_playerPosition.Y,
_playerTexture.Width,
_playerTexture.Height
);

Para uma moe­da:

Rectangle coinBounds = new Rectangle(
(int)_coinPosition.X,
(int)_coinPosition.Y,
_coinTexture.Width,
_coinTexture.Height
);

Para tes­tar col­isão:

if (playerBounds.Intersects(coinBounds))
{
// O jogador encostou na moeda
}

Esse é um dos fun­da­men­tos mais impor­tantes de jogos 2D.


🎯 Exemplo: coletando uma moeda

Imag­ine que você tem:

private Vector2 _playerPosition;
private Vector2 _coinPosition;

private Texture2D _playerTexture;
private Texture2D _coinTexture;

private bool _coinCollected = false;
private int _score = 0;

No Update:

Rectangle playerBounds = new Rectangle(
(int)_playerPosition.X,
(int)_playerPosition.Y,
_playerTexture.Width,
_playerTexture.Height
);

Rectangle coinBounds = new Rectangle(
(int)_coinPosition.X,
(int)_coinPosition.Y,
_coinTexture.Width,
_coinTexture.Height
);

if (!_coinCollected && playerBounds.Intersects(coinBounds))
{
_coinCollected = true;
_score += 10;
}

No Draw:

_spriteBatch.Begin();

_spriteBatch.Draw(_playerTexture, _playerPosition, Color.White);

if (!_coinCollected)
_spriteBatch.Draw(_coinTexture, _coinPosition, Color.White);

_spriteBatch.End();

A lóg­i­ca é clara:

  • se o jogador encostar na moe­da;
  • a moe­da é mar­ca­da como cole­ta­da;
  • a pon­tu­ação aumen­ta;
  • a moe­da deixa de ser desen­ha­da.

Esse pequeno exem­p­lo já rep­re­sen­ta uma mecâni­ca real de jogo.


🧠 Colisão não é só encostar: é decidir o que acontece depois

Detec­tar col­isão é ape­nas metade do tra­bal­ho.

A per­gun­ta mais impor­tante é:

O que acon­tece quan­do a col­isão é detec­ta­da?

Exem­p­los:

Col­isãoCon­se­quên­cia
Jogador + moe­daaumen­ta pon­tu­ação
Jogador + paredeblo­queia movi­men­to
Jogador + inimi­goperde vida
Tiro + inimi­goinimi­go sofre dano
Jogador + platafor­mapode pis­ar
Jogador + por­tamuda de fase
Bola + parederebate

Col­isão sem respos­ta não cria mecâni­ca.

A respos­ta da col­isão é o que trans­for­ma con­ta­to em regra de jogo.


🧱 Limitando o personagem à tela

Antes de cri­ar pare­des e platafor­mas, você pode começar impedin­do o jogador de sair da tela.

Exem­p­lo:

_playerPosition.X = MathHelper.Clamp(
_playerPosition.X,
0,
_graphics.PreferredBackBufferWidth - _playerTexture.Width
);

_playerPosition.Y = MathHelper.Clamp(
_playerPosition.Y,
0,
_graphics.PreferredBackBufferHeight - _playerTexture.Height
);

Isso limi­ta o per­son­agem den­tro da janela.

Se ele ten­tar pas­sar da esquer­da, fica em 0.

Se ten­tar pas­sar da dire­i­ta, fica no máx­i­mo per­mi­ti­do.

Esse tipo de lim­ite é sim­ples, mas extrema­mente útil.


⚙️ O que é física simples?

Físi­ca sim­ples em jogos não sig­nifi­ca sim­u­lar o mun­do real com pre­cisão cien­tí­fi­ca.

Sig­nifi­ca cri­ar regras bási­cas para dar sen­sação de movi­men­to.

Exem­p­los:

  • gravi­dade;
  • pulo;
  • que­da;
  • acel­er­ação;
  • desacel­er­ação;
  • atri­to;
  • impul­so;
  • col­isão;
  • rebote;
  • lim­ite de veloci­dade.

Em jogos 2D, espe­cial­mente platafor­mas, a físi­ca pre­cisa ser mais diver­ti­da do que real­ista.

Um pulo “per­feito” para game­play pode não ser fisi­ca­mente real. O obje­ti­vo é sen­sação, con­t­role e clareza.


🧲 Gravidade simples

Gravi­dade é uma acel­er­ação con­stante para baixo.

Cam­pos:

private Vector2 _playerPosition;
private Vector2 _playerVelocity;

private float _gravity = 900f;

No Update:

float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

_playerVelocity.Y += _gravity * deltaTime;
_playerPosition += _playerVelocity * deltaTime;

Isso faz o per­son­agem cair.

A veloci­dade ver­ti­cal aumen­ta com o tem­po, crian­do sen­sação de acel­er­ação.


🦘 Pulo simples

Para pular, você apli­ca uma veloci­dade para cima.

Como no eixo Y da tela val­ores maiores ficam mais para baixo, subir sig­nifi­ca veloci­dade neg­a­ti­va.

Cam­pos:

private bool _isOnGround = false;
private float _jumpForce = -450f;
private float _gravity = 900f;

No Update:

KeyboardState keyboard = Keyboard.GetState();

if (_isOnGround && keyboard.IsKeyDown(Keys.Space))
{
_playerVelocity.Y = _jumpForce;
_isOnGround = false;
}

Depois apli­ca gravi­dade:

_playerVelocity.Y += _gravity * deltaTime;
_playerPosition += _playerVelocity * deltaTime;

Mas fal­ta algo: detec­tar o chão.


🧱 Criando um chão simples

Vamos cri­ar um retân­gu­lo rep­re­sen­tan­do o chão:

private Rectangle _ground;

No Initialize:

_ground = new Rectangle(0, 500, 1280, 80);

Ago­ra cri­amos o retân­gu­lo do jogador:

Rectangle playerBounds = new Rectangle(
(int)_playerPosition.X,
(int)_playerPosition.Y,
_playerTexture.Width,
_playerTexture.Height
);

Depois da movi­men­tação:

if (playerBounds.Intersects(_ground))
{
_playerPosition.Y = _ground.Top - _playerTexture.Height;
_playerVelocity.Y = 0;
_isOnGround = true;
}

Isso faz o jogador parar sobre o chão.

A lóg­i­ca é:

  • jogador caiu;
  • retân­gu­lo dele encos­tou no chão;
  • reposi­cionamos o jogador em cima do chão;
  • zer­amos a veloci­dade ver­ti­cal;
  • mar­camos que ele está no chão.

🧩 Exemplo completo: gravidade + pulo + chão

private Vector2 _playerPosition;
private Vector2 _playerVelocity;

private float _moveSpeed = 250f;
private float _gravity = 900f;
private float _jumpForce = -450f;

private bool _isOnGround = false;

private Rectangle _ground;
private Texture2D _playerTexture;

No Initialize:

protected override void Initialize()
{
_playerPosition = new Vector2(100, 100);
_playerVelocity = Vector2.Zero;

_ground = new Rectangle(0, 500, 1280, 80);

base.Initialize();
}

No Update:

protected override void Update(GameTime gameTime)
{
float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
KeyboardState keyboard = Keyboard.GetState();

_playerVelocity.X = 0;

if (keyboard.IsKeyDown(Keys.D) || keyboard.IsKeyDown(Keys.Right))
_playerVelocity.X = _moveSpeed;

if (keyboard.IsKeyDown(Keys.A) || keyboard.IsKeyDown(Keys.Left))
_playerVelocity.X = -_moveSpeed;

if (_isOnGround && keyboard.IsKeyDown(Keys.Space))
{
_playerVelocity.Y = _jumpForce;
_isOnGround = false;
}

_playerVelocity.Y += _gravity * deltaTime;

_playerPosition += _playerVelocity * deltaTime;

Rectangle playerBounds = new Rectangle(
(int)_playerPosition.X,
(int)_playerPosition.Y,
_playerTexture.Width,
_playerTexture.Height
);

if (playerBounds.Intersects(_ground))
{
_playerPosition.Y = _ground.Top - _playerTexture.Height;
_playerVelocity.Y = 0;
_isOnGround = true;
}

base.Update(gameTime);
}

Esse é um dos primeiros grandes mar­cos no desen­volvi­men­to de um jogo de platafor­ma.

Ago­ra o per­son­agem anda, cai, pula e para no chão.


⚠️ Problema comum: a tecla espaço segurada gera pulos estranhos

No exem­p­lo aci­ma, usamos:

keyboard.IsKeyDown(Keys.Space)

Isso fun­ciona, mas pode causar com­por­ta­men­to estran­ho se o jogador segu­rar a tecla.

O ide­al é usar detecção de tecla recém-pres­sion­a­da, como vimos no capí­tu­lo ante­ri­or.

Cam­pos:

private KeyboardState _currentKeyboard;
private KeyboardState _previousKeyboard;

No Update:

_previousKeyboard = _currentKeyboard;
_currentKeyboard = Keyboard.GetState();

Méto­do:

private bool IsKeyPressed(Keys key)
{
return _currentKeyboard.IsKeyDown(key) &&
_previousKeyboard.IsKeyUp(key);
}

Uso:

if (_isOnGround && IsKeyPressed(Keys.Space))
{
_playerVelocity.Y = _jumpForce;
_isOnGround = false;
}

Isso deixa o pulo mais con­tro­la­do.


🧱 Colisão com paredes

Ago­ra imag­ine uma parede:

private Rectangle _wall = new Rectangle(600, 350, 60, 150);

Se o jogador col­idir com ela, pre­cisamos impedir que atrav­es­se.

Uma abor­dagem sim­ples é sep­a­rar movi­men­to hor­i­zon­tal e ver­ti­cal.

Primeiro apli­ca movi­men­to hor­i­zon­tal:

_playerPosition.X += _playerVelocity.X * deltaTime;

Depois tes­ta col­isão hor­i­zon­tal:

Rectangle playerBounds = GetPlayerBounds();

if (playerBounds.Intersects(_wall))
{
if (_playerVelocity.X > 0)
_playerPosition.X = _wall.Left - _playerTexture.Width;
else if (_playerVelocity.X < 0)
_playerPosition.X = _wall.Right;
}

Depois apli­ca movi­men­to ver­ti­cal:

_playerPosition.Y += _playerVelocity.Y * deltaTime;

Tes­ta col­isão ver­ti­cal:

playerBounds = GetPlayerBounds();

if (playerBounds.Intersects(_wall))
{
if (_playerVelocity.Y > 0)
{
_playerPosition.Y = _wall.Top - _playerTexture.Height;
_playerVelocity.Y = 0;
_isOnGround = true;
}
else if (_playerVelocity.Y < 0)
{
_playerPosition.Y = _wall.Bottom;
_playerVelocity.Y = 0;
}
}

Essa sep­a­ração por eixo evi­ta muitos bugs.


🧠 Por que separar colisão horizontal e vertical?

Se você move o per­son­agem em X e Y ao mes­mo tem­po, fica mais difí­cil saber de que lado ele bateu.

Ele bateu na parede lat­er­al?

Caiu em cima da platafor­ma?

Bateu a cabeça no teto?

Entrou diag­o­nal­mente no blo­co?

Ao sep­a­rar:

  1. move X;
  2. resolve col­isão em X;
  3. move Y;
  4. resolve col­isão em Y;

você sim­pli­fi­ca muito a lóg­i­ca.

Esse padrão é extrema­mente comum em jogos 2D.


🧰 Criando um método GetBounds

Para não repe­tir códi­go, crie:

private Rectangle GetPlayerBounds()
{
return new Rectangle(
(int)_playerPosition.X,
(int)_playerPosition.Y,
_playerTexture.Width,
_playerTexture.Height
);
}

Ago­ra você pode usar:

Rectangle playerBounds = GetPlayerBounds();

Isso deixa o códi­go mais limpo.

Em uma classe Player, o ide­al seria ter uma pro­priedade:

public Rectangle Bounds
{
get
{
return new Rectangle(
(int)Position.X,
(int)Position.Y,
Texture.Width,
Texture.Height
);
}
}

🎮 Criando uma classe Player com física simples

public class Player
{
public Vector2 Position;
public Vector2 Velocity;

private Texture2D _texture;

private float _moveSpeed = 250f;
private float _gravity = 900f;
private float _jumpForce = -450f;

public bool IsOnGround { get; private set; }

public Rectangle Bounds
{
get
{
return new Rectangle(
(int)Position.X,
(int)Position.Y,
_texture.Width,
_texture.Height
);
}
}

public Player(Texture2D texture, Vector2 startPosition)
{
_texture = texture;
Position = startPosition;
Velocity = Vector2.Zero;
}

public void Update(GameTime gameTime, KeyboardState keyboard)
{
float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

Velocity.X = 0;

if (keyboard.IsKeyDown(Keys.D) || keyboard.IsKeyDown(Keys.Right))
Velocity.X = _moveSpeed;

if (keyboard.IsKeyDown(Keys.A) || keyboard.IsKeyDown(Keys.Left))
Velocity.X = -_moveSpeed;

if (IsOnGround && keyboard.IsKeyDown(Keys.Space))
{
Velocity.Y = _jumpForce;
IsOnGround = false;
}

Velocity.Y += _gravity * deltaTime;
}

public void MoveX(float deltaTime)
{
Position.X += Velocity.X * deltaTime;
}

public void MoveY(float deltaTime)
{
Position.Y += Velocity.Y * deltaTime;
}

public void LandOn(Rectangle platform)
{
Position.Y = platform.Top - _texture.Height;
Velocity.Y = 0;
IsOnGround = true;
}

public void HitCeiling(Rectangle block)
{
Position.Y = block.Bottom;
Velocity.Y = 0;
}

public void HitWallLeft(Rectangle wall)
{
Position.X = wall.Left - _texture.Width;
}

public void HitWallRight(Rectangle wall)
{
Position.X = wall.Right;
}

public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(_texture, Position, Color.White);
}
}

Essa classe ain­da é sim­ples, mas já orga­ni­za bem:

  • posição;
  • veloci­dade;
  • gravi­dade;
  • pulo;
  • col­isão;
  • desen­ho.

🧱 Plataformas e blocos sólidos

Você pode cri­ar uma lista de blo­cos:

private List<Rectangle> _solidBlocks;

No Initialize:

_solidBlocks = new List<Rectangle>
{
new Rectangle(0, 500, 1280, 80),
new Rectangle(300, 420, 200, 30),
new Rectangle(650, 350, 200, 30),
new Rectangle(950, 280, 180, 30)
};

Ago­ra o jogador pode col­idir com várias platafor­mas.

No Update, depois de mover em X:

_player.MoveX(deltaTime);

foreach (Rectangle block in _solidBlocks)
{
if (_player.Bounds.Intersects(block))
{
if (_player.Velocity.X > 0)
_player.HitWallLeft(block);
else if (_player.Velocity.X < 0)
_player.HitWallRight(block);
}
}

Depois de mover em Y:

_player.MoveY(deltaTime);

foreach (Rectangle block in _solidBlocks)
{
if (_player.Bounds.Intersects(block))
{
if (_player.Velocity.Y > 0)
_player.LandOn(block);
else if (_player.Velocity.Y < 0)
_player.HitCeiling(block);
}
}

Esse mod­e­lo já per­mite cri­ar fas­es sim­ples de platafor­ma.


🧲 Aceleração e desaceleração

Até ago­ra, o per­son­agem começa e para instan­ta­nea­mente.

Para alguns jogos, isso é óti­mo. Para out­ros, pode pare­cer duro.

Você pode adi­cionar acel­er­ação.

Cam­pos:

private float _acceleration = 1200f;
private float _deceleration = 1600f;
private float _maxSpeed = 280f;

No movi­men­to hor­i­zon­tal:

float input = 0f;

if (keyboard.IsKeyDown(Keys.D) || keyboard.IsKeyDown(Keys.Right))
input = 1f;

if (keyboard.IsKeyDown(Keys.A) || keyboard.IsKeyDown(Keys.Left))
input = -1f;

if (input != 0)
{
Velocity.X += input * _acceleration * deltaTime;
Velocity.X = MathHelper.Clamp(Velocity.X, -_maxSpeed, _maxSpeed);
}
else
{
if (Velocity.X > 0)
{
Velocity.X -= _deceleration * deltaTime;

if (Velocity.X < 0)
Velocity.X = 0;
}
else if (Velocity.X < 0)
{
Velocity.X += _deceleration * deltaTime;

if (Velocity.X > 0)
Velocity.X = 0;
}
}

Esse tipo de lóg­i­ca cria movi­men­to mais suave.


🎯 Movimento arcade vs movimento físico

Nem todo jogo pre­cisa de acel­er­ação.

Movimento arcade direto

Bom para:

  • jogos de ação ráp­i­da;
  • shoot­ers;
  • puz­zles;
  • jogos casuais;
  • con­troles pre­cisos.

Exem­p­lo:

Velocity.X = input * speed;

Movimento com aceleração

Bom para:

  • platafor­ma com sen­sação de peso;
  • jogos com gelo;
  • cor­ri­da;
  • naves;
  • per­son­agens mais “físi­cos”.

Exem­p­lo:

Velocity.X += input * acceleration * deltaTime;

A escol­ha depende do tipo de jogo.

O obje­ti­vo não é ser real­ista. É ser agradáv­el de jog­ar.


🧪 Colisão com inimigos

Ago­ra vamos cri­ar um inimi­go sim­ples:

private Rectangle _enemyBounds = new Rectangle(700, 460, 48, 40);
private int _playerHealth = 3;

No Update:

if (_player.Bounds.Intersects(_enemyBounds))
{
_playerHealth--;
_player.Position = new Vector2(100, 100);
}

Esse exem­p­lo é sim­ples: se encostar no inimi­go, perde vida e vol­ta ao iní­cio.

Mas em um jogo real, você deve evi­tar que a vida dimin­ua dezenas de vezes por segun­do enquan­to o jogador está encosta­do no inimi­go.

Para isso, use inven­ci­bil­i­dade tem­porária.


🛡️ Invencibilidade temporária após dano

Cam­pos:

private float _invincibilityTimer = 0f;
private float _invincibilityDuration = 1.5f;

No Update:

float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

if (_invincibilityTimer > 0)
_invincibilityTimer -= deltaTime;

if (_player.Bounds.Intersects(_enemyBounds) && _invincibilityTimer <= 0)
{
_playerHealth--;
_invincibilityTimer = _invincibilityDuration;
}

Ago­ra o jogador não perde vida a cada frame.

Esse tipo de detal­he mel­ho­ra muito a exper­iên­cia.


💥 Knockback simples

Quan­do o jogador toma dano, você pode empurrá-lo.

if (_player.Bounds.Intersects(_enemyBounds) && _invincibilityTimer <= 0)
{
_playerHealth--;
_invincibilityTimer = _invincibilityDuration;

if (_player.Position.X < _enemyBounds.X)
_player.Velocity.X = -300f;
else
_player.Velocity.X = 300f;

_player.Velocity.Y = -250f;
}

Isso cria um impacto visu­al e mecâni­co.

O jogador sente que foi atingi­do.


🧲 Física simples de projéteis

Pro­jéteis são óti­mos para praticar movi­men­tação e col­isão.

Classe sim­ples:

public class Projectile
{
public Vector2 Position;
public Vector2 Velocity;
public bool IsActive = true;

private Texture2D _texture;

public Rectangle Bounds
{
get
{
return new Rectangle(
(int)Position.X,
(int)Position.Y,
_texture.Width,
_texture.Height
);
}
}

public Projectile(Texture2D texture, Vector2 position, Vector2 velocity)
{
_texture = texture;
Position = position;
Velocity = velocity;
}

public void Update(GameTime gameTime)
{
float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
Position += Velocity * deltaTime;
}

public void Draw(SpriteBatch spriteBatch)
{
if (IsActive)
spriteBatch.Draw(_texture, Position, Color.White);
}
}

Crian­do um tiro:

Projectile bullet = new Projectile(
bulletTexture,
_player.Position,
new Vector2(500f, 0f)
);

Col­isão com inimi­go:

if (bullet.Bounds.Intersects(enemy.Bounds))
{
bullet.IsActive = false;
enemy.TakeDamage(1);
}

🧹 Removendo objetos inativos

Se você tem uma lista de pro­jéteis:

private List<Projectile> _projectiles;

Atu­al­iza:

foreach (Projectile projectile in _projectiles)
{
projectile.Update(gameTime);
}

Remove ina­tivos:

_projectiles.RemoveAll(p => !p.IsActive);

Isso evi­ta que a lista cresça indefinida­mente.


🧠 Collision Layers: nem tudo precisa colidir com tudo

Em jogos maiores, você não quer tes­tar todos os obje­tos con­tra todos.

Exem­p­lo:

  • moe­da não pre­cisa col­idir com parede;
  • fun­do não pre­cisa col­idir com jogador;
  • partícu­las não pre­cisam col­idir com platafor­mas;
  • pro­jéteis do jogador não pre­cisam atin­gir o próprio jogador.

Uma orga­ni­za­ção sim­ples:

Player colide com: plataformas, inimigos, itens
Enemy colide com: plataformas, jogador
Projectile colide com: inimigos, paredes
Coin colide com: jogador

Essa men­tal­i­dade aju­da a man­ter o jogo efi­ciente.


📈 Performance em colisões

Para jogos pequenos, tes­tar retân­gu­los é bara­to.

Mas se você tiv­er mil­hares de obje­tos, pre­cisa otimizar.

Téc­ni­cas futuras:

  • dividir o mapa em célu­las;
  • tes­tar ape­nas obje­tos próx­i­mos;
  • usar grid espa­cial;
  • usar quadtrees;
  • sep­a­rar obje­tos por tipo;
  • evi­tar col­isões desnecessárias.

No começo, não com­plique.

Comece com Rectangle.Intersects.

Depois otimize ape­nas se pre­cis­ar.


⚠️ Erros comuns com colisão e física simples

1. Mover X e Y juntos sem resolver por eixo

Isso difi­cul­ta saber de que lado a col­isão acon­te­ceu.

2. Esquecer de zerar velocidade vertical ao cair no chão

Se não zer­ar, o per­son­agem pode con­tin­uar acu­mu­lan­do veloci­dade para baixo.

3. Usar IsKeyDown para pulo sem controle

Pode causar pulos repeti­dos ou com­por­ta­men­to estran­ho.

4. Não reposicionar o jogador após colisão

Detec­tar col­isão não bas­ta. É pre­ciso cor­ri­gir a posição.

5. Usar hitbox exatamente igual ao sprite sempre

Às vezes o sprite tem espaços vazios. A hit­box pode pre­cis­ar ser menor.

6. Não separar física de desenho

A físi­ca deve ficar no Update. O desen­ho no Draw.

7. Criar física realista demais

Jogo pre­cisa ser diver­tido, não uma sim­u­lação per­fei­ta.

8. Não limitar velocidade de queda

Gravi­dade acu­mu­la­da pode faz­er o per­son­agem cair rápi­do demais.

9. Permitir perda de vida a cada frame

Use inven­ci­bil­i­dade tem­porária após dano.

10. Não testar em velocidades diferentes

Movi­men­tos muito rápi­dos podem atrav­es­sar col­isores finos se o sis­tema for sim­ples.


🧱 Hitbox menor que o sprite

Muitas vezes, a imagem do per­son­agem é maior que a área real de col­isão.

Você pode ajus­tar:

public Rectangle Bounds
{
get
{
return new Rectangle(
(int)Position.X + 8,
(int)Position.Y + 4,
_texture.Width - 16,
_texture.Height - 4
);
}
}

Isso cria uma col­isão mais jus­ta.

Exem­p­lo:

  • sprite tem cabe­lo, capa ou som­bra;
  • hit­box con­sid­era ape­nas o cor­po;
  • o jogador não sente col­isões injus­tas.

Esse detal­he é muito impor­tante em jogos de platafor­ma.


🧪 Exercício prático do capítulo

Crie uma cena com:

✅ jogador;
✅ chão;
✅ três platafor­mas;
✅ uma moe­da;
✅ um inimi­go;
✅ pon­tu­ação;
✅ vida;
✅ gravi­dade;
✅ pulo;
✅ col­isão AABB.

Regras

  1. O jogador anda para esquer­da e dire­i­ta.
  2. O jogador pula com espaço.
  3. O jogador cai com gravi­dade.
  4. O jogador para sobre platafor­mas.
  5. O jogador cole­ta moe­da ao encostar.
  6. O jogador perde vida ao encostar no inimi­go.
  7. O jogador fica invencív­el por 1,5 segun­do após sofr­er dano.

Esse exer­cí­cio con­sol­i­da tudo que impor­ta neste capí­tu­lo.


📊 Tabela prática: conceitos do capítulo

Con­ceitoPara que serve
Vector2Rep­re­sen­tar posição, veloci­dade e direção
GameTimeCon­tro­lar movi­men­to basea­do no tem­po
RectangleCri­ar caixas de col­isão
IntersectsDetec­tar col­isão entre retân­gu­los
Veloci­dadeDefinir movi­men­to por segun­do
Gravi­dadeAcel­er­ar obje­to para baixo
PuloAplicar veloci­dade ver­ti­cal neg­a­ti­va
Platafor­maBlo­quear que­da do jogador
Hit­boxÁrea real de col­isão
Inven­ci­bil­i­dadeEvi­tar dano repeti­do por frame
Knock­backCri­ar impacto após col­isão
Nor­mal­iza­çãoEvi­tar movi­men­to diag­o­nal mais rápi­do

✅ Checklist do Capítulo 6

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

✅ como mover per­son­agem com Vector2;
✅ por que usar deltaTime;
✅ como aplicar veloci­dade;
✅ como cri­ar col­isão AABB;
✅ como usar Rectangle;
✅ como usar Intersects;
✅ como cole­tar itens;
✅ como lim­i­tar per­son­agem à tela;
✅ como aplicar gravi­dade sim­ples;
✅ como cri­ar pulo;
✅ como detec­tar chão;
✅ como resolver col­isões com platafor­mas;
✅ como sep­a­rar col­isão hor­i­zon­tal e ver­ti­cal;
✅ como cri­ar hit­box­es mel­hores;
✅ como aplicar dano;
✅ como cri­ar inven­ci­bil­i­dade tem­porária;
✅ como cri­ar knock­back;
✅ quais erros evi­tar.


🏁 Conclusão: colisão e física transformam movimento em gameplay

Movi­men­tar um per­son­agem é ape­nas o começo.

O jogo começa a ficar inter­es­sante quan­do esse movi­men­to encon­tra regras: pare­des blo­queiam, platafor­mas sus­ten­tam, moedas são cole­tadas, inimi­gos causam dano, pulos obe­de­cem gravi­dade e col­isões ger­am con­se­quên­cias.

No MonoGame, você con­strói ess­es sis­temas com códi­go. Isso exige mais tra­bal­ho, mas ensi­na pro­fun­da­mente como jogos 2D fun­cionam.

A col­isão AABB com Rectangle.Intersects é uma das for­mas mais sim­ples e efi­cientes para começar. A físi­ca sim­ples com Vector2, veloci­dade, gravi­dade e pulo é sufi­ciente para cri­ar pro­tóti­pos jogáveis, platafor­mas bási­cas, jogos top-down, shoot­ers, arcades e muitas out­ras exper­iên­cias.

O mais impor­tante é enten­der a lóg­i­ca:

posição define onde o obje­to está; veloci­dade define para onde ele vai; col­isão define o que acon­tece quan­do ele encon­tra o mun­do.

A par­tir daqui, seu jogo deixa de ser ape­nas visu­al e pas­sa a ter com­por­ta­men­to.

No próx­i­mo capí­tu­lo, essa estru­tu­ra começará a crescer de for­ma mais orga­ni­za­da com cenas, menus e arquite­tu­ra do jogo.


❓ FAQ — Colisão, física simples e movimentação no MonoGame

1. O que é colisão no MonoGame?

Col­isão é a detecção de con­ta­to entre obje­tos do jogo. Uma for­ma comum de começar é usan­do Rectangle e o méto­do Intersects, que ver­i­fi­ca se um retân­gu­lo inter­sec­ta out­ro.

2. O que é AABB?

AABB é uma col­isão basea­da em retân­gu­los alin­hados aos eixos. É sim­ples, ráp­i­da e muito usa­da em jogos 2D.

3. O que é Vector2?

Vector2 rep­re­sen­ta dados bidi­men­sion­ais, como posição, direção e veloci­dade. A doc­u­men­tação do MonoGame descreve vetores como úteis para direção, mag­ni­tude e coor­de­nadas.

4. Por que usar deltaTime?

Porque ele ajus­ta o movi­men­to ao tem­po decor­ri­do entre frames, tor­nan­do a movi­men­tação mais estáv­el.

5. Como faço o personagem pular?

Apli­can­do uma veloci­dade ver­ti­cal neg­a­ti­va quan­do ele está no chão:

_playerVelocity.Y = -450f;

6. Como aplico gravidade?

Soman­do uma acel­er­ação ver­ti­cal ao lon­go do tem­po:

_playerVelocity.Y += gravity * deltaTime;

7. Por que separar colisão horizontal e vertical?

Porque isso facili­ta desco­brir se o jogador bateu em uma parede, caiu em uma platafor­ma ou bateu a cabeça no teto.

8. Preciso de uma engine de física?

Não para começar. Muitos jogos 2D sim­ples podem usar físi­ca própria basea­da em veloci­dade, gravi­dade e retân­gu­los.

9. O que é hitbox?

É a área real usa­da para col­isão. Ela pode ser igual ou menor que o sprite.

10. Qual frase resume este capítulo?

Movi­men­to dá vida. Col­isão dá regra. Físi­ca dá sen­sação.

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 *