Como física de jogos 2D funcionam? Parte 1/3

Antes de começarmos a escrever várias linhas de códigos e conversar sobre matemática, acredito que seja importante repassar duas observações sobre o assunto abordado. Em primeiro lugar, é bom estar ciente que essa é uma das várias maneiras e uma das mais simples para programar física de objetos para jogos de plataforma 2D e em segundo lugar estarei usando a engine MonoGame(C#), porém os conceitos abordados podem ser perfeitamente usados em outras engines de funcionamento semelhante.

Como posicionar uma imagem na tela?

Em jogos 2D temos dois números importantes que são representados pelas letras X e Y. Esses números são coordenadas que informam tanto a posição vertical(Y) quanto a posição horizontal(X).

Contudo, como isso funciona na prática? Vamos observar as linhas de código abaixo e o seu resultado.

// Game1.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Game1
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }
        protected override void Initialize()
        {
            base.Initialize();
        }

        public Texture2D sprite;
        public Vector2 position;

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            // carregando sprite
            this.sprite = Content.Load<Texture2D>("player_idle");
        }

        protected override void Draw(GameTime gameTime)
        {
            // Coordenadas
            float y = 0;
            float x = 0;
            this.position = new Vector2(x, y); // posição da imagem


            spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, null);
            GraphicsDevice.Clear(Color.CornflowerBlue);
            // desenhando a imagem na tela
            spriteBatch.Draw(this.sprite, this.position, null, Color.White, 0, Vector2.Zero, 5, SpriteEffects.None, 0);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

E esse é o resultado quando rodamos o game:

posicionando imagens monogame 1
imagem 1

Observe que a imagem renderizada ficou posicionada no canto superior esquerdo da tela.

Antes de começarmos a brincar e modificar o código vamos entender o que o correu e o porquê da imagem ser renderizada exatamente naquele espaço.

Quando escolhemos o valor de X e de Y estamos definindo um ponto na tela, portanto essa é posição de valor zero tanto para X quanto para Y por padrão na engine em questão é o canto superior esquerdo da tela e esse ponto não representa o foco central do sprite do personagem.

Agora vamos mudar o valor da variável x para 100. Isso fará a imagem ser renderizada mais para a direita.

// Game1.cs
//...
float x = 100f;
float y = 0f;
//...
posicionando imagens monogame 2
imagem 2

E o valor de y também para 100.

// Game1.cs
//...
float x = 100f;
float y = 100f;
//...
posicionando imagens monogame 3
imagem 3

Detectando colisões

Após entendermos como posicionar um objeto na tela agora podemos checar se ele está colidindo em outro objeto. Para isso é necessário duas informações importantes a primeira delas é a posição e a segunda é o tamanho.

Seguindo o exemplo anterior, criamos uma plataforma de 60×10 e usamos o tamanho da imagem do personagem 20×36.

// Game1.cs
//...

// player
public Texture2D sprite;
public Vector2 playerPosition;
public Point playerSize = new Point(20, 36);

// plataforma
public Texture2D spritePlatform;
public Vector2 platformPosition;
public Point platformSize = new Point(60, 10);

//...

E agora a função para checar a colisão.

// Game1.cs
//...

public bool Overlap()
{
    bool AisToTheRightOfB = this.playerPosition.X > this.platformPosition.X + this.platformSize.X ;
    bool AisToTheLeftOfB = this.playerPosition.X + this.playerSize.X < this.platformPosition.X;
    bool AisAboveB = this.playerPosition.Y + this.playerSize.Y < this.platformPosition.Y;
    bool AisBelowB = this.playerPosition.Y > this.platformPosition.Y + this.platformSize.Y;
    return !(AisToTheRightOfB || AisToTheLeftOfB || AisAboveB || AisBelowB);
}

protected override void Update(GameTime gameTime)
{
    if (this.Overlap())
        System.Console.WriteLine("Collide");
    else
        System.Console.WriteLine("no Collide");
    base.Update(gameTime);
}

//...

Para detecta a colisão usamos um algoritmo conhecido como AABB (axis-aligned bounding boxes), onde basicamente é checado o alinhamento dos eixos dos retângulos.

Com o método para checar a colisão escrito, podemos adicionar gravidade ao nosso personagem com uma adição a sua coordenada no valor de y até que ele colida com a plataforma a cada atualização de tela.

// Game1.cs
//...

protected override void Update(GameTime gameTime)
{
    if (!this.Overlap())
        this.playerPosition = new Vector2(
            this.playerPosition.X, 
            this.playerPosition.Y + 1
         );
    base.Update(gameTime);
}

//...
monogame física para jogos 2d AABB
imagem 4

Usamos o algoritmo não só para checar colisão com objetos sólidos, mas também para checar qualquer categoria de colisão como, por exemplo, em hitboxs.

Por enquanto é isso, nas próximas partes vamos deixar mais complexo e ver soluções para problemas de desempenho.

Leave a comment