27 Eylül 2026 Pazar

2D-kollisionsdetektion i MonoGame: Fra grundlæggende rektangler til pixel-perfekte systemer med nultildeling




2D-kollisionsdetektion i MonoGame: Fra grundlæggende rektangler til pixel-perfekte systemer med nultildeling

Når du bygger 2D-spil i visuelle motorer som Unity eller Godot, føles kollisionsdetektion ofte som en række af inspektørafkrydsningsfelter: du smækker på en BoxCollider2D eller CircleCollider2D, vedhæfter en Rigidbody og håber, at det interne fysiktrin ikke hakker på mobile enheder.

I MonoGame og C# har du dog total kontrol. Der er ingen skjult fysik overhead, ingen uønsket rotationsinerti og ingen mystiske skraldesamlinger (GC) spidser, der stjæler dit rammebudget.

På Arar Games, da vi byggede Blocked: Pixel Panzer og Paint Trek, var vores arkadespilsløjfer nødvendige for at behandle hundredvis af højhastighedsfjendtlige kugler, eksploderende murstensgitre, roterende tanktårne, jagerfly-flybys og partikelskjolde ved 60 til 120 FPS på tværs af både Windows-pc'er og Android-enheder. En fysikmotor til generelle formål var udelukket – vi havde brug for en specialbygget, lagdelt kollisionsarkitektur.

I denne omfattende, kodedrevne guide vil vi tage udgangspunkt i det absolutte grundlæggende i MonoGame-kollision (Rectangle.Intersects med ægte Bullet og Enemy sprites) og bygge op til avancerede cirkeltjek, mixed clamping, anti-tunneling raycasts, produktions-grade **Pixel-Zero-Per, All-Fact Collision Pixel-Zero og Grids optimeret til mobil GC-overlevelse.


1. The Foundation: A Simple MonoGame Sprite Hierarki

Før vi opdager kollisioner, har vi brug for rene spilenheder. I MonoGame besidder en enhed grundlæggende en position, en tekstur og et afgrænsende rektangel.

Her er den grundlæggende enhedsarkitektur, der bruges på tværs af vores spil:

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

Lad os nu skabe konkrete "Player", "Enemy" og "Bullet" klasser:

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

Med disse entiteter på plads, lad os undersøge, hvordan man opdager kollisioner mellem dem, startende fra den enkleste metode.


2. Niveau 1: The Simplest Collision – Rectangle.Intersects (AABB)

Det mest grundlæggende 2D-kollisionstjek i MonoGame er Axis-Aligned Bounding Box (AABB)-testen. Udtrykket "akse-justeret" betyder ganske enkelt, at rektanglets kanter er fuldstændig parallelle med skærmens \(X\)- og \(Y\)-akser (ingen rotation).

MonoGame giver en hurtig, indbygget metode: Rectangle.Intersects(Rectangle value).

Hvordan Rectangle.Skærer virker under motorhjelmen

Under overfladen udfører MonoGame fire heltals sammenligninger:

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

Hvis alle fire betingelser er opfyldt, overlapper rektanglerne. Hvis selv en betingelse mislykkes, adskiller en tom akse dem, og ingen kollision er mulig.

Ægte gameplay-kode: 'Bullet' vs. 'Enemy' i 'Game1.Update'

Sådan kontrollerer du kollisioner mellem en liste over aktive kugler og en liste over aktive fjender inde i din primære MonoGame Update-løkke:

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

Tip til ydeevne: Bemærk, at vi itererer baglæns (for (int i = list.Count - 1; i >= 0; i--))! Hvis du bruger foreach og prøver at kalde _bullets.Remove(bullet), kaster C# en InvalidOperationException: Collection was modified. Gentagelse baglæns muliggør sikker fjernelse af elementer uden problemer med genindeksering af hukommelsen.

The Arcade Secret: "Fair Hitboxes" via Inflate

I retrospil som Blocked: Pixel Panzer inkluderer sprite-teksturer ofte gennemsigtige marginer eller antennespidser. Hvis spillerens tank eksploderer, fordi en kugle rørte ved et tomt gennemsigtigt hjørne af dens tekstur, vil spilleren føle sig snydt.

For at få kollisionen til at føles responsiv og retfærdig, bruger spil en mindre Hitbox inde i spriten ved hjælp af '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: Cirkel-til-cirkel-kollision (rotationsimmunitet)

Rektangler fungerer godt til statiske blokke og gitterfliser, men de fejler, når sprites roterer. Når et ikke-firkantet rumskib roterer i Paint Trek, skal en aksejusteret afgrænsningsboks udvide sig for at omslutte de roterende hjørner, hvilket resulterer i frustrerende "fantomkollisioner" i tom luft.

Til cirkulære asteroider, målsøgende energikugler og roterende rumfartøjer er Bounding Circles den ideelle løsning.

Firkantrodsfælden

To cirkler støder sammen, når afstanden mellem deres centre er mindre end eller lig med summen af deres radier:

\(\tekst{Afstand}(C_A, C_B) \le r_A + r_B\)

I kode bruger beregning af euklidisk afstand Math.Sqrt (eller Vector2.Distance). Men at beregne kvadratrødder i en loop med 200 projektiler forbrænder hundredvis af unødvendige CPU-cyklusser!

Ved at sammenligne kvadratafstand med kvadratradiussum eliminerer vi kvadratroden fuldstændigt:

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

MonoGame-implementering: 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);
    }
}

Integrer nu dette direkte i en enhed:

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

Nul kvadratrødder, immun over for sprite-rotation og lynhurtig.


4. Niveau 3: Blandede former – Circle vs. Box (MathHelper.Clamp)

Hvad sker der, når et cirkulært rumskib i Paint Trek navigerer gennem en tæt labyrint af rektangulære forsvarsbarrierer, eller når en rund kugle rammer en firkantet blok i Blocked: Pixel Panzer?

Vi har brug for Cirkel vs. Rektangel kollision.

Spændingsalgoritmen

Strategien er at finde det punkt på rektangelet, der er tættest på cirklens centrum, og derefter teste, om afstanden fra det nærmeste punkt til centrum er mindre end cirklens radius.

MonoGames MathHelper.Clamp gør dette trivielt:

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

Du kan nu teste spillerskjolde mod rektangulære klodser med nul tildelinger og høj nøjagtighed!


5. Niveau 4: Continuous Collision Detection (CCD) & Swept Rays

Har du nogensinde affyret en snigskytterunde med hyperhastighed eller en railgun-laser i dit spil, kun for at se kuglen på magisk vis passere lige gennem et tyndt fjendtligt skib uden at gøre skade?

Denne fejl er kendt som tunneling.

Fordi diskrete spil opdateres i tidstrin (\(\Delta t = 16,6\text{ms}\) ved 60 FPS), bevæger et objekt sig med 1.800 pixels i sekundet 30 pixels i et enkelt billede. Hvis fjendens skrog kun er 15 pixels tykt, var kuglen foran fjenden på Frame 1 og helt bag fjenden på Frame 2.

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

Løsningen: Swept Segment vs. Box (Slab-metode)

I stedet for at teste et enkelt punkt, tester vi hele linjesegmentet, der forbinder kuglens position på Frame 1 (forrige Position) til Frame 2 (currentPosition).

Her er produktions-raycasting slab-skæringsmetoden fra vores ledsagertitel 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;
    }
}

I Blocked: Pixel Panzer bruger spillerens kontinuerlige Laser Beam-færdighed denne nøjagtige raycast til at skære gennem rækker af faldende blokke uden at gå glip af en eneste kollision.


6. Niveau 5: Produktionsgrad Pixel-Perfekt kollisionsdetektion

Nu når vi det ultimative niveau af 2D-nøjagtighed: Pixel-Perfect Collision.

I et retro-tankskydespil eller rumskibsdogkamp er uregelmæssige former (tanktønder, vinger, cockpitcockpits) omgivet af gennemsigtige pixels i sprite-teksturen. Når et fjendtligt missil rammer det gennemsigtige rum, bemærker spillerne det med det samme.

Pixel-perfekt kollision inspicerer de faktiske alfa-kanaler (gennemsigtighed) af de overlappende teksturer. Hvis to ikke-gennemsigtige pixels overlapper på den samme verdenskoordinat, er der opstået et ægte fysisk hit.

Den fatale fejl: GetData inde i Update()

Mange tutorials instruerer begyndere til at gøre dette:

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

Kaldning af Texture2D.GetData() under kørsel tvinger CPU'en til at vente på, at GPU-pipelinen skylles ud, hvilket trækker teksturhukommelsen tilbage over bussen. Hvis du gør dette for snesevis af sprites, vil hver frame fylde dit spil fra 60 FPS til 5 FPS og udløse Android ANR'er med det samme!

Den kamptestede løsning fra Blocked: Pixel Panzer

I Blocked: Pixel Panzer implementerer vores 'Sprite.cs' en ultraoptimeret 'IntersectsPixel'-rutine bygget op omkring 5 afgørende regler:

  1. Static Color Array Cache: GetData kaldes nøjagtigt én gang, når teksturen indlæses, og gemmes i en Dictionary<Texture2D, Color[]>.
  2. AABB Early Exit Guard: Hvis Bounds.Intersects(other.Bounds) er falsk, redder vi straks. 99 % af kontrollerne er elimineret før inspektion af en enkelt pixel.
  3. Teksturatlas & 'SourceRectangle'-understøttelse: Håndterer sprites pakket ind i teksturatlas ved hjælp af kilderektangelforskydninger.
  4. Beregnet overlapningsvindue: Vi sløjfer kun over det nøjagtige skæringsrektangel mellem begge sprites (Math.Max(a.Top, b.Top) osv.).
  5. Alpha Short-Cuiting: Hvis Sprite A's pixel er transparent (A <= 20), springes Sprite B helt over.
  6. Property Hoisting: Adgang til virtuelle egenskaber ('Bounds') i indlejrede loops skaber tusindvis af struct-kopier. Vi gemmer dem i lokale stakvariabler, før vi går ind i løkken.

Her er den komplette, produktionsklare kode:

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

Denne rutine udføres på under 0,1 millisekunder på moderne Android-hardware og desktop-systemer.


7. Niveau 6: Broad-Phase Spatial Grid & Zero-Allocation Mobile GC

Forestil dig, at dit spil har 100 blokke, 60 kugler og 20 fjender. Hvis du tester hver enhed mod hver anden enhed ved hjælp af indlejrede 'for'-løkker, udfører du:

\(\frac{180 \times 179}{2} = 16.110 \text{ checks pr. frame!}\)

Ved 60 FPS er det næsten 1.000.000 kollisionstjek hvert sekund.

For at skalere dit spil skal du implementere Bred-Phase Spatial Partitioning.

Det ensartede rumlige hash-gitter

Vi opdeler skærmen i et 2D-gitter af ensartede celler (i Blocked: Pixel Panzer er hver celle \(150 x 150\) pixels). En enhed tester kun kollisioner mod andre enheder, der ligger i de samme gitterceller.

Mobilhukommelsesproblemet: GC Thrashing

Hvis dit SpatialGrid opretter ny liste<Punkt>() eller ny liste<Blok>() hver frame, allokerer du megabytes affald hvert minut på den administrerede heap.

På Androids Mono-runtime udløser dette hyppige Gen-0-affaldsindsamlinger, hvilket forårsager: "mono runtime: Native lock-konflikt (mono_class_is_subclass_)".

Spillet hakker, og Google Play markerer dit spil med ANR-advarsler!

Løsningen: Genanvendelige skrabebuffere

Her er Zero-Allocation Spatial Hash Grid fra CollisionManager.cs i Blocked: 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);
                            }
                        }
                    }
                }
            }
        }
    }
}

Ved at genbruge _scratchCells og _scratchCheckedEntities, kører hele kollisionstrinnet med 0 bytes heap-allokering pr. frame.


8. Sammenfattende sammenligningstabel

Teknik Matematiske omkostninger Rotationsstøtte Sikker tunnelkørsel? Best Use Case i MonoGame
AABB ('Rektangel.Skærer') Ultrahurtig (~4 heltal sammenligninger) Nej Nej Gitterblokke, kuglebredfase, UI
Cirkel (DistanceSquared) Meget hurtig (3 muls, 0 sqrt) Ja (Invariant) Nej Runde skibe, ildkugler, energikugler
Circle vs Box (Clamp) Hurtig (Local MathHelper.Clamp) Ja Nej Cirkulær spiller navigerer tætte vægge
Swept Ray (Slab CCD) Moderat (Parametrisk Raycast) Ja Ja Hurtige snigskyttekugler, railguns, lasere
Pixel-Perfect (Skæringspixel) Selektiv (Restricted Sub-rect) Ja Nej Uregelmæssige spritekonturer, fair hitboxes
Rumligt gitter (Nul-GC) Bredfase (\(O(N^2) \to O(N)\)) N/A N/A Tætte bølger, kuglehelvede, Android 60 FPS

Real-World Production Showcases: Arar Games

Disse kollisionsarkitekturer er ikke teoretiske eksperimenter – de er det egentlige ingeniørgrundlag, der driver vores kommercielt udgivne titler:

  • Blokeret: Pixel Panzer: Vores retro-tankoverlevelsesarkadespil på Google Play og Microsoft Store. Den byder på vores komplette to-tiers kollisionssystem: et nul-allokering rumligt hash-gitter, der filtrerer faldende blokke, og pixel-perfekt kontaktkontrol for kampvogne, jagerfly, tårnskaller og ColorWheel elementær ammunition.
  • Paint Trek: Vores hurtige rumskyder med roterende cirkelkollision, kontinuerlig raycasting og swept-volume missilforsvarssystemer.

Konklusion og næste trin

MonoGame giver dig kraften til at designe kollisionsdetektion, der passer perfekt til dit spils behov. Ved at lukke dyre Pixel-Perfect-tjek bag hurtige AABB-tests, bruge Squared Distances til cirkler og eliminere affaldsopsamling med Genanvendelige Scratch-buffere, kan du levere konsolglat 60/120 FPS-ydeevne på både desktop- og mobilplatforme.

Tjek vores spil i app-butikkerne for at se disse kollisionssystemer i aktion, og begynd at implementere disse mønstre i dine egne MonoGame-projekter i dag!



SEO søgeord og Hashtags

Søgeord: MonoGame 2D-kollisionsdetektion, C# spiludvikling, Rectangle.Skærer MonoGame, pixel perfekt kollision C#, spatial hash-gitter MonoGame, swept ray continuous collision, indie-spilydelsesoptimering, nulallokeringsspilsløjfe, Android MonoGame-optimering, Blocked, Arzerzer-spil.

#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixelPanzer `##GameGame``




Hiç yorum yok:

Yorum Gönder