MonoGame’de 2D Çarpışma Tespiti: Basit Dikdörtgenlerden Sıfır-GC Piksel-Hassasiyetine
Unity veya Godot gibi görsel oyun motorlarında 2D oyun geliştirirken çarpışma tespiti (collision detection) genellikle arayüzdeki birkaç onay kutusundan ibarettir: Nesneye bir BoxCollider2D veya CircleCollider2D eklersiniz, bir Rigidbody iliştirirsiniz ve arka plandaki fizik motorunun mobil cihazlarda takılma yapmamasını umarsınız.
Oysa MonoGame ve C# dünyasında kontrolün tamamı sizin elinizdedir. Gizli bir fizik motoru yükü, istenmeyen dönme sürtünmesi ve kare bütçenizi çalan gizemli çöp toplayıcı (Garbage Collection - GC) takılmalarıyla uğraşmazsınız.
Arar Games olarak Blocked: Pixel Panzer ve Paint Trek oyunlarımızı geliştirirken, arcade oyun döngümüzün hem Windows PC'de hem de Android mobil cihazlarda saniyede 60 ila 120 FPS hızında yüzlerce yüksek hızlı düşman mermisini, patlayan tuğla ızgaralarını, dönen tank taretlerini, savaş uçaklarını ve koruyucu enerji kalkanlarını işlemesi gerekiyordu. Genel amaçlı ağır fizik kütüphaneleri bu hız için hantal kalıyordu; bu yüzden amaca özel katmanlı bir çarpışma mimarisi inşa ettik.
Bu rehberde, lafı uzatmadan doğrudan çalışan kodlarla ilerleyeceğiz: En basit MonoGame çarpışmasından (Rectangle.Intersects ile somut Bullet ve Enemy sprite nesneleri) başlayacak; daire kontrolleri, karma şekil kırpmaları, mermilerin duvardan geçmesini önleyen raycast çözümleri, üretim seviyesinde Pixel-Perfect (Piksel-Hassas) çarpışma ve Android ANR kilitlenmelerini bitiren Sıfır-Tahsisli Uzamsal Izgara (Spatial Hash Grid) mimarisine kadar adım adım kodlayacağız.
1. Temel Yapı: Temiz Bir MonoGame Sprite Hiyerarşisi
Çarpışma tespiti yapabilmek için önce ekranda hareket eden temiz varlıklara ihtiyacımız var. MonoGame'de bir nesne temel olarak bir konuma (Vector2), bir dokuya (Texture2D) ve bir sınırlayıcı dikdörtgene (Rectangle) sahiptir.
İşte oyunlarımızda temel aldığımız taban sınıfı:
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);
}
}
Şimdi bu tabandan türeyen somut Player, Enemy ve Bullet sınıflarımızı oluşturalım:
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 temel sınıflarımız hazır olduğuna göre, aralarındaki çarpışmaları en basitten en karmaşığa doğru test etmeye başlayalım.
2. Kademe 1: En Basit Çarpışma – Rectangle.Intersects (AABB)
MonoGame’de en temel ve en hızlı 2D çarpışma testi Axis-Aligned Bounding Box (AABB) yöntemidir. "Eksenle hizalı" ifadesi, dikdörtgenin kenarlarının ekranın Kartezyen \(X\) ve \(Y\) eksenlerine her zaman paralel olduğu (hiçbir rotasyon içermediği) anlamına gelir.
MonoGame bunun için son derece hızlı bir yerleşik metoda sahiptir: Rectangle.Intersects(Rectangle value).
Rectangle.Intersects Arka Planda Nasıl Çalışır?
MonoGame kaynak kodunda bu metot tam olarak dört tamsayı karşılaştırması yapar:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
Dört koşul da sağlanıyorsa iki dikdörtgen kesişiyordur. Tek bir koşul bile bozulursa dikdörtgenler arasında boş bir eksen vardır ve çarpışma imkansızdır.
Gerçek Oyun Döngüsü: Game1.Update İçinde Mermi ve Düşman Kontrolü
Aktif mermiler ve düşmanlar arasındaki çarpışmayı ana Update döngüsünde şu şekilde kontrol ederiz:
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: Listeleri geriye doğru (
for (int i = list.Count - 1; i >= 0; i--)) taradığımıza dikkat edin!foreachdöngüsü içinde_bullets.Remove(bullet)çağırmaya çalışırsanız C# derhalInvalidOperationException: Collection was modifiedhatası fırlatır. Sondan başa doğru döngü kurmak, liste elemanlarını bellek indeksi kayması olmadan güvenle silmenizi sağlar.
Arcade Sırrı: Inflate ile "Adil Hitbox" Tasarımı
Blocked: Pixel Panzer gibi retro arcade oyunlarında sprite dokularının kenarlarında genellikle boş saydam pikseller veya anten çıkıntıları bulunur. Eğer oyuncunun tankı merminin o boş saydam piksele değmesiyle patlarsa oyuncu haksızlığa uğradığını hisseder.
Çarpışmayı oyuncuya karşı adil ve pürüzsüz hissettirmek için Rectangle.Inflate kullanarak hitbox'ı sprite dokusundan birkaç piksel içeri çekeriz:
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. Kademe 2: Daire-Daire Çarpışması (Dönme Bağışıklığı)
Dikdörtgenler sabit bloklar için harikadır; ancak sprite'lar dönmeye başladığında sınırlayıcı dikdörtgen köşeleri kapsamak için devasa boyutlara genişler. Bu da boş havada mermi çarpmış gibi algılanan "hayalet temaslara" (phantom collisions) neden olur.
Paint Trek'teki dönen uzay gemileri, dairesel asteroitler ve küresel enerji kalkanları için Sınırlayıcı Daireler (Bounding Circles) kusursuz çözümdür.
Karekök Tuzağı
İki daire, merkezleri arasındaki mesafe yarıçaplarının toplamından küçük veya eşitse çarpışır:
\(\text{Distance}(C_A, C_B) \le r_A + r_B\)
Kod yazarken iki nokta arasındaki mesafeyi bulmak için Math.Sqrt ya da Vector2.Distance kullanılır. Ancak ekranda 200 mermi varken her karede yüzlerce kez karekök hesaplamak işlemciyi fuzuli yere yorar!
Eşitsizliğin her iki tarafının karesini alarak karekök alma maliyetinden tamamen kurtuluruz:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
MonoGame Uygulaması: Daire ve Daire Kesişimi
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);
}
}
Bunu bir oyun nesnesine entegre etmek son derece kolaydır:
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 karekök işlemi, sprite rotasyonundan tamamen bağımsız ve inanılmaz derecede hızlı.
4. Kademe 3: Karma Şekiller – Daire ve Dikdörtgen (MathHelper.Clamp)
Paint Trek'te dairesel bir uzay gemisi dar koridorlardaki dikdörtgen savunma duvarları arasından geçerken veya Blocked: Pixel Panzer'de yuvarlak bir bomba kare bir bloğa çarptığında ne yapacağız?
Bize Daire vs. Dikdörtgen çarpışması gerekir.
Kırpma (Clamping) Algoritması
Temel mantık; dikdörtgenin yüzeyinde dairenin merkezine en yakın olan noktayı bulmak ve o nokta ile daire merkezi arasındaki karesel mesafenin daire yarıçapının karesinden küçük olup olmadığını kontrol etmektir.
MonoGame'in yerleşik MathHelper.Clamp metodu bunu iki satıra indirir:
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);
}
}
Böylece tek bir bellek tahsisi yapmadan karmaşık geometriyi çözmüş olursunuz.
5. Kademe 4: Sürekli Çarpışma Tespiti (CCD) ve Işın Kırpma (Raycasting)
Oyununuzda saniyede binlerce piksel hızla uçan bir keskin nişancı mermisi ya da lazer attığınızda, merminin ince bir düşmanın içinden hiçbir hasar vermeden geçip gittiğini gördünüz mü?
Bu hataya oyun geliştirmede Tünelleme (Tunneling) denir.
Oyunlar zaman adımlarıyla güncellendiği için (\(16.6\text{ms}\)), saniyede 1.800 piksel hızla giden bir mermi tek bir karede 30 piksel zıplar. Önündeki düşman gövdesi 15 piksel kalınlığındaysa; mermi 1. Karede düşmanın önündedir, 2. Karede ise arkasına geçmiştir. Ayrık kontroller sırasında düşmanla hiçbir karede kesişmemiştir!
1. Kare: [ Mermi ] ---> | Duvar / Düşman |
2. Kare: | Duvar / Düşman | ---> [ Mermi ]
(ÇARPIŞMA KAÇIRILDI!)
Çözüm: Süpürülmüş Işın ve Kutu Kesişimi (Slab Yöntemi)
Tek bir nokta yerine merminin önceki karedeki konumu ile bu karedeki konumu arasına çekilen doğru parçasını (ışın) hedef kutunun paralel düzlemleriyle kesiştiririz:
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;
}
}
Blocked: Pixel Panzer oyunundaki Lazer Işını Becerisi tam olarak bu algoritmayı kullanarak yukarıdan inen blok dizisini anında keser ve tünelleme hatası yaşamaz.
6. Kademe 5: Üretim Standardında Pixel-Perfect (Piksel-Hassas) Çarpışma
Şimdi 2D hassasiyetin zirvesine geliyoruz: Pixel-Perfect Çarpışma.
Düzensiz açılara sahip bir tank namlusu veya kanatlı bir uzay gemisi düşünün. Dikdörtgenler dokunun köşelerindeki şeffaf alanları da içine alır. Gerçek bir isabet olup olmadığını anlamak için örtüşen piksellerin alfa (saydamlık) değerlerini taramamız gerekir.
Ölümcül Hata: Update() İçinde GetData Çağırmak
İnternetteki pek çok başlangıç rehberi şu feci hatayı yapar:
// KESİNLİKLE YAPMAYIN!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // GPU HATTINI KİLİTLER, GC KASILMALARI YARATIR!
Oyun döngüsü içinde Texture2D.GetData() çağırmak CPU'yu GPU hattını beklemeye zorlar. Bunu her karede onlarca nesne için yapmak FPS'inizi 60'tan 5'e düşürür ve Android'de doğrudan çökmelere neden olur!
Blocked: Pixel Panzer Üretim Çözümü
Blocked: Pixel Panzer oyunumuzun Sprite.cs dosyasındaki IntersectsPixel metodu beş katı optimizasyon kuralına göre çalışır:
- Statik Renk Dizisi Önbelleği:
GetDatadoku yüklendiğinde yalnızca bir kez çağrılır veDictionary<Texture2D, Color[]>içinde saklanır. - AABB Erken Çıkış Muhafızı: Eğer
Bounds.Intersects(other.Bounds)yanlışsa tek bir piksel bile taranmadan anında çıkılır (kontrollerin %99'u burada elenir). - SpriteSheet /
SourceRectangleDesteği: Tek bir büyük sprite atlasında yer alan alt-dokuların ofsetlerini kusursuz eşler. - Sınırlandırılmış Kesişim Penceresi: Tüm doku yerine yalnızca iki dikdörtgenin kesiştiği dar pencere taranır (
Math.Max(a.Top, b.Top)vb.). - Alfa Erken Çıkışı (Early Bailout): Eğer Sprite A'nın pikseli şeffafsa (
A <= 20), Sprite B'nin pikseli hiç taranmaz. - Özellik (Property) Kaldırma:
Boundsgibi struct döndüren özellikleri iç döngüde çağırmak binlerce gereksiz kopyalama yaratır; bunlar döngü başında yerel değişkenlere alınır.
İşte tam çalışan üretim kodu:
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 metot, en yoğun sahnelerde bile Android telefonlarda ve Windows cihazlarında milisaniyenin onda biri sürede kusursuz çalışır.
7. Kademe 6: Uzamsal Izgara (Spatial Grid) ve Sıfır-Tahsisli Mobil GC Mimarisi
Sahnede 100 blok, 60 mermi ve 20 düşman olduğunda iç içe for döngüleriyle her nesneyi birbiriyle karşılaştırırsanız:
\(\frac{180 \times 179}{2} = \text{Kare başına } 16.110 \text{ kontrol!}\)
60 FPS hızında bu, saniyede yaklaşık 1.000.000 çarpışma kontrolü demektir.
Bunu çözmek için ekranı sanal ızgara hücrelerine böleriz (Blocked: Pixel Panzer'de her hücre \(150\times 150\) pikseldir). Bir nesne yalnızca aynı ızgara hücresinde bulunan diğer nesnelerle test edilir.
Android ve Mobil Bellek Kâbusu: GC Thrashing
Eğer her karede new List<Point>() veya new List<Sprite>() oluşturursanız, yönetilen belleğe dakikada megabaytlarca çöp yığarsınız.
Android Mono çalışma zamanında bu durum sürekli çöp toplayıcıyı (GC) tetikler ve şu meşhur kilitlenmeye yol açar:
mono runtime: Native lock contention (mono_class_is_subclass_)
Oyun takılır, kare düşer ve Google Play konsolunda ANR (Uygulama Yanıt Vermiyor) uyarıları patlar!
Çözüm: Sıfır-Tahsisli Tamponlar (Scratch Buffers)
İşte Blocked: Pixel Panzer'in CollisionManager.cs dosyasında uyguladığımız Sıfır-Tahsisli Uzamsal Izgara:
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);
}
}
}
}
}
}
}
}
_scratchCells ve _scratchCheckedEntities listeleri tekrar tekrar temizlenip kullanılarak çarpışma adımı kare başına tam 0 bayt bellek tahsisiyle tamamlanır.
8. Özet Karşılaştırma Tablosu
| Teknik | Matematiksel Maliyet | Rotasyon Desteği | Tünelleme Koruması | MonoGame'de En Uygun Kullanım |
|---|---|---|---|---|
AABB (Rectangle.Intersects) |
Ultra-Hızlı (~4 tamsayı kontrolü) | Yok | Yok | Izgara blokları, mermi erken kontrolü, UI |
Daire (DistanceSquared) |
Çok Hızlı (3 çarpma, 0 karekök) | Var (Bağışık) | Yok | Yuvarlak gemiler, ateş topları, kalkanlar |
Daire vs Kutu (Clamp) |
Hızlı (Yerel MathHelper.Clamp) | Var | Yok | Duvarlar arasında gezen yuvarlak oyuncu |
| Süpürülmüş Işın (CCD) | Orta (Parametrik Doğru Parçası) | Var | Var | Hızlı roketler, kesintisiz lazerler |
Pixel-Perfect (IntersectsPixel) |
Seçici (Yalnızca Kesişen Alan) | Var | Yok | Girintili sprite hatları, adil temaslar |
| Uzamsal Izgara (Sıfır-GC) | Geniş Faz (\(O(N^2) \to O(N)\)) | İlgisiz | İlgisiz | Yoğun dalgalar, Android'de 60 FPS akıcılık |
Gerçek Oyunlarımızdan Referanslar: Arar Games
Bu sistemler sadece teorik anlatımlar değildir; mağazalarda yer alan yapımlarımızın kalbidir:
- Blocked: Pixel Panzer: İki kademeli çarpışma mimarimizin en eksiksiz örneğidir. ColorWheel mühimmatı, dönen oyuncu taretleri, patlayan blok formasyonları ve lazer ışınları; sıfır-tahsisli spatial grid sayesinde Google Play ve Windows'ta 60'tan fazla dilde 60 FPS akıcılıkla çalışır.
- Paint Trek: Rotasyonel daire-daire çarpışmaları ve süpürülmüş ışın algoritmalarıyla yüksek tempolu retro uzay savaşlarını ve mermi cehennemi kaçış dinamiklerini yönetir.
Sonuç
MonoGame, gereksiz motor yüklerinden arınmış yalın yapısıyla amaca özel yüksek performanslı çarpışma sistemleri kurmanız için mükemmel bir ortam sunar. Ağır Pixel-Perfect testlerini hızlı AABB filtrelerinin arkasına gizleyerek, Daire matematiğinde kareköklerden kaçınarak ve Sıfır-Tahsisli Uzamsal Izgaralar ile bellek temizleyicisini rahatlatarak her cihazda akıcı ve pürüzsüz oyunlar inşa edebilirsiniz.
Siz MonoGame projelerinizde hangi çarpışma zorluklarıyla karşılaştınız? Mağaza bağlantılarından oyunlarımızı inceleyebilir ve deneyimlerinizi bizimle paylaşabilirsiniz!
Mağaza Bağlantıları ve Kaynaklar
- Blocked: Pixel Panzer (Google Play): Android için İndirin
- Blocked: Pixel Panzer (Microsoft Store): Windows için İndirin
- MonoGame Framework: monogame.net
SEO Anahtar Kelimeleri ve Etiketler
Anahtar Kelimeler: MonoGame 2D çarpışma tespiti, C# oyun geliştirme, Rectangle.Intersects MonoGame, pixel perfect collision C#, spatial hash grid MonoGame, sürekli çarpışma tespiti MonoGame, bağımsız oyun performans optimizasyonu, sıfır tahsisli oyun döngüsü, Android MonoGame optimizasyonu, Arar Games, Blocked Pixel Panzer, Paint Trek.
#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformansOptimizasyonu #CleanCode #BlockedPixelPanzer #PaintTrek #ArarGames #OyunGeliştirme
Hiç yorum yok:
Yorum Gönder