MonoGame-də 2D Toqquşmanın Aşkarlanması: Əsas Düzbucaqlılardan Sıfır Ayırma Piksel Mükəmməl Sistemlərinə
Unity və ya Godot kimi vizual mühərriklərdə 2D oyunları qurarkən, toqquşmanın aşkarlanması tez-tez bir sıra müfəttiş qeyd qutuları kimi hiss olunur: siz BoxCollider2D və ya CircleCollider2D üzərinə sillə vurursunuz, Rigidbody əlavə edirsiniz və ümid edirik ki, daxili fizika addımı mobil cihazlarda kəkələməz.
MonoGame və C#-da isə tam nəzarət sizdədir. Heç bir gizli fizika yükü, arzuolunmaz fırlanma ətaləti və çərçivə büdcənizi oğurlayan sirli zibil kolleksiyası (GC) sıçrayışları yoxdur.
Arar Games-də, Bloklanmış: Pixel Panzer və Paint Trek qurarkən, arcade oyun dövrələrimiz həm Windows PC, həm də Android cihazlarında 60-120 PS-də yüzlərlə yüksək sürətli düşmən gülləsini, partlayan kərpic torlarını, fırlanan tank qüllələrini, döyüş təyyarələrinin uçuşlarını və hissəcik qalxanlarını emal etmək üçün lazım idi. Ümumi təyinatlı fizika mühərrikindən söhbət belə gedə bilməzdi - bizə məqsədyönlü, səviyyəli toqquşma arxitekturası lazım idi.
Bu hərtərəfli, kodla idarə olunan bələdçidə biz MonoGame toqquşmasının mütləq əsaslarından ("Rectangle. Intersects" real "Bullet" və "Düşmən" spritləri ilə) başlayacağıq və təkmil çevrə yoxlamaları, qarışıq sıxma, anti-tunel şüaları, istehsal dərəcəli Pixelsion-Zeroalfectioner, Scooter Pixelsion-Pixel-Pixelsion-alfectsalffekti ilə inkişaf edəcəyik. Şəbəkələr** mobil GC-nin sağ qalması üçün optimallaşdırılmışdır.
1. Vəqf: Sadə MonoGame Sprite İerarxiyası
Toqquşmaları aşkar etməzdən əvvəl bizə təmiz oyun obyektləri lazımdır. MonoGame-də obyekt əsas olaraq mövqeyə, fakturaya və məhdudlaşdırıcı düzbucaqlıya malikdir.
Oyunlarımızda istifadə olunan əsas obyekt arxitekturası budur:
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);
}
}
İndi gəlin konkret “Oyunçu”, “Düşmən” və “Güllə” siniflərini yaradaq:
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;
}
}
Bu obyektlər mövcud olduqda, gəlin ən sadə üsuldan başlayaraq, onlar arasında toqquşmaların necə aşkar ediləcəyini araşdıraq.
2. Səviyyə 1: Ən Sadə Toqquşma – Düzbucaqlı.Kəsişir (AABB)
MonoGame-də ən əsas 2D toqquşma yoxlanışı Axis-Aligned Bounding Box (AABB) testidir. "Oxa düzülmüş" termini sadəcə olaraq düzbucaqlının kənarlarının ekranın \(X\) və \(Y\) oxlarına tam paralel olduğunu bildirir (fırlanma yoxdur).
MonoGame sürətli, daxili metodu təmin edir: Düzbucaqlı.Kəsişir (Düzbucaqlı dəyəri).
Düzbucaqlı.Kəsişir Başlıq Altında Necə İşləyir
Səthin altında MonoGame dörd tam müqayisəni həyata keçirir:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
Bütün dörd şərt yerinə yetirilərsə, düzbucaqlılar üst-üstə düşür. Hətta bir şərt uğursuz olarsa, onları boş ox ayırır və heç bir toqquşma mümkün deyil.
Real Gameplay Kodu: Game1.Update-də Bullet vs. Düşmən
Əsas MonoGame Yeniləmə döngənizdə aktiv güllələrin siyahısı ilə aktiv düşmənlərin siyahısı arasında toqquşmaları necə yoxlayırsınız:
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);
}
}
Performans İpucu: Geriyə doğru təkrarladığımıza diqqət yetirin (
for (int i = list.Count - 1; i >= 0; i--))! Əgərforeachistifadə edib_bullets.Remove(bullet)çağırmağa cəhd etsəniz, C#InvalidOperationException: Kolleksiya dəyişdirildiatır. Geriyə doğru iterasiya yaddaşın yenidən indeksləşdirilməsi problemi olmadan elementin təhlükəsiz çıxarılmasına imkan verir.
Arcade sirri: "Şişirmək" vasitəsilə "Ədalətli Hitboxlar"
Bloklanmış: Pixel Panzer kimi retro oyunlarda sprite teksturalarına tez-tez şəffaf kənarlar və ya antenanın ucları daxildir. Bir güllə onun toxumasının boş şəffaf küncünə dəydiyi üçün oyunçunun tankı partlayırsa, oyunçu özünü aldadılmış hiss edəcək.
Toqquşmanın həssas və ədalətli olmasını təmin etmək üçün oyunlar Rectangle.Inflate istifadə edərək sprite daxilində daha kiçik Hitbox istifadə edir:
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. Səviyyə 2: Dairədən Dairəyə Toqquşma (Rotasiya İmmuniteti)
Düzbucaqlılar statik bloklar və şəbəkə plitələri üçün əla işləyir, lakin spritlər fırlananda uğursuz olur. Qeyri-kvadrat kosmik gəmi Paint Trek-də fırlandıqda, oxa düzülmüş məhdudlaşdırıcı qutu fırlanan küncləri əhatə etmək üçün genişlənməlidir, nəticədə boş havada əsəbiləşən "fantom toqquşmalar" yaranır.
Dairəvi asteroidlər, hədəf enerji kürələri və fırlanan kosmik gəmilər üçün Bounding Circles ideal həlldir.
Kvadrat Kök Tələsi
Mərkəzləri arasındakı məsafə radiuslarının cəmindən az və ya bərabər olduqda iki dairə toqquşur:
\(\text{Distance}(C_A, C_B) \le r_A + r_B\)
Kodda, Evklid məsafəsinin hesablanmasında Math.Sqrt (və ya Vector2.Distance) istifadə olunur. Bununla belə, 200 mərmi ilə bir döngədə kvadrat köklərin hesablanması yüzlərlə lazımsız CPU dövrəsini yandırır!
kvadrat məsafəni ilə kvadrat radius cəmi ilə müqayisə edərək, kvadrat kökü tamamilə aradan qaldırırıq:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
MonoGame Tətbiqi: 'Dairə' və 'Dairə'
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);
}
}
İndi bunu birbaşa quruma inteqrasiya edin:
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);
}
}
Sıfır kvadrat kök, sprite fırlanmasına qarşı immunitet və ildırım sürəti.
4. Səviyyə 3: Qarışıq Formalar – Dairə və Qutu ('MathHelper.Clamp')
Paint Trek-də dairəvi kosmik gəmi düzbucaqlı müdafiə maneələrinin sıx labirintindən keçdikdə və ya dəyirmi güllə Bloklanmış: Piksel Panzer-də kvadrat bloka dəydikdə nə baş verir?
Bizə Dairə ilə Düzbucaqlı toqquşması lazımdır.
Sıxma alqoritmi
Strategiya düzbucaqlıda dairənin mərkəzinə ən yaxın nöqtəni tapmaq və sonra həmin ən yaxın nöqtədən mərkəzə olan məsafənin dairənin radiusundan az olub olmadığını yoxlamaqdır.
MonoGame-in 'MathHelper.Clamp' bunu əhəmiyyətsiz edir:
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);
}
}
İndi siz oyunçu qalxanlarını düzbucaqlı kərpiclərə qarşı sıfır ayırma və yüksək dəqiqliklə sınaqdan keçirə bilərsiniz!
5. Səviyyə 4: Davamlı Toqquşmanın Aşkarlanması (CCD) və Süpürülən Şüalar
Siz heç oyununuzda hiper-sürətli snayper raundunu və ya relsli tüfəng lazerini atəşə tutmusunuz, ancaq güllənin sehirli şəkildə nazik düşmən gəmisindən zərər görmədən düz keçməsini izləmisinizmi?
Bu baq tunelinq kimi tanınır.
Diskret oyunlar zaman addımları ilə yeniləndiyinə görə (\(\Delta t = 16.6\text{ms}\) 60 FPS-də) saniyədə 1800 piksel sürətlə hərəkət edən obyekt bir kadrda 30 piksel səyahət edir. Əgər düşmən gövdəsinin qalınlığı cəmi 15 pikseldirsə, güllə 1-ci kadrda düşmənin qarşısında, 2-ci kadrda isə tamamilə düşmənin arxasında idi.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
Həll yolu: Süpürülən Seqment Qutuya qarşı (Slab Metodu)
Tək nöqtəni yoxlamaq əvəzinə, 1-ci Çərçivədə (əvvəlkiMövqe) 2-ci Kadrda (cariMövqe) güllənin mövqeyini birləşdirən bütün xətt seqmentini sınaqdan keçiririk.
Budur, SpiralWar başlığımızdan istehsal raycasting plitələr kəsişmə üsulu:
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;
}
}
Bloklanmış: Piksel Panzer oyununda oyunçunun davamlı Lazer Şüa bacarığı bir toqquşmanı əldən vermədən enən blokların cərgələrini kəsmək üçün bu dəqiq raycastdan istifadə edir.
6. Səviyyə 5: İstehsal dərəcəli Piksel-Mükəmməl Toqquşmanın Aşkarlanması
İndi biz 2D dəqiqliyin son səviyyəsinə çatırıq: Piksel-Mükəmməl Toqquşma.
Retro tank atıcısı və ya kosmik gəmi it dalaşında nizamsız formalar (tank çəlləkləri, qanadlar, kokpit kokpitləri) sprite teksturasında şəffaf piksellərlə əhatə olunur. Düşmən raketi o şəffaf məkana dəyəndə, oyunçular dərhal fərq edirlər.
Piksel-mükəmməl toqquşma üst-üstə düşən fakturaların faktiki alfa (şəffaflıq) kanallarını yoxlayır. İki qeyri-şəffaf piksel eyni dünya koordinatında üst-üstə düşürsə, əsl fiziki vuruş baş verdi.
Ölümcül səhv: Update() daxilində GetData
Bir çox dərslik yeni başlayanlara bunu etməyi tapşırır:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
İş vaxtı zamanı Texture2D.GetData() çağırılması CPU-nu GPU boru kəmərinin yuyulmasını gözləməyə məcbur edir, faktura yaddaşını avtobusun üzərindən geri çəkir. Bunu hər kadrda onlarla sprit üçün etmək oyununuzu 60 FPS-dən 5 FPS-ə qədər yığacaq və dərhal Android ANR-lərini işə salacaq!
Bloklanmış: Pixel Panzer-dən Döyüş Yolu ilə Sınaq Həll
Bloklanmış: Pixel Panzer-də 'Sprite.cs'imiz 5 mühüm qayda ətrafında qurulmuş ultra optimallaşdırılmış 'IntersectsPixel' rutinini həyata keçirir:
- Statik Rəng Massivi Keşi:
GetDatafaktura yükləndikdə bir dəfə çağırılır vəLüğət<Texture2D, Rəng[]>-də saxlanılır. - AABB Erkən Çıxış Mühafizəsi: Əgər
Sərhədlər.Kəsişir(digər.Sərhədlər)yanlışdırsa, biz dərhal zəmanət veririk. Bir pikseli yoxlamadan əvvəl yoxlamaların 99%-i aradan qaldırılır. - Texture Atlas &
SourceDectangledəstəyi: Mənbə düzbucaqlı ofsetlərindən istifadə edərək faktura atlaslarına yığılmış spritləri idarə edir. - Hesablanmış Üst-üstə düşmə Pəncərəsi: Biz yalnız hər iki sprit (
Math.Max(a.Top, b.Top)və s.) arasındakı dəqiq kəsişmə düzbucaqlı üzərində döngə edirik. - Alfa Qısa Qapanma: Əgər Sprite A-nın pikseli şəffafdırsa (
A <= 20), Sprite B tamamilə atlanır. - Əmlakın qaldırılması: İç içə çevrilmiş döngələrdə virtual xassələrə ('Sərhədlər') daxil olmaq minlərlə struktur nüsxəsini yaradır. Döngəyə daxil olmamışdan əvvəl onları yerli stek dəyişənlərində saxlayırıq.
Budur tam, istehsala hazır kod:
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;
}
}
Bu proqram müasir Android aparat və masaüstü sistemlərində 0,1 millisaniyədə yerinə yetirilir.
7. Level 6: Broad-Phase Spatial Grid & Zero-Allocation Mobile GC
Imagine your game has 100 blocks, 60 bullets, and 20 enemies. If you test every entity against every other entity using nested for loops, you perform:
\(\frac{180 \times 179}{2} = 16,110 \text{ checks per frame!}\)
At 60 FPS, that is nearly 1,000,000 collision checks every second.
To scale your game, you must implement Broad-Phase Spatial Partitioning.
The Uniform Spatial Hash Grid
We divide the screen into a 2D grid of uniform cells (in Blocked: Pixel Panzer, each cell is \(150 \times 150\) pixels). An entity only tests collisions against other entities that reside in the same grid cells.
The Mobile Memory Problem: GC Thrashing
If your SpatialGrid creates new List<Point>() or new List<Block>() every frame, you allocate megabytes of garbage every minute on the managed heap.
On Android's Mono runtime, this triggers frequent Gen-0 Garbage Collections, causing:
mono runtime: Native lock contention (mono_class_is_subclass_)
The game stutters and Google Play flags your game with ANR warnings!
The Solution: Reusable Scratch Buffers
Here is the Zero-Allocation Spatial Hash Grid from 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);
}
}
}
}
}
}
}
}
By reusing _scratchCells and _scratchCheckedEntities, the entire collision step runs with 0 bytes of heap allocation per frame.
8. Summary Comparison Table
| Technique | Mathematical Cost | Rotational Support | Tunneling Safe? | Best Use Case in MonoGame |
|---|---|---|---|---|
AABB (Rectangle.Intersects) |
Ultra-Fast (~4 integer comparisons) | No | No | Grid blocks, bullet broad-phase, UI |
Circle (DistanceSquared) |
Very Fast (3 muls, 0 sqrt) | Yes (Invariant) | No | Round ships, fireballs, energy orbs |
Circle vs Box (Clamp) |
Fast (Local MathHelper.Clamp) | Yes | No | Circular player navigating tight walls |
| Swept Ray (Slab CCD) | Moderate (Parametric Raycast) | Yes | Yes | Fast sniper bullets, railguns, lasers |
Pixel-Perfect (IntersectsPixel) |
Selective (Restricted Sub-rect) | Yes | No | Irregular sprite contours, fair hitboxes |
| Spatial Grid (Zero-GC) | Broad-phase (\(O(N^2) \to O(N)\)) | N/A | N/A | Dense waves, bullet hells, Android 60 FPS |
Real-World Production Showcases: Arar Games
These collision architectures are not theoretical experiments—they are the real engineering foundation powering our commercially released titles:
- Blocked: Pixel Panzer: Our retro tank survival arcade game on Google Play and Microsoft Store. It features our complete two-tier collision system: a zero-allocation spatial hash grid filtering falling blocks, and pixel-perfect contact checking for tanks, fighter jets, turret shells, and ColorWheel elemental ammunition.
- Paint Trek: Our fast-paced space shooter featuring rotational circle collision, continuous raycasting, and swept-volume missile defense systems.
Conclusion & Next Steps
MonoGame gives you the power to design collision detection that perfectly matches the needs of your game. By gating expensive Pixel-Perfect checks behind fast AABB tests, utilizing Squared Distances for circles, and eliminating garbage collection with Reusable Scratch Buffers, you can deliver console-smooth 60/120 FPS performance on both desktop and mobile platforms.
Check out our games on the app stores to see these collision systems in action, and start implementing these patterns in your own MonoGame projects today!
Store Links & Resources
- Blocked: Pixel Panzer on Google Play: Download for Android
- Blocked: Pixel Panzer on Microsoft Store: Download for Windows
- MonoGame Framework: monogame.net
SEO Keywords & Hashtags
Keywords: MonoGame 2D collision detection, C# game development, Rectangle.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