27 Eylül 2026 Pazar

2D-botsingsdetectie in MonoGame: van eenvoudige rechthoeken tot pixel-perfecte systemen zonder toewijzing




2D-botsingsdetectie in MonoGame: van eenvoudige rechthoeken tot pixel-perfecte systemen zonder toewijzing

Bij het bouwen van 2D-games in visuele engine zoals Unity of Godot voelt botsingsdetectie vaak aan als een reeks controlevakjes: je klikt op een BoxCollider2D of CircleCollider2D, koppelt een Rigidbody en hoopt dat de interne fysica-stap niet hapert op mobiele apparaten.

In MonoGame en C# heb je echter de volledige controle. Er is geen verborgen fysica boven het hoofd, geen ongewenste rotatietraagheid en geen mysterieuze garbage collection (GC)-pieken die je framebudget stelen.

Bij Arar Games, toen we Blocked: Pixel Panzer en Paint Trek bouwden, moesten onze arcadespelloops honderden snelle vijandelijke kogels, exploderende baksteenroosters, roterende tankkoepels, straaljagers en deeltjesschilden verwerken met 60 tot 120 FPS op zowel Windows-pc's als Android-apparaten. Van een fysica-engine voor algemeen gebruik was geen sprake; we hadden een speciaal gebouwde, gelaagde botsingsarchitectuur nodig.

In deze uitgebreide, codegestuurde gids gaan we uit van de absolute basisprincipes van MonoGame-botsing ('Rectangle.Intersects' met echte 'Bullet'- en 'Enemy'-sprites) en bouwen we op naar geavanceerde cirkelcontroles, gemengde klemming, anti-tunneling raycasts, Pixel-Perfect-botsing van productiekwaliteit en Zero-Allocation Spatial Grids geoptimaliseerd voor mobiele GC-overleving.


1. De basis: een eenvoudige MonoGame Sprite-hiërarchie

Voordat we botsingen kunnen detecteren, hebben we schone game-entiteiten nodig. In MonoGame bezit een entiteit fundamenteel een positie, een textuur en een begrenzende rechthoek.

Hier is de basisentiteitsarchitectuur die in onze games wordt gebruikt:

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

Laten we nu concrete klassen ‘Player’, ‘Enemy’ en ‘Bullet’ maken:

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

Nu deze entiteiten aanwezig zijn, gaan we onderzoeken hoe we botsingen tussen deze entiteiten kunnen detecteren, te beginnen met de eenvoudigste methode.


2. Niveau 1: de eenvoudigste botsing – Rectangle.Intersects (AABB)

De meest fundamentele 2D-botsingscontrole in MonoGame is de Axis-Aligned Bounding Box (AABB)-test. De term "as-uitgelijnd" betekent eenvoudigweg dat de randen van de rechthoek volledig evenwijdig zijn aan de \(X\)- en \(Y\)-assen van het scherm (geen rotatie).

MonoGame biedt een snelle, ingebouwde methode: Rectangle.Intersects(Rechthoekwaarde).

Hoe Rectangle.Intersects onder de motorkap werkt

Onder de oppervlakte voert MonoGame vier vergelijkingen van gehele getallen uit:

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

Als aan alle vier de voorwaarden is voldaan, overlappen de rechthoeken elkaar. Als zelfs maar één voorwaarde mislukt, worden ze gescheiden door een lege as en is er geen botsing mogelijk.

Echte gameplay-code: Bullet versus Enemy in Game1.Update

Hier ziet u hoe u botsingen controleert tussen een lijst met actieve kogels en een lijst met actieve vijanden in uw hoofdlus van MonoGame Update:

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

Prestatietip: Merk op dat we achteruit herhalen (for (int i = list.Count - 1; i >= 0; i--))! Als u foreach gebruikt en _bullets.Remove(bullet) probeert aan te roepen, genereert C# een InvalidOperationException: Collection was gewijzigd. Door achterwaarts te itereren, kunnen elementen veilig worden verwijderd zonder problemen met het opnieuw indexeren van het geheugen.

Het Arcadegeheim: "Eerlijke Hitboxes" via Inflate

In retrogames zoals Blocked: Pixel Panzer bevatten sprite-texturen vaak transparante marges of antennepieken. Als de tank van de speler ontploft omdat een kogel een lege, transparante hoek van de structuur raakt, voelt de speler zich bedrogen.

Om een ​​botsing responsief en eerlijk te laten aanvoelen, gebruiken games een kleinere Hitbox in de sprite met behulp van 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: botsing van cirkel tot cirkel (rotatie-immuniteit)

Rechthoeken werken prima voor statische blokken en rastertegels, maar mislukken als sprites roteren. Wanneer een niet-vierkant ruimteschip roteert in Paint Trek, moet een op de as uitgelijnd grenskader uitzetten om de draaiende hoeken te omsluiten, wat resulteert in frustrerende "fantoombotsingen" in lege lucht.

Voor cirkelvormige asteroïden, energiebollen en roterende ruimtevaartuigen zijn Bounding Circles de ideale oplossing.

De vierkantswortelval

Twee cirkels botsen wanneer de afstand tussen hun middelpunten kleiner is dan of gelijk is aan de som van hun stralen:

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

In code wordt voor het berekenen van de Euclidische afstand gebruik gemaakt van Math.Sqrt (of Vector2.Distance). Het berekenen van vierkantswortels in een lus met 200 projectielen verbrandt echter honderden onnodige CPU-cycli!

Door de kwadraatafstand te vergelijken met de kwadraatradiussom, elimineren we de vierkantswortel volledig:

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

MonoGame-implementatie: Cirkel versus Cirkel

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

Integreer dit nu rechtstreeks in een entiteit:

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 vierkantswortels, immuun voor sprite-rotatie en razendsnel.


4. Niveau 3: Gemengde vormen – Cirkel versus doos (MathHelper.Clamp)

Wat gebeurt er als een rond ruimteschip in Paint Trek door een krap doolhof van rechthoekige verdedigingsbarrières navigeert, of wanneer een ronde kogel een vierkant blok raakt in Blocked: Pixel Panzer?

We hebben een Cirkel vs. Rechthoek-botsing nodig.

Het klemalgoritme

De strategie is om het punt op de rechthoek te vinden dat zich het dichtst bij het middelpunt van de cirkel bevindt, en vervolgens te testen of de afstand van dat dichtstbijzijnde punt tot het middelpunt kleiner is dan de straal van de cirkel.

MonoGame's MathHelper.Clamp maakt dit triviaal:

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

Je kunt nu spelersschilden testen tegen rechthoekige stenen zonder toewijzingen en met hoge nauwkeurigheid!


5. Niveau 4: Continue botsingsdetectie (CCD) en geveegde stralen

Heb je ooit een hypersnelle sluipschutter of een railgun-laser afgevuurd in je spel, om vervolgens te zien hoe de kogel op magische wijze dwars door een dun vijandelijk schip ging zonder schade aan te richten?

Deze bug staat bekend als tunneling.

Omdat afzonderlijke games in tijdstappen worden bijgewerkt (\(\Delta t = 16,6\text{ms}\) bij 60 FPS), verplaatst een object dat met een snelheid van 1800 pixels per seconde beweegt 30 pixels in één frame. Als de romp van de vijand slechts 15 pixels dik is, bevond de kogel zich vóór de vijand op Frame 1 en volledig achter de vijand op Frame 2.

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

De oplossing: geveegd segment versus doos (plaatmethode)

In plaats van een enkel punt te testen, testen we het hele lijnsegment dat de positie van de kogel op Frame 1 (previousPosition) verbindt met Frame 2 (currentPosition).

Hier is de productie-raycasting-methode voor plaatintersectie uit onze begeleidende titel 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;
    }
}

In Blocked: Pixel Panzer gebruikt de continue Laser Beam-vaardigheid van de speler deze exacte raycast om door rijen aflopende blokken te snijden zonder ook maar één botsing te missen.


6. Niveau 5: Pixel-perfecte botsingsdetectie van productiekwaliteit

Nu bereiken we het ultieme niveau van 2D-nauwkeurigheid: Pixel-Perfect Collision.

In een retro tankschieter of ruimteschipluchtgevecht worden onregelmatige vormen (tanklopen, vleugels, cockpitcockpits) omgeven door transparante pixels in de sprite-textuur. Wanneer een vijandelijke raket die transparante ruimte raakt, merken spelers dit onmiddellijk.

Pixel-perfecte botsing inspecteert de daadwerkelijke alfakanalen (transparantie) van de overlappende texturen. Als twee niet-transparante pixels elkaar overlappen op dezelfde wereldcoördinaat, is er sprake van een echte fysieke treffer.

De fatale fout: GetData in Update()

Veel tutorials instrueren beginners om dit te doen:

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

Het aanroepen van Texture2D.GetData() tijdens runtime dwingt de CPU te wachten tot de GPU-pijplijn leeg is, waardoor textuurgeheugen terug over de bus wordt getrokken. Als je dit voor tientallen sprites per frame doet, gaat je game van 60 FPS naar 5 FPS en worden Android ANR's onmiddellijk geactiveerd!

De beproefde oplossing van Geblokkeerd: Pixel Panzer

In Blocked: Pixel Panzer implementeert onze Sprite.cs een ultrageoptimaliseerde IntersectsPixel-routine die is opgebouwd rond 5 cruciale regels:

  1. Statische Color Array Cache: GetData wordt precies één keer aangeroepen wanneer de textuur wordt geladen, en opgeslagen in een Dictionary<Texture2D, Color[]>.
  2. AABB Early Exit Guard: Als Bounds.Intersects(other.Bounds) onwaar is, redden we ons onmiddellijk. 99% van de controles wordt geëlimineerd voordat ook maar één pixel wordt geïnspecteerd.
  3. Textuuratlas en SourceRectangle-ondersteuning: Verwerkt sprites die in textuuratlassen zijn verpakt met behulp van bronrechthoek-offsets.
  4. Berekend overlapvenster: We doorlopen alleen de exacte snijrechthoek tussen beide sprites (Math.Max(a.Top, b.Top), enz.).
  5. Alfakortsluiting: Als de pixel van Sprite A transparant is (A <= 20), wordt Sprite B volledig overgeslagen.
  6. Eigenschappen hijsen: Toegang tot virtuele eigenschappen ("Grenzen") in geneste lussen creëert duizenden struct-kopieën. We slaan ze op in lokale stapelvariabelen voordat ze in de lus terechtkomen.

Hier is de volledige, productieklare code:

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

Deze routine wordt in minder dan 0,1 milliseconden uitgevoerd op moderne Android-hardware en desktopsystemen.


7. Niveau 6: Ruimtelijk raster in brede fase en mobiele GC met nultoewijzing

Stel je voor dat je spel 100 blokken, 60 kogels en 20 vijanden bevat. Als je elke entiteit met elke andere entiteit test met behulp van geneste for-lussen, voer je het volgende uit:

\(\frac{180 \times 179}{2} = 16.110 \text{ controles per frame!}\)

Bij 60 FPS zijn dat bijna 1.000.000 botsingscontroles per seconde.

Om je spel te schalen, moet je Broad-Phase Spatial Partitioning implementeren.

Het uniforme ruimtelijke hashraster

We verdelen het scherm in een 2D-raster van uniforme cellen (in Geblokkeerd: Pixel Panzer is elke cel \(150 \maal 150\) pixels). Een entiteit test alleen botsingen met andere entiteiten die zich in dezelfde rastercellen bevinden.

Het mobiele geheugenprobleem: GC Thrashing

Als uw SpatialGrid elk frame nieuwe Lijst<Punt>() of nieuwe Lijst<Blok>() creëert, wijst u elke minuut megabytes aan rommel toe aan de beheerde heap.

Op de Mono-runtime van Android activeert dit frequente Gen-0 Garbage Collections, waardoor: mono runtime: Native lock-conflict (mono_class_is_subclass_)

Het spel stottert en Google Play markeert je spel met ANR-waarschuwingen!

De oplossing: herbruikbare krasbuffers

Hier is het Zero-Allocation Spatial Hash Grid van CollisionManager.cs in Geblokkeerd: 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);
                            }
                        }
                    }
                }
            }
        }
    }
}

Door _scratchCells en _scratchCheckedEntities opnieuw te gebruiken, wordt de gehele botsingsstap uitgevoerd met 0 bytes aan heaptoewijzing per frame.


8. Samenvattende vergelijkingstabel

Techniek Wiskundige kosten Rotatieondersteuning Veilig tunnelen? Beste gebruiksscenario in MonoGame
AABB (Rechthoek.Intersects) Ultrasnel (~4 vergelijkingen van gehele getallen) Nee Nee Rasterblokken, brede fase, UI
Cirkel (Afstandkwadraat) Zeer snel (3 muls, 0 sqrt) Ja (invariant) Nee Ronde schepen, vuurballen, energiebollen
Cirkel versus doos (Klem) Snel (lokale MathHelper.Clamp) Ja Nee Circulaire speler die door strakke muren navigeert
Swept Ray (plaat-CCD) Matig (parametrische straal) Ja Ja Snelle sluipschutterkogels, railguns, lasers
Pixel-Perfect (IntersectsPixel) Selectief (beperkt subrecht) Ja Nee Onregelmatige sprite-contouren, eerlijke hitboxen
Ruimtelijk raster (Zero-GC) Brede fase (\(O(N^2) \to O(N)\)) N.v.t. N.v.t. Dichte golven, kogelhellen, Android 60 FPS

Real-World productieshowcases: Arar Games

Deze botsingsarchitecturen zijn geen theoretische experimenten; ze vormen de echte technische basis die ten grondslag ligt aan onze commercieel uitgebrachte titels:

  • Geblokkeerd: Pixel Panzer: Ons retro tankoverlevingsarcadespel op Google Play en Microsoft Store. Het beschikt over ons complete botsingssysteem met twee niveaus: een ruimtelijk hash-raster met nultoewijzing dat vallende blokken filtert, en pixel-perfecte contactcontrole voor tanks, straaljagers, geschutskoepels en elementaire ColorWheel-munitie.
  • Paint Trek: Onze snelle space shooter met roterende cirkelbotsing, continue raycasting en raketafweersystemen met een groot volume.

Conclusie en volgende stappen

MonoGame geeft je de mogelijkheid om botsingsdetectie te ontwerpen die perfect aansluit bij de behoeften van je game. Door dure Pixel-Perfect-controles achter snelle AABB-tests te plaatsen, kwadraatafstanden voor cirkels te gebruiken en afvalverzameling te elimineren met Herbruikbare Scratch Buffers, kunt u console-vloeiende 60/120 FPS-prestaties leveren op zowel desktop- als mobiele platforms.

Bekijk onze games in de appstores om deze botsingssystemen in actie te zien, en begin vandaag nog met het implementeren van deze patronen in je eigen MonoGame-projecten!



SEO-trefwoorden en hashtags

Trefwoorden: MonoGame 2D botsingsdetectie, C# game-ontwikkeling, Rectangle.Intersects MonoGame, pixel perfecte botsing C#, ruimtelijk hash-raster MonoGame, continue botsing met geveegde straal, optimalisatie van indiegame-prestaties, gameloop zonder toewijzing, Android MonoGame-optimalisatie, 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