Deteksi Tabrakan 2D di MonoGame: Dari Persegi Panjang Dasar hingga Sistem Piksel Sempurna Alokasi Nol
Saat membuat game 2D di mesin visual seperti Unity atau Godot, deteksi tabrakan sering kali terasa seperti serangkaian kotak centang inspektur: Anda menekan BoxCollider2D atau CircleCollider2D, memasang Rigidbody, dan berharap langkah fisika internal tidak tersendat di perangkat seluler.
Namun, di MonoGame dan C#, Anda memegang kendali penuh. Tidak ada overhead fisika tersembunyi, tidak ada inersia rotasi yang tidak diinginkan, dan tidak ada lonjakan pengumpulan sampah (GC) misterius yang mencuri anggaran frame Anda.
Di Arar Games, saat kami membuat Blocked: Pixel Panzer dan Paint Trek, loop game arcade kami diperlukan untuk memproses ratusan peluru musuh berkecepatan tinggi, kotak bata yang meledak, turret tank yang berputar, flyby jet tempur, dan perisai partikel pada 60 hingga 120 FPS di PC Windows dan perangkat Android. Mesin fisika untuk tujuan umum tidak mungkin dilakukan—kami memerlukan arsitektur tabrakan berjenjang yang dibuat khusus.
Dalam panduan komprehensif berbasis kode ini, kita akan mulai dari dasar-dasar absolut tabrakan MonoGame (Rectangle.Intersects dengan sprite Bullet dan Enemy yang sebenarnya) dan melanjutkan ke pemeriksaan lingkaran tingkat lanjut, penjepitan campuran, pancaran sinar anti-tunneling, Tabrakan Piksel Sempurna tingkat produksi, dan Kisi Spasial Alokasi Nol yang dioptimalkan untuk kelangsungan GC seluler.
1. Fondasi: Hierarki Sprite MonoGame Sederhana
Sebelum mendeteksi tabrakan, kita memerlukan entitas game yang bersih. Di MonoGame, suatu entitas pada dasarnya memiliki posisi, tekstur, dan persegi panjang pembatas.
Berikut adalah arsitektur entitas dasar yang digunakan di seluruh game kami:
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);
}
}
Sekarang mari kita buat kelas Pemain, Musuh, dan Bullet yang konkret:
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;
}
}
Dengan adanya entitas-entitas ini, mari kita jelajahi cara mendeteksi tabrakan di antara entitas-entitas tersebut, dimulai dari metode yang paling sederhana.
2. Level 1: Tabrakan Paling Sederhana – Rectangle.Intersects (AABB)
Pemeriksaan tabrakan 2D yang paling mendasar di MonoGame adalah pengujian Axis-Aligned Bounding Box (AABB). Istilah "sejajar sumbu" berarti bahwa tepi persegi panjang benar-benar sejajar dengan sumbu \(X\) dan \(Y\) layar (tidak ada rotasi).
MonoGame menyediakan metode bawaan yang cepat: Rectangle.Intersects(Rectangle value).
Cara Kerja Rectangle.Intersects di Balik Terpal
Di bawah permukaan, MonoGame menjalankan empat perbandingan bilangan bulat:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
Jika keempat kondisi terpenuhi, persegi panjang akan tumpang tindih. Jika satu kondisi saja gagal, sumbu kosong akan memisahkannya, dan tumbukan tidak mungkin terjadi.
Kode Gameplay Sebenarnya: Bullet vs. Musuh di Game1.Update
Inilah cara Anda memeriksa tabrakan antara daftar peluru aktif dan daftar musuh aktif di dalam loop Pembaruan MonoGame utama Anda:
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);
}
}
Tips Performa: Perhatikan bahwa kita melakukan iterasi mundur (
for (int i = list.Count - 1; i >= 0; i--))! Jika Anda menggunakanforeachdan mencoba memanggil_bullets.Remove(bullet), C# akan memunculkanInvalidOperationException: Koleksi telah dimodifikasi. Iterasi mundur memungkinkan penghapusan elemen dengan aman tanpa masalah pengindeksan ulang memori.
Rahasia Arcade: "Hitbox yang Adil" melalui Inflate
Dalam game retro seperti Diblokir: Pixel Panzer, tekstur sprite sering kali menyertakan margin transparan atau lonjakan antena. Jika tank pemain meledak karena peluru menyentuh sudut transparan kosong pada teksturnya, pemain akan merasa tertipu.
Untuk membuat tabrakan terasa responsif dan adil, game menggunakan Hitbox yang lebih kecil di dalam sprite menggunakan 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. Level 2: Tabrakan Lingkaran-ke-Lingkaran (Imunitas Rotasi)
Persegi panjang berfungsi baik untuk blok statis dan ubin kisi, tetapi gagal saat sprite berputar. Saat pesawat luar angkasa non-persegi berputar di Paint Trek, kotak pembatas yang sejajar sumbu harus melebar untuk menutupi sudut yang berputar, sehingga mengakibatkan "tabrakan hantu" yang membuat frustrasi di udara kosong.
Untuk asteroid berbentuk lingkaran, bola energi yang bergerak, dan pesawat ruang angkasa yang berputar, Lingkaran Pembatas adalah solusi ideal.
Perangkap Akar Kuadrat
Dua buah lingkaran bertumbukan jika jarak antara pusat-pusatnya kurang dari atau sama dengan jumlah jari-jarinya:
\(\teks{Jarak}(C_A, C_B) \le r_A + r_B\)
Dalam kode, penghitungan jarak Euclidean menggunakan Math.Sqrt (atau Vector2.Distance). Namun, menghitung akar kuadrat dalam satu lingkaran dengan 200 proyektil akan membakar ratusan siklus CPU yang tidak perlu!
Dengan membandingkan jarak kuadrat dengan jumlah radius kuadrat, kita menghilangkan akar kuadrat sepenuhnya:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
Implementasi MonoGame: Lingkaran vs. Lingkaran
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);
}
}
Sekarang integrasikan ini langsung ke dalam suatu entitas:
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);
}
}
Nol akar kuadrat, kebal terhadap rotasi sprite, dan secepat kilat.
4. Level 3: Bentuk Campuran – Lingkaran vs. Kotak (MathHelper.Clamp)
Apa yang terjadi jika pesawat luar angkasa melingkar di Paint Trek menavigasi melalui labirin ketat penghalang pertahanan persegi panjang, atau saat peluru bundar mengenai balok persegi di Blocked: Pixel Panzer?
Kita memerlukan tabrakan Lingkaran vs. Persegi Panjang.
Algoritma Penjepitan
Strateginya adalah mencari titik pada persegi panjang yang paling dekat dengan pusat lingkaran, lalu menguji apakah jarak titik terdekat ke pusat tersebut lebih kecil dari jari-jari lingkaran.
MathHelper.Clamp MonoGame menjadikan ini sepele:
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);
}
}
Anda sekarang dapat menguji perisai pemain terhadap batu bata persegi panjang dengan alokasi nol dan akurasi tinggi!
5. Level 4: Deteksi Tabrakan Berkelanjutan (CCD) & Sinar Sapu
Pernahkah Anda menembakkan peluru penembak jitu berkecepatan tinggi atau laser railgun dalam permainan Anda, hanya untuk menyaksikan peluru secara ajaib menembus kapal musuh yang tipis tanpa menimbulkan kerusakan?
Bug ini dikenal sebagai tunneling.
Karena game terpisah diperbarui dalam langkah waktu (\(\Delta t = 16,6\text{ms}\) pada 60 FPS), objek yang bergerak dengan kecepatan 1.800 piksel per detik bergerak 30 piksel dalam satu bingkai. Jika ketebalan lambung musuh hanya 15 piksel, maka peluru berada di depan musuh pada Frame 1, dan sepenuhnya di belakang musuh pada Frame 2.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
Solusi: Segmen Sapu vs. Kotak (Metode Pelat)
Daripada menguji satu titik, kami menguji seluruh segmen garis yang menghubungkan posisi poin pada Frame 1 (previousPosition) ke Frame 2 (currentPosition).
Berikut adalah metode persimpangan lempengan raycasting produksi dari judul pendamping kami 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;
}
}
Dalam Blocked: Pixel Panzer, Keterampilan Sinar Laser pemain yang terus-menerus menggunakan pancaran sinar yang tepat ini untuk memotong deretan blok yang menurun tanpa melewatkan satu tabrakan pun.
6. Level 5: Deteksi Tabrakan Sempurna Piksel Tingkat Produksi
Sekarang kita mencapai tingkat akurasi 2D tertinggi: Tabrakan Piksel Sempurna.
Dalam penembak tank retro atau pertempuran udara pesawat ruang angkasa, bentuk tidak beraturan (laras tangki, sayap, kokpit kokpit) dikelilingi oleh piksel transparan dalam tekstur sprite. Saat misil musuh menghantam ruang transparan itu, pemain akan langsung menyadarinya.
Tabrakan piksel sempurna memeriksa saluran alfa (transparansi) sebenarnya dari tekstur yang tumpang tindih. Jika dua piksel non-transparan tumpang tindih pada koordinat dunia yang sama, serangan fisik sebenarnya telah terjadi.
Kesalahan Fatal: GetData di dalam Update()
Banyak tutorial yang menginstruksikan pemula untuk melakukan ini:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
Memanggil Texture2D.GetData() saat runtime akan memaksa CPU menunggu hingga pipeline GPU terisi, sehingga menarik kembali memori tekstur ke bus. Melakukan hal ini pada lusinan sprite di setiap frame akan meningkatkan game Anda dari 60 FPS menjadi 5 FPS dan langsung memicu ANR Android!
Solusi Teruji Pertempuran dari Diblokir: Pixel Panzer
Di Blocked: Pixel Panzer, Sprite.cs kami mengimplementasikan rutinitas IntersectsPixel yang sangat optimal yang dibangun berdasarkan 5 aturan penting:
- Cache Array Warna Statis:
GetDatadipanggil tepat satu kali saat tekstur dimuat, dan disimpan dalamKamus<Tekstur2D, Warna[]>. - AABB Early Exit Guard: Jika
Bounds.Intersects(other.Bounds)salah, kami segera melakukan bail out. 99% pemeriksaan dihilangkan sebelum memeriksa satu piksel. - Dukungan Atlas Tekstur &
SourceRectangle: Menangani sprite yang dimasukkan ke dalam atlas tekstur menggunakan offset persegi panjang sumber. - Jendela Tumpang Tindih Terhitung: Kita hanya mengulang persegi panjang perpotongan yang tepat antara kedua sprite (
Math.Max(a.Top, b.Top), dll.). - Hubungan Pendek Alfa: Jika piksel Sprite A transparan (
A <= 20), Sprite B dilewati sepenuhnya. - Property Hoisting: Mengakses properti virtual (
Bounds) dalam loop bersarang akan menghasilkan ribuan salinan struct. Kami menyimpannya dalam variabel tumpukan lokal sebelum memasuki loop.
Berikut kode lengkap siap produksi:
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;
}
}
Rutinitas ini dijalankan dalam waktu kurang dari 0,1 milidetik pada perangkat keras Android modern dan sistem desktop.
7. Level 6: Jaringan Spasial Fase Luas & GC Seluler Alokasi Nol
Bayangkan game Anda memiliki 100 blok, 60 peluru, dan 20 musuh. Jika Anda menguji setiap entitas terhadap setiap entitas lainnya menggunakan perulangan for yang disarangkan, Anda akan melakukan:
\(\frac{180 \times 179}{2} = 16.110 \text{ cek per frame!}\)
Pada 60 FPS, itu berarti hampir 1.000.000 pemeriksaan tabrakan setiap detik.
Untuk menskalakan game Anda, Anda harus menerapkan Partisi Spasial Fase Luas.
Grid Hash Spasial Seragam
Kami membagi layar menjadi kisi 2D sel seragam (di Diblokir: Pixel Panzer, setiap sel berukuran \(150 \kali 150\) piksel). Suatu entitas hanya menguji tabrakan terhadap entitas lain yang berada di sel grid yang sama.
Masalah Memori Ponsel: GC Thrashing
Jika SpatialGrid Anda membuat Daftar<Point>() baru atau Daftar<Block>() baru setiap frame, Anda mengalokasikan megabita sampah setiap menit di tumpukan terkelola.
Pada runtime Mono Android, hal ini sering memicu Pengumpulan Sampah Gen-0, yang menyebabkan:
waktu proses mono: Pertentangan kunci asli (mono_class_is_subclass_)
Game tersendat dan Google Play menandai game Anda dengan peringatan ANR!
Solusinya: Scratch Buffer yang Dapat Digunakan Kembali
Berikut ini Kisi Hash Spasial Alokasi Nol dari CollisionManager.cs di Diblokir: 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);
}
}
}
}
}
}
}
}
Dengan menggunakan kembali _scratchCells dan _scratchCheckedEntities, seluruh langkah tabrakan berjalan dengan 0 byte alokasi heap per frame.
8. Tabel Perbandingan Ringkasan
| Teknik | Biaya Matematika | Dukungan Rotasi | Terowongan Aman? | Kasus Penggunaan Terbaik di MonoGame |
|---|---|---|---|---|
AABB (Persegi Panjang.Persimpangan) |
Sangat Cepat (~4 perbandingan bilangan bulat) | Tidak | Tidak | Blok kisi, fase luas peluru, UI |
Lingkaran (DistanceSquared) |
Sangat Cepat (3 muls, 0 sqrt) | Ya (Invarian) | Tidak | Kapal bundar, bola api, bola energi |
Lingkaran vs Kotak (Penjepit) |
Cepat (MathHelper.Clamp Lokal) | Ya | Tidak | Pemain melingkar menavigasi dinding yang rapat |
| Sapu Ray (Slab CCD) | Sedang (Parametrik Raycast) | Ya | Ya | Peluru penembak jitu cepat, railgun, laser |
Pixel-Sempurna (IntersectsPixel) |
Selektif (Subrek Terbatas) | Ya | Tidak | Kontur sprite tidak beraturan, hitbox wajar |
| Kotak Spasial (Zero-GC) | Fase luas (\(O(N^2) \ke O(N)\)) | T/A | T/A | Ombak padat, peluru neraka, Android 60 FPS |
Pameran Produksi Dunia Nyata: Arar Games
Arsitektur tabrakan ini bukanlah eksperimen teoretis—ini adalah landasan teknik nyata yang mendukung judul-judul kami yang dirilis secara komersial:
- Diblokir: Pixel Panzer: Game arcade survival tank retro kami di Google Play dan Microsoft Store. Ini menampilkan sistem tabrakan dua tingkat kami yang lengkap: jaringan hash spasial tanpa alokasi yang menyaring blok yang jatuh, dan pemeriksaan kontak piksel sempurna untuk tank, jet tempur, cangkang menara, dan amunisi elemen ColorWheel.
- Paint Trek: Penembak luar angkasa berkecepatan tinggi kami yang dilengkapi tabrakan lingkaran rotasi, pancaran sinar terus menerus, dan sistem pertahanan rudal volume sapuan.
Kesimpulan & Langkah Selanjutnya
MonoGame memberi Anda kemampuan untuk merancang deteksi tabrakan yang sesuai dengan kebutuhan game Anda. Dengan memasukkan pemeriksaan Pixel-Perfect yang mahal di belakang pengujian AABB yang cepat, memanfaatkan Squared Distances untuk lingkaran, dan menghilangkan pengumpulan sampah dengan Reusable Scratch Buffer, Anda dapat memberikan kinerja 60/120 FPS yang mulus di konsol pada platform desktop dan seluler.
Lihat game kami di toko aplikasi untuk melihat sistem tabrakan ini beraksi, dan mulailah menerapkan pola ini dalam proyek MonoGame Anda hari ini!
Tautan & Sumber Toko
- Diblokir: Pixel Panzer di Google Play: Unduh untuk Android
- Diblokir: Pixel Panzer di Microsoft Store: Unduh untuk Windows
- Kerangka MonoGame: monogame.net
Kata Kunci & Tagar SEO
Kata kunci: Deteksi tabrakan 2D MonoGame, pengembangan game C#, Rectangle.Intersects MonoGame, tabrakan sempurna piksel C#, MonoGame kisi hash spasial, tabrakan kontinu sinar sapuan, pengoptimalan performa game indie, game loop alokasi nol, pengoptimalan Android MonoGame, Game Arar, Panzer Piksel yang Diblokir, Paint Trek.
#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixelPanzer #PaintTrek #ArarGames #GameProgramming