27 Eylül 2026 Pazar

Detecção de colisão 2D em MonoGame: de retângulos básicos a sistemas Pixel-Perfect com alocação zero




Detecção de colisão 2D em MonoGame: de retângulos básicos a sistemas Pixel-Perfect com alocação zero

Ao construir jogos 2D em motores visuais como Unity ou Godot, a detecção de colisão muitas vezes parece uma série de caixas de seleção de inspetores: você coloca um BoxCollider2D ou CircleCollider2D, anexa um Rigidbody e espera que a etapa interna da física não gagueje em dispositivos móveis.

No entanto, em MonoGame e C#, você tem controle total. Não há sobrecarga física oculta, nenhuma inércia rotacional indesejada e nenhum pico misterioso de coleta de lixo (GC) roubando seu orçamento de quadros.

Na Arar Games, quando construímos Blocked: Pixel Panzer e Paint Trek, nossos loops de jogos de arcade precisavam processar centenas de balas inimigas em alta velocidade, grades de tijolos explodindo, torres de tanques giratórias, sobrevoos de caças e escudos de partículas de 60 a 120 FPS em PCs com Windows e dispositivos Android. Um mecanismo de física de uso geral estava fora de questão – precisávamos de uma arquitetura de colisão em camadas especialmente desenvolvida.

Neste guia abrangente e baseado em código, começaremos com os fundamentos absolutos da colisão MonoGame (Rectangle.Intersects com sprites Bullet e Enemy reais) e desenvolveremos verificações de círculo avançadas, fixação mista, raycasts anti-tunneling, Colisão Pixel-Perfect de nível de produção e Grades espaciais de alocação zero otimizadas para sobrevivência de GC móvel.


1. A Fundação: Uma Hierarquia Simples de MonoGame Sprite

Antes de detectar colisões, precisamos de entidades de jogo limpas. No MonoGame, uma entidade possui fundamentalmente uma posição, uma textura e um retângulo delimitador.

Aqui está a arquitetura de entidade básica usada em nossos jogos:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class Sprite
{
    public Vector2 Position;
    public Texture2D Texture;
    public Color Tint = Color.White;
    public bool IsActive = true;

    // The raw Axis-Aligned Bounding Box (AABB)
    public virtual Rectangle Bounds => new Rectangle(
        (int)Position.X,
        (int)Position.Y,
        Texture != null ? Texture.Width : 0,
        Texture != null ? Texture.Height : 0
    );

    public virtual void Draw(SpriteBatch spriteBatch)
    {
        if (!IsActive || Texture == null) return;
        spriteBatch.Draw(Texture, Position, Tint);
    }
}

Agora vamos criar classes Player, Enemy e Bullet concretas:

public class Bullet : Sprite
{
    public Vector2 Velocity;
    public int Damage = 25;

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

public class Enemy : Sprite
{
    public int Health = 100;

    public void TakeDamage(int damage)
    {
        Health -= damage;
        if (Health <= 0)
        {
            IsActive = false;
        }
    }
}

public class Player : Sprite
{
    public int Health = 100;
    public bool IsInvulnerable = false;

    public void TakeDamage(int damage)
    {
        if (IsInvulnerable) return;
        Health -= damage;
    }
}

Com essas entidades instaladas, vamos explorar como detectar colisões entre elas, começando pelo método mais simples.


2. Nível 1: A colisão mais simples – Rectangle.Intersects (AABB)

A verificação de colisão 2D mais fundamental no MonoGame é o teste Axis-Aligned Bounding Box (AABB). O termo "alinhado ao eixo" significa simplesmente que as bordas do retângulo são completamente paralelas aos eixos \(X\) e \(Y\) da tela (sem rotação).

MonoGame fornece um método rápido e integrado: Rectangle.Intersects(Rectangle value).

Como Rectangle.Intersects funciona nos bastidores

Abaixo da superfície, o MonoGame executa quatro comparações de inteiros:

public bool Intersects(Rectangle value)
{
    return value.Left < this.Right &&
           this.Left < value.Right &&
           value.Top < this.Bottom &&
           this.Top < value.Bottom;
}

Se todas as quatro condições forem atendidas, os retângulos se sobrepõem. Se pelo menos uma condição falhar, um eixo vazio as separará e nenhuma colisão será possível.

Código de jogo real: Bullet vs. Enemy em Game1.Update

Aqui está como você verifica as colisões entre uma lista de marcadores ativos e uma lista de inimigos ativos dentro do loop principal Update do MonoGame:

public class Game1 : Game
{
    private List<Bullet> _bullets = new List<Bullet>();
    private List<Enemy> _enemies = new List<Enemy>();

    protected override void Update(GameTime gameTime)
    {
        // 1. Update bullet and enemy positions
        foreach (var bullet in _bullets) bullet.Update(gameTime);

        // 2. Collision Check: Bullets vs Enemies
        for (int b = _bullets.Count - 1; b >= 0; b--)
        {
            var bullet = _bullets[b];
            if (!bullet.IsActive) continue;

            for (int e = _enemies.Count - 1; e >= 0; e--)
            {
                var enemy = _enemies[e];
                if (!enemy.IsActive) continue;

                // The AABB check!
                if (bullet.Bounds.Intersects(enemy.Bounds))
                {
                    // Collision occurred!
                    enemy.TakeDamage(bullet.Damage);
                    bullet.IsActive = false;

                    // Remove inactive bullet immediately
                    _bullets.RemoveAt(b);
                    
                    if (!enemy.IsActive)
                    {
                        _enemies.RemoveAt(e);
                    }

                    // A bullet can only hit one enemy; break the inner loop
                    break;
                }
            }
        }

        base.Update(gameTime);
    }
}

Dica de desempenho: Observe que iteramos de trás para frente (for (int i = list.Count - 1; i >= 0; i--))! Se você usar foreach e tentar chamar _bullets.Remove(bullet), C# lançará uma InvalidOperationException: Collection foi modificada. A iteração reversa permite a remoção segura de elementos sem problemas de reindexação de memória.

O segredo do arcade: "Hitboxes justos" via Inflate

Em jogos retrô como Blocked: Pixel Panzer, as texturas de sprites geralmente incluem margens transparentes ou pontas de antena. Se o tanque do jogador explodir porque uma bala tocou um canto transparente vazio de sua textura, o jogador se sentirá enganado.

Para fazer com que a colisão pareça responsiva e justa, os jogos usam um Hitbox menor dentro do sprite usando Rectangle.Inflate:

public class EnemyTank : Enemy
{
    // Shrink the bounding box by 6 pixels on all sides for fair collision
    public override Rectangle Bounds
    {
        get
        {
            Rectangle raw = base.Bounds;
            raw.Inflate(-6, -6); // Reduces width and height by 12px
            return raw;
        }
    }
}

3. Nível 2: Colisão círculo a círculo (imunidade rotacional)

Retângulos funcionam muito bem para blocos estáticos e blocos de grade, mas falham quando os sprites giram. Quando uma nave espacial não quadrada gira em Paint Trek, uma caixa delimitadora alinhada ao eixo deve se expandir para incluir os cantos giratórios, resultando em frustrantes "colisões fantasmas" no ar vazio.

Para asteroides circulares, orbes de energia e espaçonaves rotativas, Bounding Circles são a solução ideal.

A armadilha da raiz quadrada

Dois círculos colidem quando a distância entre seus centros é menor ou igual à soma de seus raios:

\(\text{Distância}(C_A, C_B) \le r_A + r_B\)

No código, o cálculo da distância euclidiana usa Math.Sqrt (ou Vector2.Distance). No entanto, calcular raízes quadradas em um loop com 200 projéteis queima centenas de ciclos desnecessários de CPU!

Ao comparar a distância quadrada com a soma do raio quadrado, eliminamos completamente a raiz quadrada:

\(\text{DistânciaQuadrada} \le (r_A + r_B)^2\)

Implementação MonoGame: Circle vs. Circle

public struct Circle
{
    public Vector2 Center;
    public float Radius;

    public Circle(Vector2 center, float radius)
    {
        Center = center;
        Radius = radius;
    }

    public bool Intersects(Circle other)
    {
        float radiusSum = this.Radius + other.Radius;
        
        // MonoGame built-in Vector2.DistanceSquared
        return Vector2.DistanceSquared(this.Center, other.Center) <= (radiusSum * radiusSum);
    }
}

Agora integre isso diretamente em uma entidade:

public class PaintTrekFighter : Sprite
{
    public float CollisionRadius = 18f;
    public Vector2 Center => Position + new Vector2(Texture.Width * 0.5f, Texture.Height * 0.5f);

    public Circle BoundingCircle => new Circle(Center, CollisionRadius);

    public bool CollidesWith(PaintTrekFighter other)
    {
        return this.BoundingCircle.Intersects(other.BoundingCircle);
    }
}

Zero raízes quadradas, imune à rotação de sprites e extremamente rápido.


4. Nível 3: Formas mistas – Círculo vs. Caixa (MathHelper.Clamp)

O que acontece quando uma nave circular em Paint Trek navega por um labirinto estreito de barreiras de defesa retangulares ou quando uma bala redonda atinge um bloco quadrado em Blocked: Pixel Panzer?

Precisamos da colisão Círculo vs. Retângulo.

O Algoritmo de Fixação

A estratégia é encontrar o ponto no retângulo que está mais próximo do centro do círculo e então testar se a distância desse ponto mais próximo ao centro é menor que o raio do círculo.

O MathHelper.Clamp do MonoGame torna isso trivial:

public static class Collision2D
{
    public static bool CircleIntersectsRectangle(Circle circle, Rectangle rect)
    {
        // Find the closest point on the rectangle to the circle center
        float closestX = MathHelper.Clamp(circle.Center.X, rect.Left, rect.Right);
        float closestY = MathHelper.Clamp(circle.Center.Y, rect.Top, rect.Bottom);

        // Vector from closest point to circle center
        float distanceX = circle.Center.X - closestX;
        float distanceY = circle.Center.Y - closestY;

        // Check squared distance against squared radius
        float distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
        return distanceSquared <= (circle.Radius * circle.Radius);
    }
}

Agora você pode testar os escudos dos jogadores contra tijolos retangulares com zero alocações e alta precisão!


5. Nível 4: Detecção Contínua de Colisão (CCD) e Raios Varridos

Você já disparou um atirador de elite de hipervelocidade ou um canhão laser em seu jogo, apenas para ver a bala passar magicamente através de uma nave inimiga estreita sem causar danos?

Esse bug é conhecido como túnel.

Como os jogos discretos são atualizados em intervalos de tempo (\(\Delta t = 16,6\text{ms}\) a 60 FPS), um objeto que se move a 1.800 pixels por segundo viaja 30 pixels em um único quadro. Se o casco inimigo tiver apenas 15 pixels de espessura, a bala estava na frente do inimigo no Quadro 1 e completamente atrás do inimigo no Quadro 2.

Frame 1:  [ Bullet ]  --->       | Enemy Wall |
Frame 2:                          | Enemy Wall |       --->  [ Bullet ]
                               (NO HIT DETECTED!)

A solução: segmento varrido vs. caixa (método de laje)

Em vez de testar um único ponto, testamos todo o segmento de linha conectando a posição do marcador no Quadro 1 (previousPosition) ao Quadro 2 (currentPosition).

Aqui está o método de interseção de laje de produção de raios do nosso título complementar SpiralWar:

public static class ContinuousCollision
{
    public static bool IntersectsSweptRay(Vector2 rayStart, Vector2 rayEnd, Rectangle box, out Vector2 hitPoint)
    {
        hitPoint = Vector2.Zero;

        Vector2 direction = rayEnd - rayStart;
        float tMin = 0f;
        float tMax = 1f;

        // Clip against X slabs
        if (MathF.Abs(direction.X) > 1e-6f)
        {
            float invX = 1f / direction.X;
            float t1 = (box.Left - rayStart.X) * invX;
            float t2 = (box.Right - rayStart.X) * invX;
            if (t1 > t2) (t1, t2) = (t2, t1);

            tMin = MathF.Max(tMin, t1);
            tMax = MathF.Min(tMax, t2);
            if (tMin > tMax) return false;
        }
        else if (rayStart.X < box.Left || rayStart.X > box.Right)
        {
            return false;
        }

        // Clip against Y slabs
        if (MathF.Abs(direction.Y) > 1e-6f)
        {
            float invY = 1f / direction.Y;
            float t1 = (box.Top - rayStart.Y) * invY;
            float t2 = (box.Bottom - rayStart.Y) * invY;
            if (t1 > t2) (t1, t2) = (t2, t1);

            tMin = MathF.Max(tMin, t1);
            tMax = MathF.Min(tMax, t2);
            if (tMin > tMax) return false;
        }
        else if (rayStart.Y < box.Top || rayStart.Y > box.Bottom)
        {
            return false;
        }

        hitPoint = rayStart + direction * tMin;
        return true;
    }
}

Em Blocked: Pixel Panzer, a habilidade Laser Beam contínua do jogador usa esse raio exato para cortar fileiras de blocos descendentes sem perder uma única colisão.


6. Nível 5: Detecção de colisão perfeita com pixels de nível de produção

Agora alcançamos o nível máximo de precisão 2D: Pixel-Perfect Collision.

Em um jogo de tiro de tanque retrô ou em um combate aéreo de nave espacial, formas irregulares (barris de tanque, asas, cabines de pilotagem) são cercadas por pixels transparentes na textura do sprite. Quando um míssil inimigo atinge aquele espaço transparente, os jogadores percebem imediatamente.

A colisão perfeita de pixels inspeciona os canais alfa (transparência) reais das texturas sobrepostas. Se dois pixels não transparentes se sobrepõem na mesma coordenada mundial, ocorreu um verdadeiro impacto físico.

O erro fatal: GetData dentro de Update()

Muitos tutoriais instruem os iniciantes a fazer isso:

// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!

Chamar Texture2D.GetData() durante o tempo de execução força a CPU a aguardar a liberação do pipeline da GPU, puxando a memória de textura de volta para o barramento. Fazer isso para dezenas de sprites em cada quadro irá diminuir seu jogo de 60 FPS para 5 FPS e acionar ANRs do Android imediatamente!

A solução testada em batalha de Bloqueado: Pixel Panzer

Em Blocked: Pixel Panzer, nosso Sprite.cs implementa uma rotina IntersectsPixel ultra-otimizada construída em torno de 5 regras cruciais:

  1. Cache estático de matriz de cores: GetData é chamado exatamente uma vez quando a textura é carregada e armazenado em um Dictionary<Texture2D, Color[]>.
  2. AABB Early Exit Guard: Se Bounds.Intersects(other.Bounds) for falso, nós resgatamos imediatamente. 99% das verificações são eliminadas antes de inspecionar um único pixel.
  3. Atlas de Textura e Suporte SourceRectangle: Manipula sprites compactados em atlas de textura usando deslocamentos de retângulo de origem.
  4. Janela de sobreposição calculada: Fazemos apenas um loop sobre o retângulo de interseção exato entre os dois sprites (Math.Max(a.Top, b.Top), etc.).
  5. Curto-circuito alfa: Se o pixel do Sprite A for transparente (A <= 20), o Sprite B será completamente ignorado.
  6. Elevação de propriedades: Acessar propriedades virtuais (Bounds) em loops aninhados cria milhares de cópias de estruturas. Nós os armazenamos em variáveis ​​de pilha locais antes de entrar no loop.

Aqui está o código completo e pronto para produção:

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class AdvancedSprite : Sprite
{
    private static readonly Dictionary<Texture2D, Color[]> _textureDataCache = new();

    public Rectangle? SourceRectangle { get; set; }

    public static Color[] GetCachedTextureData(Texture2D texture)
    {
        if (!_textureDataCache.TryGetValue(texture, out var data))
        {
            data = new Color[texture.Width * texture.Height];
            texture.GetData(data);
            _textureDataCache[texture] = data;
        }
        return data;
    }

    public bool IntersectsPixel(AdvancedSprite other)
    {
        // 1. Hoist bounds into local stack variables
        Rectangle a = this.Bounds;
        Rectangle b = other.Bounds;

        // 2. Fast AABB Broad-Phase check
        if (!a.Intersects(b)) return false;

        // Fallback to rectangle check if textures are missing
        if (this.Texture == null || other.Texture == null) return true;

        // 3. Retrieve pre-cached color arrays (Zero GPU latency)
        Color[] dataA = GetCachedTextureData(this.Texture);
        Color[] dataB = GetCachedTextureData(other.Texture);

        int aTexW = this.Texture.Width;
        int bTexW = other.Texture.Width;

        // Atlas / SourceRectangle offset mapping
        bool aHasSrc = this.SourceRectangle.HasValue;
        Rectangle aSrc = aHasSrc ? this.SourceRectangle.Value : Rectangle.Empty;
        int aOriginX = aHasSrc ? aSrc.X : 0;
        int aOriginY = aHasSrc ? aSrc.Y : 0;
        int aSpanX   = aHasSrc ? aSrc.Width  : this.Texture.Width;
        int aSpanY   = aHasSrc ? aSrc.Height : this.Texture.Height;

        bool bHasSrc = other.SourceRectangle.HasValue;
        Rectangle bSrc = bHasSrc ? other.SourceRectangle.Value : Rectangle.Empty;
        int bOriginX = bHasSrc ? bSrc.X : 0;
        int bOriginY = bHasSrc ? bSrc.Y : 0;
        int bSpanX   = bHasSrc ? bSrc.Width  : other.Texture.Width;
        int bSpanY   = bHasSrc ? bSrc.Height : other.Texture.Height;

        // 4. Calculate the overlapping sub-rectangle
        int top    = Math.Max(a.Top, b.Top);
        int bottom = Math.Min(a.Bottom, b.Bottom);
        int left   = Math.Max(a.Left, b.Left);
        int right  = Math.Min(a.Right, b.Right);

        // 5. Scan only overlapping pixels
        for (int y = top; y < bottom; y++)
        {
            // Row offsets hoisted outside the inner X loop!
            int colorA_Y = aOriginY + (int)((y - a.Y) / (float)a.Height * aSpanY);
            int colorB_Y = bOriginY + (int)((y - b.Y) / (float)b.Height * bSpanY);
            int rowA = colorA_Y * aTexW;
            int rowB = colorB_Y * bTexW;

            for (int x = left; x < right; x++)
            {
                int colorA_X = aOriginX + (int)((x - a.X) / (float)a.Width * aSpanX);

                // Early Bailout: If Pixel A is transparent, skip B entirely!
                if (dataA[colorA_X + rowA].A <= 20) continue;

                int colorB_X = bOriginX + (int)((x - b.X) / (float)b.Width * bSpanX);

                // If Pixel B is also opaque, we have confirmed contact!
                if (dataB[colorB_X + rowB].A > 20)
                {
                    return true;
                }
            }
        }

        return false;
    }
}

Essa rotina é executada em menos de 0,1 milissegundos em hardware Android moderno e sistemas de desktop.


7. Nível 6: Grade Espacial de Fase Ampla e GC Móvel de Alocação Zero

Imagine que seu jogo tem 100 blocos, 60 balas e 20 inimigos. Se você testar cada entidade em relação a todas as outras entidades usando loops for aninhados, você executará:

\(\frac{180 \times 179}{2} = 16.110 \text{ verificações por quadro!}\)

A 60 FPS, isso equivale a quase 1.000.000 de verificações de colisão a cada segundo.

Para dimensionar seu jogo, você deve implementar Particionamento Espacial de Fase Ampla.

A grade de hash espacial uniforme

Dividimos a tela em uma grade 2D de células uniformes (em Blocked: Pixel Panzer, cada célula tem \(150 \times 150\) pixels). Uma entidade testa apenas colisões contra outras entidades que residem nas mesmas células da grade.

O problema da memória móvel: GC Thrashing

Se seu SpatialGrid criar new List<Point>() ou new List<Block>() a cada quadro, você alocará megabytes de lixo a cada minuto no heap gerenciado.

No tempo de execução Mono do Android, isso aciona coletas de lixo Gen-0 frequentes, causando: tempo de execução mono: contenção de bloqueio nativo (mono_class_is_subclass_)

O jogo trava e o Google Play sinaliza seu jogo com avisos ANR!

A solução: buffers de rascunho reutilizáveis

Aqui está a Grade de hash espacial de alocação zero de CollisionManager.cs em Bloqueado: Pixel Panzer:

public class SpatialGridManager
{
    private const int CELL_SIZE = 150;
    private readonly Dictionary<Point, List<Sprite>> _grid = new();

    // Zero-GC: Reusable scratch buffers allocated ONCE at startup
    private readonly List<Point> _scratchCells = new();
    private readonly HashSet<Sprite> _scratchCheckedEntities = new();

    public void BuildGrid(List<Sprite> entities)
    {
        // Clear lists without re-allocating new List objects
        foreach (var list in _grid.Values)
        {
            list.Clear();
        }

        foreach (var entity in entities)
        {
            if (!entity.IsActive) continue;

            GetOccupiedCells(entity.Bounds, _scratchCells);
            foreach (var cell in _scratchCells)
            {
                if (!_grid.TryGetValue(cell, out var list))
                {
                    list = new List<Sprite>();
                    _grid[cell] = list;
                }
                list.Add(entity);
            }
        }
    }

    private void GetOccupiedCells(Rectangle bounds, List<Point> outCells)
    {
        outCells.Clear();

        int startX = bounds.Left / CELL_SIZE;
        int startY = bounds.Top / CELL_SIZE;
        int endX = bounds.Right / CELL_SIZE;
        int endY = bounds.Bottom / CELL_SIZE;

        for (int x = startX; x <= endX; x++)
        {
            for (int y = startY; y <= endY; y++)
            {
                outCells.Add(new Point(x, y));
            }
        }
    }

    public void CheckCollisions(Player player)
    {
        if (player.IsInvulnerable) return;

        GetOccupiedCells(player.Bounds, _scratchCells);
        _scratchCheckedEntities.Clear();

        foreach (var cell in _scratchCells)
        {
            if (_grid.TryGetValue(cell, out var entitiesInCell))
            {
                foreach (var other in entitiesInCell)
                {
                    if (other == player || _scratchCheckedEntities.Contains(other)) continue;
                    _scratchCheckedEntities.Add(other);

                    // 1. Broad-phase AABB test
                    if (player.Bounds.Intersects(other.Bounds))
                    {
                        // 2. High-precision Pixel-Perfect test
                        if (player is AdvancedSprite advPlayer && other is AdvancedSprite advOther)
                        {
                            if (advPlayer.IntersectsPixel(advOther))
                            {
                                player.TakeDamage(10);
                            }
                        }
                    }
                }
            }
        }
    }
}

Ao reutilizar _scratchCells e _scratchCheckedEntities, toda a etapa de colisão é executada com 0 bytes de alocação de heap por quadro.


8. Tabela de comparação resumida

Técnica Custo Matemático Apoio Rotativo Tunelamento seguro? Melhor caso de uso em MonoGame
AABB (Retângulo.Intersecção) Ultrarrápido (~4 comparações de números inteiros) Não Não Blocos de grade, marcador de fase ampla, UI
Círculo (DistânciaQuadrada) Muito rápido (3 muls, 0 sqrt) Sim (Invariante) Não Navios redondos, bolas de fogo, orbes de energia
Círculo vs Caixa (Grampo) Rápido (Local MathHelper.Clamp) Sim Não Jogador circular navegando em paredes estreitas
Raio de varredura (CCD de laje) Moderado (Raycast Paramétrico) Sim Sim Balas rápidas de atirador, armas ferroviárias, lasers
Pixel-Perfeito (IntersectsPixel) Seletivo (Sub-rect Restrito) Sim Não Contornos de sprites irregulares, hitboxes justos
Grade Espacial (Zero-GC) Fase ampla (\(O(N^2) \to O(N)\)) N/A N/A Ondas densas, infernos de balas, Android 60 FPS

Demonstrações de produção do mundo real: Arar Games

Essas arquiteturas de colisão não são experimentos teóricos — elas são a verdadeira base de engenharia que alimenta nossos títulos lançados comercialmente:

  • Bloqueado: Pixel Panzer: Nosso jogo retrô de arcade de sobrevivência de tanques no Google Play e na Microsoft Store. Ele apresenta nosso sistema completo de colisão de dois níveis: uma grade hash espacial de alocação zero que filtra blocos em queda e verificação de contato com pixels perfeitos para tanques, jatos de combate, cartuchos de torre e munição elementar ColorWheel.
  • Paint Trek: Nosso jogo de tiro espacial em ritmo acelerado com colisão circular rotacional, lançamento de raios contínuo e sistemas de defesa contra mísseis de grande volume.

Conclusão e próximas etapas

MonoGame oferece o poder de projetar detecção de colisão que atenda perfeitamente às necessidades do seu jogo. Ao agrupar verificações caras de Pixel-Perfect atrás de testes rápidos AABB, utilizar Distâncias Quadradas para círculos e eliminar a coleta de lixo com Reusable Scratch Buffers, você pode oferecer desempenho suave de console de 60/120 FPS em plataformas desktop e móveis.

Confira nossos jogos nas lojas de aplicativos para ver esses sistemas de colisão em ação e comece a implementar esses padrões em seus próprios projetos MonoGame hoje mesmo!



Palavras-chave e hashtags de SEO

Palavras-chave: Detecção de colisão MonoGame 2D, desenvolvimento de jogos em C#, Rectangle.Intersects MonoGame, colisão perfeita de pixels C#, grade de hash espacial MonoGame, colisão contínua de raios varridos, otimização de desempenho de jogos indie, loop de jogo de alocação zero, otimização de MonoGame para Android, Arar Games, Blocked Pixel Panzer, Paint Trek.

#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixelPanzer #PaintTrek #ArarGames #GameProgramming




Hiç yorum yok:

Yorum Gönder