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 uforeachgebruikt en_bullets.Remove(bullet)probeert aan te roepen, genereert C# eenInvalidOperationException: 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:
- Statische Color Array Cache:
GetDatawordt precies één keer aangeroepen wanneer de textuur wordt geladen, en opgeslagen in eenDictionary<Texture2D, Color[]>. - 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. - Textuuratlas en
SourceRectangle-ondersteuning: Verwerkt sprites die in textuuratlassen zijn verpakt met behulp van bronrechthoek-offsets. - Berekend overlapvenster: We doorlopen alleen de exacte snijrechthoek tussen beide sprites (
Math.Max(a.Top, b.Top), enz.). - Alfakortsluiting: Als de pixel van Sprite A transparant is (
A <= 20), wordt Sprite B volledig overgeslagen. - 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!
Winkellinks en bronnen
- Geblokkeerd: Pixel Panzer op Google Play: Downloaden voor Android
- Geblokkeerd: Pixel Panzer in Microsoft Store: Downloaden voor Windows
- MonoGame-framework: monogame.net
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