27 Eylül 2026 Pazar

Detección de colisiones 2D en MonoGame: desde rectángulos básicos hasta sistemas perfectos de píxeles de asignación cero




Detección de colisiones 2D en MonoGame: desde rectángulos básicos hasta sistemas perfectos de píxeles de asignación cero

Al crear juegos 2D en motores visuales como Unity o Godot, la detección de colisiones a menudo se siente como una serie de casillas de verificación del inspector: presionas un BoxCollider2D o CircleCollider2D, adjuntas un Rigidbody y esperas que el paso de física interna no tartamudee en los dispositivos móviles.

Sin embargo, en MonoGame y C#, tú tienes el control total. No hay sobrecarga física oculta, ni inercia rotacional no deseada, ni picos misteriosos de recolección de basura (GC) que roben su presupuesto de marco.

En Arar Games, cuando construimos Blocked: Pixel Panzer y Paint Trek, nuestros bucles de juego arcade necesitaban procesar cientos de balas enemigas de alta velocidad, rejillas de ladrillos explosivas, torretas de tanques giratorias, sobrevuelos de aviones de combate y escudos de partículas de 60 a 120 FPS en PC con Windows y dispositivos Android. Un motor de física de propósito general estaba fuera de discusión: necesitábamos una arquitectura de colisión escalonada y diseñada específicamente.

En esta guía completa basada en código, comenzaremos desde los conceptos básicos absolutos de la colisión MonoGame ("Rectangle.Intersects" con sprites "Bullet" y "Enemy" reales) y avanzaremos hasta comprobaciones de círculos avanzadas, sujeción mixta, raycasts anti-túnel, colisión de píxeles perfectos de nivel de producción y cuadrículas espaciales de asignación cero optimizadas para la supervivencia de GC móvil.


1. La base: una jerarquía de sprites monojuego simple

Antes de detectar colisiones, necesitamos entidades de juego limpias. En MonoGame, una entidad posee fundamentalmente una posición, una textura y un rectángulo delimitador.

Esta es la arquitectura de entidad básica utilizada en nuestros juegos:

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);
    }
}

Ahora creemos clases concretas Player, Enemy y Bullet:

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;
    }
}

Con estas entidades implementadas, exploremos cómo detectar colisiones entre ellas, comenzando por el método más simple.


2. Nivel 1: La colisión más simple – Rectangle.Intersects (AABB)

La comprobación de colisión 2D más fundamental en MonoGame es la prueba Axis-Aligned Bounding Box (AABB). El término "alineado con el eje" simplemente significa que los bordes del rectángulo son completamente paralelos a los ejes \(X\) e \(Y\) de la pantalla (sin rotación).

MonoGame proporciona un método rápido e integrado: Rectangle.Intersects(Valor del rectángulo).

Cómo funciona Rectangle.Intersects bajo el capó

Debajo de la superficie, MonoGame ejecuta cuatro comparaciones de números enteros:

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

Si se cumplen las cuatro condiciones, los rectángulos se superponen. Si incluso una condición falla, un eje vacío las separa y no es posible ninguna colisión.

Código de juego real: Bullet vs. Enemy en Game1.Update

Así es como verificas las colisiones entre una lista de balas activas y una lista de enemigos activos dentro de tu bucle principal de "Actualización" de 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);
    }
}

Consejo de rendimiento: ¡Observe que iteramos hacia atrás (for (int i = list.Count - 1; i >= 0; i--))! Si usa foreach e intenta llamar a _bullets.Remove(bullet), C# arroja una InvalidOperationException: la colección fue modificada. La iteración hacia atrás permite la eliminación segura de elementos sin problemas de reindexación de la memoria.

El secreto de Arcade: "Hitboxes justos" a través de Inflate

En juegos retro como Blocked: Pixel Panzer, las texturas de los sprites suelen incluir márgenes transparentes o picos de antena. Si el tanque del jugador explota porque una bala tocó una esquina transparente vacía de su textura, el jugador se sentirá engañado.

Para que la colisión se sienta receptiva y justa, los juegos usan un Hitbox más pequeño dentro del objeto 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. Nivel 2: Colisión círculo a círculo (inmunidad rotacional)

Los rectángulos funcionan muy bien para bloques estáticos y mosaicos de cuadrícula, pero fallan cuando los sprites giran. Cuando una nave espacial no cuadrada gira en Paint Trek, un cuadro delimitador alineado con el eje debe expandirse para encerrar las esquinas giratorias, lo que resulta en frustrantes "colisiones fantasmas" en el aire.

Para asteroides circulares, orbes de energía guiados y naves espaciales giratorias, los Círculos delimitadores son la solución ideal.

La trampa de la raíz cuadrada

Dos círculos chocan cuando la distancia entre sus centros es menor o igual a la suma de sus radios:

\(\text{Distancia}(C_A, C_B) \le r_A + r_B\)

En el código, para calcular la distancia euclidiana se utiliza Math.Sqrt (o Vector2.Distance). Sin embargo, calcular raíces cuadradas en un bucle con 200 proyectiles quema cientos de ciclos de CPU innecesarios.

Al comparar la distancia al cuadrado con la suma del radio al cuadrado, eliminamos completamente la raíz cuadrada:

\(\text{DistanciaCuadrada} \le (r_A + r_B)^2\)

Implementación de MonoGame: Círculo versus Círculo

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);
    }
}

Ahora integre esto directamente en una entidad:

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);
    }
}

Cero raíces cuadradas, inmune a la rotación de sprites y ultrarrápido.


4. Nivel 3: Formas mixtas: círculo versus caja (MathHelper.Clamp)

¿Qué sucede cuando una nave espacial circular en Paint Trek navega a través de un estrecho laberinto de barreras defensivas rectangulares, o cuando una bala redonda golpea un bloque cuadrado en Blocked: Pixel Panzer?

Necesitamos una colisión Círculo vs. Rectángulo.

El algoritmo de sujeción

La estrategia es encontrar el punto del rectángulo más cercano al centro del círculo y luego probar si la distancia desde ese punto más cercano al centro es menor que el radio del círculo.

MathHelper.Clamp de MonoGame hace que esto sea 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);
    }
}

¡Ahora puedes probar los escudos de los jugadores contra ladrillos rectangulares sin asignaciones y con alta precisión!


5. Nivel 4: Detección continua de colisiones (CCD) y rayos barridos

¿Alguna vez has disparado una bala de francotirador de hipervelocidad o un cañón láser de riel en tu juego, solo para ver cómo la bala atraviesa mágicamente una delgada nave enemiga sin causar daño?

Este error se conoce como tunelización.

Debido a que los juegos discretos se actualizan en pasos de tiempo (\(\Delta t = 16,6\text{ms}\) a 60 FPS), un objeto que se mueve a 1800 píxeles por segundo viaja 30 píxeles en un solo cuadro. Si el casco del enemigo tiene solo 15 píxeles de grosor, la bala estaba delante del enemigo en el Cuadro 1 y completamente detrás del enemigo en el Cuadro 2.

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

La solución: segmento barrido versus caja (método de losa)

En lugar de probar un solo punto, probamos todo el segmento de línea que conecta la posición de la bala en el Cuadro 1 (posición anterior) con el Cuadro 2 (posición actual).

Aquí está el método de producción de intersección de losas de raycasting de nuestro título complementario 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;
    }
}

En Blocked: Pixel Panzer, la habilidad Rayo láser continua del jugador utiliza este raycast exacto para cortar filas de bloques descendentes sin perder una sola colisión.


6. Nivel 5: Detección de colisiones perfecta en píxeles de nivel de producción

Ahora alcanzamos el máximo nivel de precisión 2D: Colisión perfecta de píxeles.

En un juego de disparos de tanques retro o un combate aéreo de naves espaciales, las formas irregulares (barriles de tanques, alas, cabinas de pilotaje) están rodeadas de píxeles transparentes en la textura del sprite. Cuando un misil enemigo alcanza ese espacio transparente, los jugadores lo notan inmediatamente.

La colisión de píxeles perfectos inspecciona los canales alfa (transparencia) reales de las texturas superpuestas. Si dos píxeles no transparentes se superponen en la misma coordenada mundial, se ha producido un verdadero impacto físico.

El error fatal: GetData dentro de Update()

Muchos tutoriales instruyen a los principiantes a hacer esto:

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

Llamar a Texture2D.GetData() durante el tiempo de ejecución obliga a la CPU a esperar a que se vacíe la canalización de la GPU, lo que devuelve la memoria de textura al bus. ¡Hacer esto para docenas de sprites en cada fotograma acelerará tu juego de 60 FPS a 5 FPS y activará ANR de Android inmediatamente!

La solución probada en batalla de Bloqueado: Pixel Panzer

En Blocked: Pixel Panzer, nuestro Sprite.cs implementa una rutina IntersectsPixel ultra optimizada construida alrededor de 5 reglas cruciales:

  1. Caché de matriz de colores estática: GetData se llama exactamente una vez cuando se carga la textura y se almacena en un Diccionario<Texture2D, Color[]>.
  2. AABB Early Exit Guard: Si Bounds.Intersects(other.Bounds) es falso, nos retiramos inmediatamente. El 99% de los controles se eliminan antes de inspeccionar un solo píxel.
  3. Soporte para Atlas de texturas y SourceRectangle: Maneja sprites empaquetados en atlas de texturas utilizando desplazamientos del rectángulo de origen.
  4. Ventana de superposición calculada: Solo recorremos el rectángulo de intersección exacto entre ambos sprites (Math.Max(a.Top, b.Top), etc.).
  5. Cortocircuito alfa: Si el píxel del Sprite A es transparente (A <= 20), el Sprite B se omite por completo.
  6. Elevación de propiedades: El acceso a propiedades virtuales ("Bounds") en bucles anidados crea miles de copias de estructuras. Los almacenamos en variables de pila local antes de ingresar al ciclo.

Aquí está el código completo, listo para producción:

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;
    }
}

Esta rutina se ejecuta en menos de 0,1 milisegundos en sistemas de escritorio y hardware Android modernos.


7. Nivel 6: Red espacial de fase amplia y GC móvil de asignación cero

Imagina que tu juego tiene 100 bloques, 60 balas y 20 enemigos. Si prueba cada entidad con todas las demás entidades utilizando bucles "for" anidados, realiza:

\(\frac{180 \times 179}{2} = 16,110 \text{ ¡verificaciones por cuadro!}\)

A 60 FPS, eso es casi 1.000.000 de comprobaciones de colisión por segundo.

Para escalar tu juego, debes implementar Partición espacial de fase amplia.

La cuadrícula hash espacial uniforme

Dividimos la pantalla en una cuadrícula 2D de celdas uniformes (en Bloqueado: Pixel Panzer, cada celda tiene \(150 \× 150\) píxeles). Una entidad sólo prueba colisiones contra otras entidades que residen en las mismas celdas de la cuadrícula.

El problema de la memoria móvil: paliza de GC

Si su SpatialGrid crea nueva Lista<Punto>() o nueva Lista<Bloque>() en cada fotograma, asigna megabytes de basura cada minuto en el montón administrado.

En el tiempo de ejecución Mono de Android, esto desencadena frecuentes recolecciones de basura Gen-0, lo que provoca: tiempo de ejecución mono: contención de bloqueo nativo (mono_class_is_subclass_)

¡El juego tartamudea y Google Play marca tu juego con advertencias ANR!

La solución: buffers reutilizables

Aquí está la Cuadrícula hash espacial de asignación cero de CollisionManager.cs en 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);
                            }
                        }
                    }
                }
            }
        }
    }
}

Al reutilizar _scratchCells y _scratchCheckedEntities, todo el paso de colisión se ejecuta con 0 bytes de asignación de montón por cuadro.


8. Tabla comparativa resumida

Técnica Costo matemático Soporte rotacional ¿Es seguro hacer túneles? Mejor caso de uso en MonoGame
AABB (Rectangle.Intersects) Ultrarrápido (~4 comparaciones de números enteros) No No Bloques de cuadrícula, bala de fase amplia, UI
Círculo (DistanciaCuadrada) Muy rápido (3 muls, 0 sqrt) Sí (invariante) No Naves redondas, bolas de fuego, orbes de energía
Círculo frente a caja (Abrazadera) Rápido (MathHelper.Clamp local) Sí No Jugador circular navegando por paredes estrechas
Rayo barrido (CCD en placa) Moderado (Raycast paramétrico) Sí Sí Balas rápidas de francotirador, cañones de riel, láseres
Píxel perfecto (IntersectsPixel) Selectivo (Subrecto Restringido) Sí No Contornos de sprites irregulares, hitboxes justos
Cuadrícula espacial (Cero-GC) Fase amplia (\(O(N^2) \to O(N)\)) N/A N/A Olas densas, infiernos de balas, Android 60 FPS

Muestras de producción del mundo real: Arar Games

Estas arquitecturas de colisión no son experimentos teóricos; son la verdadera base de ingeniería que impulsa nuestros títulos lanzados comercialmente:

  • Bloqueado: Pixel Panzer: Nuestro juego arcade retro de supervivencia de tanques en Google Play y Microsoft Store. Cuenta con nuestro completo sistema de colisión de dos niveles: una cuadrícula de hash espacial de asignación cero que filtra los bloques que caen y una verificación de contacto con píxeles perfectos para tanques, aviones de combate, proyectiles de torretas y municiones elementales ColorWheel.
  • Paint Trek: Nuestro trepidante shooter espacial que presenta colisiones circulares rotacionales, emisión de rayos continua y sistemas de defensa antimisiles de volumen barrido.

Conclusión y próximos pasos

MonoGame te brinda el poder de diseñar una detección de colisiones que se adapte perfectamente a las necesidades de tu juego. Al realizar costosas comprobaciones de Pixel-Perfect detrás de pruebas rápidas AABB, utilizar Distancias cuadradas para círculos y eliminar la recolección de basura con Búfers Scratch reutilizables, puedes ofrecer un rendimiento fluido de 60/120 FPS en consolas tanto en plataformas de escritorio como móviles.

¡Echa un vistazo a nuestros juegos en las tiendas de aplicaciones para ver estos sistemas de colisión en acción y comienza a implementar estos patrones en tus propios proyectos MonoGame hoy mismo!


Enlaces de tiendas y recursos


Palabras clave y hashtags de SEO

Palabras clave: Detección de colisiones 2D MonoGame, desarrollo de juegos C#, Triangle.Intersects MonoGame, colisión perfecta de píxeles C#, cuadrícula hash espacial MonoGame, colisión continua de rayos barridos, optimización del rendimiento de juegos independientes, bucle de juego de asignación cero, optimización MonoGame de 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