27 Eylül 2026 Pazar

Detecció de col·lisions 2D a MonoGame: des de rectangles bàsics fins a sistemes perfectes de píxels d'assignació zero




Detecció de col·lisions 2D a MonoGame: des de rectangles bàsics fins a sistemes perfectes de píxels d'assignació zero

Quan es construeixen jocs en 2D en motors visuals com Unity o Godot, la detecció de col·lisions sovint se sent com una sèrie de caselles de verificació d'inspector: apliqueu un "BoxCollider2D" o "CircleCollider2D", adjunteu un "Rigidbody" i espereu que el pas de la física interna no tartamudeixi als dispositius mòbils.

A MonoGame i C#, però, tens el control total. No hi ha cap sobrecàrrega física oculta, cap inèrcia de rotació no desitjada i cap espiga misteriosa de recollida d'escombraries (GC) que us roben el pressupost del fotograma.

A Arar Games, quan vam construir Blocked: Pixel Panzer i Paint Trek, els nostres bucles de jocs arcade necessitaven per processar centenars de bales enemigues d'alta velocitat, quadrícules de maons explosius, torretes de tancs giratòries, sobrevols d'avions de caça i escuts de partícules a 60 i 120 dispositius amb Windows PC i FPS amb Android. Un motor de física de propòsit general estava fora de dubte: necessitàvem una arquitectura de col·lisió en nivells dissenyada específicament.

En aquesta guia completa, guiada per codi, començarem des dels fonaments absoluts de la col·lisió MonoGame ('Rectangle.Intersects' amb els sprites Bullet' i Enemy' reals) i passarem a comprovacions de cercles avançades, fixació mixta, difusió de rayos anti-túnel, col·lisió de grau de producció **Pixel-Perfect **Col·lisió per a mòbils i colisions Pixel-Perfect, optimitzats per a mòbils i colisions **Zillo. Supervivència del GC.


1. La fundació: una jerarquia simple de "Sprite" de monojoc

Abans de detectar col·lisions, necessitem entitats de joc netes. A MonoGame, una entitat té fonamentalment una posició, una textura i un rectangle delimitant.

A continuació es mostra l'arquitectura d'entitats de referència utilitzada als nostres jocs:

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

Ara creem classes concretes de "Jugador", "Enemic" i "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;
    }
}

Amb aquestes entitats al seu lloc, anem a explorar com detectar col·lisions entre elles, partint del mètode més senzill.


2. Nivell 1: la col·lisió més senzilla: "Rectangle.Intersects" (AABB)

La comprovació de col·lisions 2D més fonamental a MonoGame és la prova Axis-Aligned Bounding Box (AABB). El terme "alineat amb eixos" significa simplement que les vores del rectangle són completament paral·leles als eixos \(X\) i \(Y\) de la pantalla (sense rotació).

MonoGame proporciona un mètode ràpid i integrat: Rectangle.Intersects(Rectangle value).

Com funciona Rectangle.Intersects Under the Hood

Sota la superfície, MonoGame executa quatre comparacions de nombres enters:

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

Si es compleixen les quatre condicions, els rectangles se superposen. Si fins i tot una condició falla, un eix buit els separa i no hi ha cap col·lisió possible.

Codi de joc real: Bullet vs. Enemy a Game1.Update

A continuació s'explica com comproveu les col·lisions entre una llista de bales actives i una llista d'enemics actius dins del vostre bucle principal "Actualització" 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);
    }
}

Consell de rendiment: Tingueu en compte que iterem cap enrere (for (int i = list.Count - 1; i >= 0; i--))! Si utilitzeu foreach i intenteu cridar _bullets.Remove(bullet), C# llança una InvalidOperationException: la col·lecció s'ha modificat. Iterar cap enrere permet l'eliminació segura dels elements sense problemes de reindexació de la memòria.

The Arcade Secret: "Fair Hitboxes" mitjançant Inflate

En jocs retro com Blocked: Pixel Panzer, les textures dels sprites solen incloure marges transparents o puntes d'antena. Si el tanc del jugador explota perquè una bala ha tocat una cantonada transparent buida de la seva textura, el jugador se sentirà enganyat.

Perquè la col·lisió se senti sensible i justa, els jocs utilitzen un Hitbox més petit dins de l'esprite amb `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. Nivell 2: col·lisió cercle a cercle (immunitat rotacional)

Els rectangles funcionen molt bé per a blocs estàtics i rajoles de quadrícula, però fallen quan els sprites giren. Quan una nau espacial no quadrada gira a Paint Trek, un quadre delimitador alineat amb l'eix s'ha d'expandir per tancar les cantonades giratòries, donant lloc a frustrants "colisions fantasma" a l'aire buit.

Per als asteroides circulars, els orbes d'energia d'origen i les naus espacials rotatives, els Bounding Circles són la solució ideal.

La trampa d'arrel quadrada

Dos cercles xoquen quan la distància entre els seus centres és menor o igual a la suma dels seus radis:

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

Al codi, per calcular la distància euclidiana s'utilitza "Math.Sqrt" (o "Vector2.Distance"). Tanmateix, calcular arrels quadrades en un bucle amb 200 projectils crema centenars de cicles de CPU innecessaris!

En comparar la distància quadrada amb la suma del radi quadrat, eliminem completament l'arrel quadrada:

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

Implementació de MonoGame: Cercle vs. Cercle

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

Ara integreu-ho directament en una entitat:

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

Arrels quadrades zero, immune a la rotació dels sprites i ràpid com un llamp.


4. Nivell 3: formes mixtes: cercle contra caixa (MathHelper.Clamp)

Què passa quan una nau espacial circular a Paint Trek navega per un laberint reduït de barreres de defensa rectangulars o quan una bala rodona colpeja un bloc quadrat a Blocked: Pixel Panzer?

Necessitem col·lisió Cercle vs. Rectangle.

L'algoritme de fixació

L'estratègia és trobar el punt del rectangle més proper al centre del cercle i després comprovar si la distància des del punt més proper al centre és menor que el radi del cercle.

MathHelper.Clamp de MonoGame fa que això sigui 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);
    }
}

Ara podeu provar els escuts dels jugadors contra maons rectangulars amb zero assignacions i alta precisió!


5. Nivell 4: Detecció de col·lisió contínua (CCD) i raigs escombrats

Alguna vegada has disparat una ronda de franctirador d'hiper-velocitat o un làser de canó de ferrocarril al teu joc, només per veure com la bala passa màgicament directament a través d'un vaixell enemic prim sense causar danys?

Aquest error es coneix com a túnel.

Com que els jocs discrets s'actualitzen en passos de temps (\(\Delta t = 16,6\text{ms}\) a 60 FPS), un objecte que es mou a 1.800 píxels per segon viatja 30 píxels en un sol fotograma. Si el casc de l'enemic només té 15 píxels de gruix, la bala estava davant de l'enemic al fotograma 1 i completament darrere de l'enemic al fotograma 2.

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

La solució: segment escombrat vs. caixa (mètode de llosa)

En lloc de provar un únic punt, provem tot el segment de línia que connecta la posició de la vinyeta al fotograma 1 ('previousPosition') amb el fotograma 2 ('currentPosition').

Aquí teniu el mètode d'intersecció de lloses de radicasting de producció del nostre títol complementari 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;
    }
}

A Bloquejat: Pixel Panzer, l'habilitat Laser Beam contínua del jugador utilitza aquest raycast exacte per tallar files de blocs descendents sense perdre cap col·lisió.


6. Nivell 5: Detecció de col·lisions perfecta per a píxels de grau de producció

Ara arribem al nivell definitiu de precisió 2D: Col·lisió Pixel-Perfect.

En un tirador de tancs retro o un combat de gossos amb naus espacials, les formes irregulars (canons de tancs, ales, cabines de la cabina) estan envoltades de píxels transparents a la textura dels sprites. Quan un míssil enemic colpeja aquest espai transparent, els jugadors s'adonen immediatament.

La col·lisió perfecta de píxels inspecciona els canals alfa (transparència) reals de les textures superposades. Si dos píxels no transparents se superposen a la mateixa coordenada mundial, s'ha produït un veritable cop físic.

L'error fatal: GetData dins de Update()

Molts tutorials indiquen als principiants que facin això:

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

La crida a Texture2D.GetData() durant el temps d'execució obliga a la CPU a esperar que la canalització de la GPU s'esgoti, recuperant la memòria de textures sobre el bus. Si feu això per a desenes de sprites cada fotograma, el vostre joc va de 60 a 5 FPS i activarà els ANR d'Android immediatament!

La solució provada en batalla de Bloquejat: Pixel Panzer

A Bloquejat: Pixel Panzer, el nostre Sprite.cs implementa una rutina IntersectsPixel ultra optimitzada construïda al voltant de 5 regles crucials:

  1. Memòria cau de matriu de colors estàtic: GetData es crida exactament una vegada quan es carrega la textura, i s'emmagatzema en un Diccionari<Texture2D, Color[]>.
  2. Guàrdia de sortida anticipada de l'AABB: si Bounds.Intersects(other.Bounds) és fals, rescatem immediatament. El 99% de les comprovacions s'eliminen abans d'inspeccionar un sol píxel.
  3. Suport de l'Atles de Textures i SourceRectangle: Gestiona els sprites empaquetats en atles de textures mitjançant desplaçaments de rectangles d'origen.
  4. Finestra de superposició calculada: només fem un bucle sobre el rectangle d'intersecció exacte entre ambdós sprites (Math.Max(a.Top, b.Top), etc.).
  5. ** Curtcircuit alfa:** si el píxel de l'Sprite A és transparent (A <= 20), l'Sprite B s'omet completament.
  6. Property Hoisting: Accedint a propietats virtuals (Bounds) en bucles imbricats crea milers de còpies d'estructura. Les emmagatzemem en variables de pila locals abans d'entrar al bucle.

Aquí teniu el codi complet i llest per a la producció:

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

Aquesta rutina s'executa en menys de 0,1 mil·lisegons en sistemes d'escriptori i maquinari Android moderns.


7. Nivell 6: quadrícula espacial de fase àmplia i GC mòbil amb assignació zero

Imagineu que el vostre joc té 100 blocs, 60 bales i 20 enemics. Si proveu totes les entitats amb totes les altres entitats utilitzant bucles "for" imbricats, feu el següent:

\(\frac{180 \times 179}{2} = 16.110 \text{ comprovacions per fotograma!}\)

A 60 FPS, són gairebé 1.000.000 de comprovacions de col·lisió cada segon.

Per escalar el vostre joc, heu d'implementar la partició espacial de fase ampla.

La graella de hash espacial uniforme

Dividim la pantalla en una quadrícula 2D de cel·les uniformes (a Bloquejat: Pixel Panzer, cada cel·la té 150 \(\times 150\) píxels). Una entitat només prova les col·lisions amb altres entitats que resideixen a les mateixes cel·les de la quadrícula.

El problema de la memòria mòbil: GC Thrashing

Si el vostre SpatialGrid crea nova List<Point>() o new List<Block>() a cada fotograma, assigneu megabytes d'escombraries cada minut al munt gestionat.

Al temps d'execució Mono d'Android, això desencadena col·leccions d'escombraries Gen-0 freqüents, provocant: `temps d'execució mono: contenció de bloqueig natiu (mono_class_is_subclass_)'

El joc tartamudeja i Google Play marca el teu joc amb avisos ANR!

La solució: Buffers Scratch reutilitzables

Aquí teniu la Grilla de hash espacial d'assignació zero de `CollisionManager.cs' a Bloquejat: 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 reutilitzar _scratchCells i _scratchCheckedEntities, tot el pas de col·lisió s'executa amb 0 bytes d'assignació de pila per fotograma.


8. Taula de comparació resum

Tècnica Cost matemàtic Suport rotacional Túneles segurs? Millor cas d'ús a MonoGame
AABB (Rectangle.Intersectes) Ultra-ràpid (~4 comparacions de nombres enters) No No Blocs de quadrícula, fase ampla de bala, IU
Cercle (DistanceSquared) Molt ràpid (3 muls, 0 sqrt) Sí (invariant) No Vaixells rodons, boles de foc, orbes d'energia
Circle vs Box ('Pinça') Ràpid (Local MathHelper.Clamp) Sí No Jugador circular navegant per parets atapeïdes
Raig escombrat (LCD de llosa) Moderat (Raycast paramètric) Sí Sí Bales de franctirador ràpids, canons de ferrocarril, làsers
Píxel perfecte (IntersectsPixel) Selectiu (Subrect. restringit) Sí No Contorns de sprite irregulars, hitboxes justos
Graella espacial (Zero-GC) Fase ampla (\(O(N^2) \a O(N)\)) N/A N/A Ones denses, inferns de bala, Android 60 FPS

Vitrines de producció del món real: Arar Games

Aquestes arquitectures de col·lisió no són experiments teòrics: són la veritable base d'enginyeria que alimenta els nostres títols comercials:

  • Bloquejat: Pixel Panzer: El nostre joc arcade de supervivència de tancs retro a Google Play i Microsoft Store. Compta amb el nostre sistema de col·lisió de dos nivells complet: una quadrícula de hash espacial d'assignació zero que filtra els blocs que cauen i una comprovació de contacte perfecta per a píxels per a tancs, avions de caça, cartutxos de torreta i munició elemental ColorWheel.
  • Paint Trek: El nostre tret espacial trepidant que inclou col·lisió de cercles rotatius, emissió de raigs contínua i sistemes de defensa de míssils de gran volum.

Conclusió i propers passos

MonoGame us ofereix el poder de dissenyar una detecció de col·lisions que s'ajusti perfectament a les necessitats del vostre joc. Mitjançant les costoses comprovacions Pixel-Perfect darrere de les proves ràpides AABB, utilitzant Distàncies quadrades per a cercles i eliminant la recollida d'escombraries amb Buffers Scratch Reutilitzables, podeu oferir un rendiment de 60/120 FPS fluid per a la consola tant en plataformes d'escriptori com mòbils.

Fes una ullada als nostres jocs a les botigues d'aplicacions per veure aquests sistemes de col·lisió en acció i comença a implementar aquests patrons als teus propis projectes MonoGame avui mateix!


Enllaços i recursos de la botiga


Paraules clau i hashtags de SEO

Paraules clau: Detecció de col·lisions en 2D de MonoGame, desenvolupament de jocs C#, Rectangle. Intersects MonoGame, col·lisió perfecta de píxels C#, quadrícula de hash espacial MonoGame, col·lisió contínua de raigs escombrats, optimització del rendiment del joc independent, bucle de joc d'assignació zero, optimització d'Android MonoGame, Arar Games, Pa Blocked Trek Pixel Panzer.

#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #GameDev``#BlockedPixels``#GameDev #Programació de jocs




Hiç yorum yok:

Yorum Gönder