2D-Kollisionserkennung in MonoGame: Von einfachen Rechtecken zu pixelgenauen Systemen ohne Zuordnung
Beim Erstellen von 2D-Spielen in visuellen Engines wie Unity oder Godot fühlt sich die Kollisionserkennung oft wie eine Reihe von Inspektor-Kontrollkästchen an: Sie legen einen „BoxCollider2D“ oder „CircleCollider2D“ an, hängen einen „Rigidbody“ an und hoffen, dass der interne Physikschritt auf Mobilgeräten nicht stottert.
In MonoGame und C# haben Sie jedoch die volle Kontrolle. Es gibt keinen versteckten physikalischen Aufwand, keine unerwünschte Rotationsträgheit und keine mysteriösen Garbage Collection (GC)-Spitzen, die Ihr Rahmenbudget stehlen.
Als wir bei Arar Games Blocked: Pixel Panzer und Paint Trek entwickelten, mussten unsere Arcade-Spielschleifen Hunderte von schnellen feindlichen Kugeln, explodierenden Ziegelgittern, rotierenden Panzertürmen, Vorbeiflügen von Kampfjets und Partikelschilden mit 60 bis 120 FPS auf Windows-PCs und Android-Geräten verarbeiten. Eine universelle Physik-Engine kam nicht in Frage – wir brauchten eine speziell entwickelte, abgestufte Kollisionsarchitektur.
In diesem umfassenden, codegesteuerten Leitfaden beginnen wir mit den absoluten Grundlagen der MonoGame-Kollision („Rectangle.Intersects“ mit echten „Bullet“- und „Enemy“-Sprites) und bauen auf erweiterte Kreisprüfungen, Mixed Clamping, Anti-Tunneling-Raycasts, Pixel-Perfect-Kollision in Produktionsqualität und Zero-Allocation Spatial Grids auf, die für das Überleben auf mobilen GCs optimiert sind.
1. Die Grundlage: Eine einfache MonoGame-„Sprite“-Hierarchie
Bevor wir Kollisionen erkennen können, benötigen wir saubere Spieleinheiten. In MonoGame besitzt eine Entität grundsätzlich eine Position, eine Textur und ein umgrenzendes Rechteck.
Hier ist die grundlegende Entitätsarchitektur, die in unseren Spielen verwendet wird:
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);
}
}
Jetzt erstellen wir konkrete Klassen „Player“, „Enemy“ und „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;
}
}
Wenn diese Entitäten vorhanden sind, wollen wir zunächst mit der einfachsten Methode untersuchen, wie Kollisionen zwischen ihnen erkannt werden können.
2. Ebene 1: Die einfachste Kollision – „Rectangle.Intersects“ (AABB)
Die grundlegendste 2D-Kollisionsprüfung in MonoGame ist der Axis-Aligned Bounding Box (AABB)-Test. Der Begriff „achsenausgerichtet“ bedeutet einfach, dass die Kanten des Rechtecks vollständig parallel zu den Achsen \(X\) und \(Y\) des Bildschirms verlaufen (keine Drehung).
MonoGame bietet eine schnelle, integrierte Methode: „Rectangle.Intersects(Rectangle value)“.
Wie „Rectangle.Intersects“ unter der Haube funktioniert
Unter der Oberfläche führt MonoGame vier Ganzzahlvergleiche durch:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
Wenn alle vier Bedingungen erfüllt sind, überlappen sich die Rechtecke. Wenn auch nur eine Bedingung fehlschlägt, werden sie durch eine leere Achse getrennt und es ist keine Kollision möglich.
Echter Gameplay-Code: „Bullet“ vs. „Enemy“ in „Game1.Update“.
So überprüfen Sie Kollisionen zwischen einer Liste aktiver Kugeln und einer Liste aktiver Feinde in Ihrer Haupt-MonoGame-„Update“-Schleife:
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);
}
}
Leistungstipp: Beachten Sie, dass wir rückwärts iterieren (
for (int i = list.Count - 1; i >= 0; i--))! Wenn Sie „foreach“ verwenden und versuchen, „_bullets.Remove(bullet)“ aufzurufen, löst C# eine „InvalidOperationException: Collection wurde geändert“ aus. Die Rückwärtsiteration ermöglicht das sichere Entfernen von Elementen ohne Probleme bei der Neuindizierung des Speichers.
Das Arcade-Geheimnis: „Fair Hitboxes“ über „Inflate“.
In Retro-Spielen wie Blocked: Pixel Panzer enthalten Sprite-Texturen oft transparente Ränder oder Antennenspitzen. Wenn der Panzer des Spielers explodiert, weil eine Kugel eine leere transparente Ecke seiner Textur berührt hat, fühlt sich der Spieler betrogen.
Damit sich Kollisionen reaktionsschnell und fair anfühlen, verwenden Spiele mithilfe von „Rectangle.Inflate“ eine kleinere Hitbox innerhalb des Sprites:
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. Stufe 2: Kreis-zu-Kreis-Kollision (Rotationsimmunität)
Rechtecke eignen sich hervorragend für statische Blöcke und Rasterkacheln, versagen jedoch, wenn Sprites rotieren. Wenn sich in Paint Trek ein nicht quadratisches Raumschiff dreht, muss sich ein an der Achse ausgerichteter Begrenzungsrahmen ausdehnen, um die sich drehenden Ecken einzuschließen, was zu frustrierenden „Phantomkollisionen“ in der Luft führt.
Für kreisförmige Asteroiden, zielsuchende Energiekugeln und rotierende Raumfahrzeuge sind Begrenzungskreise die ideale Lösung.
Die Quadratwurzelfalle
Zwei Kreise kollidieren, wenn der Abstand zwischen ihren Mittelpunkten kleiner oder gleich der Summe ihrer Radien ist:
\(\text{Abstand}(C_A, C_B) \le r_A + r_B\)
Im Code wird zur Berechnung der euklidischen Distanz „Math.Sqrt“ (oder „Vector2.Distance“) verwendet. Allerdings verbrennt die Berechnung von Quadratwurzeln in einer Schleife mit 200 Projektilen Hunderte unnötiger CPU-Zyklen!
Indem wir den Quadratabstand mit der Quadratradiussumme vergleichen, eliminieren wir die Quadratwurzel vollständig:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
MonoGame-Implementierung: „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);
}
}
Integrieren Sie dies nun direkt in eine Entität:
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);
}
}
Keine Quadratwurzeln, immun gegen Sprite-Rotation und blitzschnell.
4. Level 3: Gemischte Formen – Kreis vs. Box („MathHelper.Clamp“)
Was passiert, wenn ein kreisförmiges Raumschiff in Paint Trek durch ein enges Labyrinth rechteckiger Verteidigungsbarrieren navigiert oder wenn eine runde Kugel in Blocked: Pixel Panzer einen quadratischen Block trifft?
Wir brauchen eine Kreis-gegen-Rechteck-Kollision.
Der Spannalgorithmus
Die Strategie besteht darin, den Punkt auf dem Rechteck zu finden, der dem Kreismittelpunkt am nächsten liegt, und dann zu testen, ob der Abstand von diesem nächstgelegenen Punkt zum Mittelpunkt kleiner als der Kreisradius ist.
„MathHelper.Clamp“ von MonoGame macht dies 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);
}
}
Sie können Spielerschilde jetzt ohne Zuweisungen und mit hoher Genauigkeit gegen rechteckige Steine testen!
5. Ebene 4: Kontinuierliche Kollisionserkennung (CCD) und geschwenkte Strahlen
Haben Sie in Ihrem Spiel schon einmal ein Hochgeschwindigkeits-Scharfschützengeschoss oder einen Railgun-Laser abgefeuert, nur um zu beobachten, wie die Kugel auf magische Weise ein dünnes feindliches Schiff durchdringt, ohne Schaden anzurichten?
Dieser Fehler wird als Tunneling bezeichnet.
Da diskrete Spiele in Zeitschritten aktualisiert werden (\(\Delta t = 16,6\text{ms}\) bei 60 FPS), bewegt sich ein Objekt mit 1.800 Pixeln pro Sekunde 30 Pixel in einem einzelnen Frame. Wenn die gegnerische Hülle nur 15 Pixel dick ist, befand sich die Kugel in Bild 1 vor dem Feind und in Bild 2 vollständig hinter dem Feind.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
Die Lösung: Sweep-Segment vs. Box (Plattenmethode)
Anstatt einen einzelnen Punkt zu testen, testen wir das gesamte Liniensegment, das die Position des Aufzählungszeichens in Frame 1 („ previousPosition“) mit Frame 2 („currentPosition“) verbindet.
Hier ist die Produktions-Raycasting-Plattenschnittmethode aus unserem Begleittitel 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 nutzt die kontinuierliche Laserstrahl-Fertigkeit des Spielers genau diesen Strahl, um Reihen absteigender Blöcke zu durchschneiden, ohne eine einzige Kollision zu verpassen.
6. Stufe 5: Pixelgenaue Kollisionserkennung in Produktionsqualität
Jetzt erreichen wir das ultimative Niveau der 2D-Genauigkeit: Pixel-Perfect Collision.
In einem Retro-Panzer-Shooter oder Raumschiff-Luftkampf werden unregelmäßige Formen (Panzerrohre, Flügel, Cockpit-Cockpits) von transparenten Pixeln in der Sprite-Textur umgeben. Wenn eine feindliche Rakete diesen transparenten Raum trifft, merken es die Spieler sofort.
Bei der pixelgenauen Kollision werden die tatsächlichen Alphakanäle (Transparenzkanäle) der überlappenden Texturen überprüft. Wenn sich zwei nicht transparente Pixel an derselben Weltkoordinate überlappen, liegt ein echter physischer Treffer vor.
Der fatale Fehler: „GetData“ in „Update()“.
Viele Tutorials weisen Anfänger dazu an:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
Der Aufruf von „Texture2D.GetData()“ während der Laufzeit zwingt die CPU, auf das Leeren der GPU-Pipeline zu warten, wodurch der Texturspeicher über den Bus zurückgezogen wird. Wenn Sie dies für Dutzende von Sprites in jedem Frame tun, wird Ihr Spiel von 60 FPS auf 5 FPS gesteigert und Android-ANRs werden sofort ausgelöst!
Die kampferprobte Lösung von Blocked: Pixel Panzer
In Blocked: Pixel Panzer implementiert unser „Sprite.cs“ eine hochoptimierte „IntersectsPixel“-Routine, die auf 5 entscheidenden Regeln basiert:
- Statischer Farbarray-Cache: „GetData“ wird genau einmal aufgerufen, wenn die Textur geladen wird, und in einem „Dictionary<Texture2D, Color[]>“ gespeichert.
- AABB Early Exit Guard: Wenn „Bounds.Intersects(other.Bounds)“ falsch ist, steigen wir sofort aus. 99 % der Kontrollen entfallen, bevor ein einzelnes Pixel geprüft wird.
- Unterstützung für Texturatlas und „SourceRectangle“: Verarbeitet Sprites, die in Texturatlanten gepackt sind, unter Verwendung von Quellrechteck-Offsets.
- Berechnetes Überlappungsfenster: Wir durchlaufen nur das exakte Schnittrechteck zwischen beiden Sprites („Math.Max(a.Top, b.Top)“ usw.).
- Alpha-Kurzschluss: Wenn das Pixel von Sprite A transparent ist („A <= 20“), wird Sprite B vollständig übersprungen.
- Property Hoisting: Durch den Zugriff auf virtuelle Eigenschaften („Bounds“) in verschachtelten Schleifen werden Tausende von Strukturkopien erstellt. Wir speichern sie in lokalen Stapelvariablen, bevor wir in die Schleife eintreten.
Hier ist der vollständige, produktionsbereite 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;
}
}
Diese Routine wird auf moderner Android-Hardware und Desktop-Systemen in weniger als 0,1 Millisekunden ausgeführt.
7. Ebene 6: Breitphasiges räumliches Raster und mobiler GC ohne Zuweisung
Stellen Sie sich vor, Ihr Spiel hat 100 Blöcke, 60 Kugeln und 20 Feinde. Wenn Sie jede Entität mit jeder anderen Entität mithilfe verschachtelter „for“-Schleifen testen, führen Sie Folgendes aus:
\(\frac{180 \times 179}{2} = 16.110 \text{ Schecks pro Frame!}\)
Bei 60 FPS sind das fast 1.000.000 Kollisionsprüfungen pro Sekunde.
Um Ihr Spiel zu skalieren, müssen Sie Broad-Phase Spatial Partitioning implementieren.
Das einheitliche räumliche Hash-Gitter
Wir unterteilen den Bildschirm in ein 2D-Raster aus gleichmäßigen Zellen (in Blocked: Pixel Panzer hat jede Zelle 150 \(\times 150\) Pixel). Eine Entität testet nur Kollisionen mit anderen Entitäten, die sich in denselben Gitterzellen befinden.
Das Problem des mobilen Speichers: GC-Thrashing
Wenn Ihr „SpatialGrid“ in jedem Frame „new List
Auf der Mono-Laufzeitumgebung von Android löst dies häufige Garbage Collections der Generation 0 aus, was zu Folgendem führt: „Mono-Laufzeit: Native Sperrkonflikt (mono_class_is_subclass_)“.
Das Spiel stottert und Google Play markiert Ihr Spiel mit ANR-Warnungen!
Die Lösung: Wiederverwendbare Scratch-Puffer
Hier ist das Zero-Allocation Spatial Hash Grid aus „CollisionManager.cs“ in 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);
}
}
}
}
}
}
}
}
Durch die Wiederverwendung von „_scratchCells“ und „_scratchCheckedEntities“ wird der gesamte Kollisionsschritt mit 0 Byte Heap-Zuweisung pro Frame ausgeführt.
8. Zusammenfassende Vergleichstabelle
| Technik | Mathematische Kosten | Rotationsunterstützung | Tunnelbau sicher? | Bester Anwendungsfall in MonoGame |
|---|---|---|---|---|
AABB (Rectangle.Intersects) |
Ultraschnell (~4 Ganzzahlvergleiche) | Nein | Nein | Gitterblöcke, Bullet-Breitphase, UI |
Kreis (DistanceSquared) |
Sehr schnell (3 Muls, 0 Quadratmeter) | Ja (Invariant) | Nein | Runde Schiffe, Feuerbälle, Energiekugeln |
| Kreis vs. Box („Klemme“) | Schnell (Local MathHelper.Clamp) | Ja | Nein | Kreisspieler navigiert durch enge Mauern |
| Swept Ray (Slab CCD) | Moderat (Parametrischer Raycast) | Ja | Ja | Schnelle Scharfschützengeschosse, Railguns, Laser |
Pixel-Perfekt (IntersectsPixel) |
Selektiv (eingeschränktes Unterrecht) | Ja | Nein | Unregelmäßige Sprite-Konturen, faire Hitboxen |
| Räumliches Gitter (Zero-GC) | Breitphasig (\(O(N^2) \to O(N)\)) | N/A | N/A | Dichte Wellen, Kugelhöllen, Android 60 FPS |
Produktionsvorführungen aus der realen Welt: Arar Games
Diese Kollisionsarchitekturen sind keine theoretischen Experimente – sie sind die eigentliche technische Grundlage für unsere kommerziell veröffentlichten Titel:
- Blockiert: Pixel Panzer: Unser Retro-Panzer-Survival-Arcade-Spiel bei Google Play und Microsoft Store. Es verfügt über unser komplettes zweistufiges Kollisionssystem: ein räumliches Hash-Gitter ohne Zuordnung, das fallende Blöcke filtert, und eine pixelgenaue Kontaktprüfung für Panzer, Kampfjets, Turmgranaten und ColorWheel-Elementarmunition.
- Paint Trek: Unser rasanter Weltraum-Shooter mit Rotationskreiskollision, kontinuierlichem Raycasting und Raketenabwehrsystemen mit überstrichenem Volumen.
Fazit und nächste Schritte
MonoGame gibt Ihnen die Möglichkeit, eine Kollisionserkennung zu entwerfen, die perfekt zu den Anforderungen Ihres Spiels passt. Indem Sie teure Pixel-Perfect-Prüfungen hinter schnellen AABB-Tests einschließen, quadratische Distanzen für Kreise verwenden und die Speicherbereinigung mit wiederverwendbaren Scratch-Puffer eliminieren, können Sie sowohl auf Desktop- als auch auf mobilen Plattformen eine konsolenähnliche Leistung von 60/120 FPS liefern.
Schauen Sie sich unsere Spiele in den App Stores an, um diese Kollisionssysteme in Aktion zu sehen, und beginnen Sie noch heute mit der Implementierung dieser Muster in Ihren eigenen MonoGame-Projekten!
Store-Links und Ressourcen
- Blockiert: Pixel Panzer bei Google Play: Download für Android
- Blockiert: Pixel Panzer im Microsoft Store: Download für Windows
- MonoGame Framework: monogame.net
SEO-Keywords und Hashtags
Schlüsselwörter: MonoGame 2D-Kollisionserkennung, C#-Spieleentwicklung, Triangle.Intersects MonoGame, Pixel Perfect Collision C#, Spatial Hash Grid MonoGame, Swept Ray Continuous Collision, Indie Game Performance Optimization, Zero Allocation Game Loop, Android MonoGame Optimization, 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