27 Eylül 2026 Pazar

Détection de collision 2D dans MonoGame : des rectangles de base aux systèmes Pixel-Perfect à allocation nulle




Détection de collision 2D dans MonoGame : des rectangles de base aux systèmes Pixel-Perfect à allocation nulle

Lors de la création de jeux 2D dans des moteurs visuels comme Unity ou Godot, la détection de collision ressemble souvent à une série de cases à cocher d'inspecteur : vous appuyez sur un « BoxCollider2D » ou un « CircleCollider2D », attachez un « Rigidbody » et espérez que l'étape physique interne ne bégaie pas sur les appareils mobiles.

Dans MonoGame et C#, cependant, vous avez un contrôle total. Il n'y a pas de surcharge physique cachée, pas d'inertie de rotation indésirable et pas de pics mystérieux de collecte de déchets (GC) qui volent votre budget de trame.

Chez Arar Games, lorsque nous avons créé Blocked : Pixel Panzer et Paint Trek, nos boucles de jeu d'arcade devaient traiter des centaines de balles ennemies à grande vitesse, des grilles de briques explosives, des tourelles de chars rotatives, des survols d'avions de combat et des boucliers contre les particules à une vitesse de 60 à 120 FPS sur les PC Windows et les appareils Android. Un moteur physique à usage général était hors de question : nous avions besoin d’une architecture de collision à plusieurs niveaux spécialement conçue.

Dans ce guide complet, basé sur le code, nous partirons des bases absolues de la collision MonoGame (« Rectangle.Intersects » avec de vrais sprites « Bullet » et « Enemy ») et passerons aux vérifications de cercle avancées, au serrage mixte, aux raycasts anti-tunneling, à la collision Pixel-Perfect de qualité production et aux grilles spatiales à allocation nulle optimisées pour la survie du GC mobile.


1. La Fondation : Une hiérarchie simple de « Sprite » MonoGame

Avant de détecter des collisions, nous avons besoin d’entités de jeu propres. Dans MonoGame, une entité possède fondamentalement une position, une texture et un rectangle englobant.

Voici l'architecture d'entité de base utilisée dans nos jeux :

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

Créons maintenant les classes concrètes Player, Enemy et 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;
    }
}

Une fois ces entités en place, explorons comment détecter les collisions entre elles, en commençant par la méthode la plus simple.


2. Niveau 1 : La collision la plus simple – « Rectangle.Intersects » (AABB)

La vérification de collision 2D la plus fondamentale dans MonoGame est le test Axis-Aligned Bounding Box (AABB). Le terme « aligné sur l'axe » signifie simplement que les bords du rectangle sont complètement parallèles aux axes \(X\) et \(Y\) de l'écran (pas de rotation).

MonoGame fournit une méthode intégrée rapide : Rectangle.Intersects(Rectangle value).

Comment Rectangle.Intersects fonctionne sous le capot

Sous la surface, MonoGame exécute quatre comparaisons d'entiers :

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

Si les quatre conditions sont remplies, les rectangles se chevauchent. Si même une condition échoue, un axe vide les sépare et aucune collision n’est possible.

Code de gameplay réel : Bullet contre Enemy dans Game1.Update

Voici comment vérifier les collisions entre une liste de balles actives et une liste d'ennemis actifs dans votre boucle principale « Mise à jour » 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);
    }
}

Conseil de performance : Notez que nous itérons en arrière (for (int i = list.Count - 1; i >= 0; i--)) ! Si vous utilisez foreach et essayez d'appeler _bullets.Remove(bullet), C# renvoie une InvalidOperationException : La collection a été modifiée. L'itération vers l'arrière permet la suppression d'éléments en toute sécurité sans problèmes de réindexation de la mémoire.

The Arcade Secret : "Fair Hitboxes" via Inflate

Dans les jeux rétro comme Blocked : Pixel Panzer, les textures des sprites incluent souvent des marges transparentes ou des pointes d'antenne. Si le tank du joueur explose parce qu'une balle a touché un coin vide et transparent de sa texture, le joueur se sentira trompé.

Pour que les collisions semblent réactives et équitables, les jeux utilisent une Hitbox plus petite à l'intérieur du sprite en utilisant 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. Niveau 2 : Collision de cercle à cercle (immunité rotationnelle)

Les rectangles fonctionnent très bien pour les blocs statiques et les tuiles de grille, mais ils échouent lorsque les sprites tournent. Lorsqu'un vaisseau spatial non carré tourne dans Paint Trek, un cadre de délimitation aligné sur l'axe doit s'étendre pour englober les coins en rotation, ce qui entraîne des « collisions fantômes » frustrantes dans l'air vide.

Pour les astéroïdes circulaires, les orbes d'énergie à tête chercheuse et les vaisseaux spatiaux en rotation, les Bounding Circles sont la solution idéale.

Le piège à racines carrées

Deux cercles entrent en collision lorsque la distance entre leurs centres est inférieure ou égale à la somme de leurs rayons :

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

Dans le code, le calcul de la distance euclidienne utilise « Math.Sqrt » (ou « Vector2.Distance »). Cependant, calculer des racines carrées dans une boucle avec 200 projectiles brûle des centaines de cycles CPU inutiles !

En comparant la distance carrée à la somme du rayon carré, nous éliminons complètement la racine carrée :

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

Implémentation de MonoGame : Circle contre 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);
    }
}

Intégrez maintenant cela directement dans une entité :

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

Zéro racine carrée, immunisé contre la rotation des sprites et ultra-rapide.


4. Niveau 3 : Formes mixtes – Cercle contre boîte (MathHelper.Clamp)

Que se passe-t-il lorsqu'un vaisseau spatial circulaire dans Paint Trek navigue à travers un labyrinthe étroit de barrières de défense rectangulaires, ou lorsqu'une balle ronde touche un bloc carré dans Blocked : Pixel Panzer ?

Nous avons besoin d'une collision Cercle contre Rectangle.

L'algorithme de serrage

La stratégie consiste à trouver le point du rectangle le plus proche du centre du cercle, puis à tester si la distance entre ce point le plus proche et le centre est inférieure au rayon du cercle.

« MathHelper.Clamp » de MonoGame rend cela 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);
    }
}

Vous pouvez désormais tester les boucliers des joueurs contre des briques rectangulaires sans allocation et avec une grande précision !


5. Niveau 4 : Détection de collision continue (CCD) et rayons balayés

Avez-vous déjà tiré un tir de sniper à hyper-vitesse ou un laser railgun dans votre jeu, pour ensuite voir la balle passer comme par magie à travers un mince vaisseau ennemi sans infliger de dégâts ?

Ce bug est connu sous le nom de tunnelage.

Étant donné que les jeux discrets se mettent à jour par pas de temps (\(\Delta t = 16,6\text{ms}\) à 60 FPS), un objet se déplaçant à 1 800 pixels par seconde parcourt 30 pixels dans une seule image. Si la coque ennemie n'a que 15 pixels d'épaisseur, la balle était devant l'ennemi sur l'image 1 et complètement derrière l'ennemi sur l'image 2.

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

La solution : segment balayé ou boîte (méthode dalle)

Au lieu de tester un seul point, nous testons l'ensemble du segment de ligne reliant la position de la puce sur l'image 1 (previousPosition) à l'image 2 (currentPosition).

Voici la méthode d'intersection de dalles de production raycasting de notre titre compagnon 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;
    }
}

Dans Blocked : Pixel Panzer, la compétence Laser Beam continue du joueur utilise ce raycast exact pour trancher des rangées de blocs descendants sans manquer une seule collision.


6. Niveau 5 : Détection de collision au pixel près de qualité production

Nous atteignons désormais le niveau ultime de précision 2D : Pixel-Perfect Collision.

Dans un jeu de tir de char rétro ou un combat aérien de vaisseau spatial, des formes irrégulières (barils de char, ailes, cockpits de cockpit) sont entourées de pixels transparents dans la texture du sprite. Lorsqu’un missile ennemi touche cet espace transparent, les joueurs le remarquent immédiatement.

La collision parfaite au pixel près inspecte les canaux alpha (transparence) réels des textures qui se chevauchent. Si deux pixels non transparents se chevauchent aux mêmes coordonnées mondiales, un véritable impact physique s'est produit.

L'erreur fatale : GetData dans Update()

De nombreux tutoriels demandent aux débutants de le faire :

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

L'appel de « Texture2D.GetData() » pendant l'exécution force le CPU à attendre que le pipeline GPU se vide, récupérant ainsi la mémoire de texture sur le bus. Faire cela pour des dizaines de sprites à chaque image fera passer votre jeu de 60 FPS à 5 FPS et déclenchera immédiatement les ANR Android !

La solution testée au combat de Blocked : Pixel Panzer

Dans Blocked : Pixel Panzer, notre Sprite.cs implémente une routine IntersectsPixel ultra-optimisée construite autour de 5 règles cruciales :

  1. Cache de tableau de couleurs statique : GetData est appelé exactement une fois lors du chargement de la texture et stocké dans un Dictionary<Texture2D, Color[]>.
  2. AABB Early Exit Guard : Si Bounds.Intersects(other.Bounds) est faux, nous renflouons immédiatement. 99 % des contrôles sont éliminés avant d’inspecter un seul pixel.
  3. Support de l'Atlas de textures et de SourceRectangle : Gère les sprites emballés dans des atlas de textures à l'aide des décalages de rectangle source.
  4. Fenêtre de chevauchement calculée : Nous effectuons une boucle uniquement sur le rectangle d'intersection exact entre les deux sprites (Math.Max(a.Top, b.Top), etc.).
  5. Court-circuit alpha : Si le pixel du Sprite A est transparent (A <= 20), le Sprite B est complètement ignoré.
  6. Property Hoisting : L'accès aux propriétés virtuelles (« Bounds ») dans des boucles imbriquées crée des milliers de copies de structure. Nous les stockons dans des variables de pile locales avant d'entrer dans la boucle.

Voici le code complet, prêt pour la production :

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

Cette routine s'exécute en moins de 0,1 milliseconde sur le matériel Android et les systèmes de bureau modernes.


7. Niveau 6 : Grille spatiale à large phase et GC mobile à allocation nulle

Imaginez que votre jeu comporte 100 blocs, 60 balles et 20 ennemis. Si vous testez chaque entité par rapport à toutes les autres entités à l'aide de boucles « for » imbriquées, vous effectuez :

\(\frac{180 \times 179}{2} = 16 110 \text{ contrôles par image !}\)

À 60 FPS, cela représente près de 1 000 000 de contrôles de collision chaque seconde.

Pour faire évoluer votre jeu, vous devez mettre en œuvre le partitionnement spatial en phase large.

La grille de hachage spatiale uniforme

Nous divisons l'écran en une grille 2D de cellules uniformes (dans Bloqué : Pixel Panzer, chaque cellule vaut 150 $ \times 150$ pixels). Une entité teste uniquement les collisions avec d'autres entités résidant dans les mêmes cellules de la grille.

Le problème de la mémoire mobile : GC Thrashing

Si votre SpatialGrid crée new List<Point>() ou new List<Block>() à chaque image, vous allouez des mégaoctets de déchets chaque minute sur le tas géré.

Sur le runtime Mono d'Android, cela déclenche de fréquentes récupérations de place Gen-0, provoquant : mono runtime : conflit de verrouillage natif (mono_class_is_subclass_)

Le jeu bégaie et Google Play signale votre jeu avec des avertissements ANR !

La solution : des tampons anti-rayures réutilisables

Voici la Grille de hachage spatiale à allocation nulle de CollisionManager.cs dans Bloqué : 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);
                            }
                        }
                    }
                }
            }
        }
    }
}

En réutilisant _scratchCells et _scratchCheckedEntities, toute l'étape de collision s'exécute avec 0 octet d'allocation de tas par trame.


8. Tableau de comparaison récapitulatif

Techniques Coût mathématique Support rotationnel Le tunnelage est-il sécurisé ? Meilleur cas d'utilisation dans MonoGame
AABB (Rectangle.Intersects) Ultra-rapide (~ 4 comparaisons entières) Non Non Blocs de grille, puce à large phase, interface utilisateur
Cercle (DistanceSquared) Très rapide (3 muls, 0 sqrt) Oui (invariant) Non Navires ronds, boules de feu, orbes d'énergie
Cercle vs Boîte (Clamp) Rapide (MathHelper.Clamp local) Oui Non Joueur circulaire naviguant dans des murs étroits
Rayon balayé (Dalle CCD) Modéré (Raycast paramétrique) Oui Oui Balles de tireurs d'élite rapides, railguns, lasers
Pixel-Perfect (IntersectsPixel) Sélectif (Sous-rect restreint) Oui Non Contours de sprite irréguliers, hitbox équitables
Grille spatiale (Zéro-GC) Phase large (\(O(N^2) \to O(N)\)) N/A N/A Vagues denses, enfers de balles, Android 60 FPS

Vitrines de production réelles : Arar Games

Ces architectures de collision ne sont pas des expériences théoriques : elles constituent la véritable base technique sur laquelle reposent nos titres commercialisés :

  • Bloqué : Pixel Panzer : Notre jeu d'arcade de survie de tank rétro sur Google Play et Microsoft Store. Il comprend notre système complet de collision à deux niveaux : une grille de hachage spatiale à allocation nulle filtrant les blocs tombants et une vérification de contact parfaite au pixel près pour les chars, les avions de combat, les obus de tourelle et les munitions élémentaires ColorWheel.
  • Paint Trek : Notre jeu de tir spatial au rythme effréné propose une collision en cercle rotatif, un raycasting continu et des systèmes de défense antimissile à volume balayé.

Conclusion et prochaines étapes

MonoGame vous donne le pouvoir de concevoir une détection de collision qui correspond parfaitement aux besoins de votre jeu. En effectuant des vérifications Pixel-Perfect coûteuses derrière des tests AABB rapides, en utilisant des Distances carrées pour les cercles et en éliminant le garbage collection avec des Tampons de rayures réutilisables, vous pouvez offrir des performances fluides de 60/120 FPS sur console sur les plates-formes de bureau et mobiles.

Découvrez nos jeux sur les magasins d'applications pour voir ces systèmes de collision en action, et commencez dès aujourd'hui à implémenter ces modèles dans vos propres projets MonoGame !


Liens et ressources du magasin


Mots-clés et hashtags SEO

Mots clés : Détection de collision MonoGame 2D, développement de jeux C#, Rectangle.Intersects MonoGame, collision parfaite de pixels C#, grille de hachage spatiale MonoGame, collision continue à rayons balayés, optimisation des performances de jeu indépendant, boucle de jeu à allocation nulle, optimisation Android MonoGame, 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