مونو گیم میں # 2D تصادم کا پتہ لگانا: بنیادی مستطیل سے زیرو الاوکیشن پکسل پرفیکٹ سسٹم تک
Unity یا Godot جیسے بصری انجنوں میں 2D گیمز بناتے وقت، تصادم کا پتہ لگانا اکثر انسپکٹر چیک باکسز کی ایک سیریز کی طرح محسوس ہوتا ہے: آپ BoxCollider2D یا CircleCollider2D پر تھپڑ مارتے ہیں، ایک Rigidbody جوڑتے ہیں، اور امید کرتے ہیں کہ اندرونی طبیعیات کا مرحلہ موبائل آلات پر نہیں ہچکچاتا۔
MonoGame اور C# میں، تاہم، آپ مکمل کنٹرول میں ہیں۔ کوئی پوشیدہ طبیعیات اوور ہیڈ، کوئی ناپسندیدہ گردشی جڑتا نہیں ہے، اور کوئی پراسرار کچرا جمع کرنے (GC) اسپائکس آپ کے فریم بجٹ کو چوری نہیں کرتا ہے۔
Arar گیمز میں، جب ہم نے Blocked: Pixel Panzer اور Paint Trek بنایا، تو ہمارے آرکیڈ گیم لوپس کو سیکڑوں تیز رفتار دشمن کی گولیوں، اینٹوں کے پھٹنے والے گرڈز، گھومنے والے ٹینک برج، فائٹر جیٹ فلائی بائیز، اور پارٹیکل شیلڈز کی ضرورت ہوتی ہے۔ ایک عمومی مقصدی فزکس انجن سوال سے باہر تھا — ہمیں ایک مقصد سے بنایا ہوا، ٹائرڈ ٹکراؤ فن تعمیر کی ضرورت تھی۔
اس جامع، کوڈ سے چلنے والی گائیڈ میں، ہم MonoGame کے تصادم کی مکمل بنیادی باتوں سے شروع کریں گے (Rectangle. Intersects with realBullet اور Enemy sprites) اور اعلی درجے کے دائرے کی جانچ، مخلوط کلیمپنگ، اینٹی ٹنلنگ رے کاسٹ، پروڈکشن گریڈ P-lixel** اور P-Lixer** زیرو ایلوکیشن اسپیشل گرڈز موبائل جی سی کی بقا کے لیے موزوں ہیں۔
1. فاؤنڈیشن: ایک سادہ مونو گیم سپرائٹ درجہ بندی
تصادم کا پتہ لگانے سے پہلے، ہمیں کلین گیم اداروں کی ضرورت ہے۔ MonoGame میں، ایک ہستی بنیادی طور پر ایک پوزیشن، ایک ساخت، اور ایک باؤنڈنگ مستطیل رکھتی ہے۔
ہمارے گیمز میں استعمال ہونے والا بنیادی ہستی کا فن تعمیر یہ ہے:
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);
}
}
اب آئیے کنکریٹ 'پلیئر'، 'دشمن' اور 'بلٹ' کلاسز بنائیں:
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;
}
}
ان ہستیوں کو اپنی جگہ پر رکھتے ہوئے، آئیے یہ دریافت کرتے ہیں کہ ان کے درمیان ٹکراؤ کا کیسے پتہ لگایا جائے، سب سے آسان طریقہ سے۔
2. سطح 1: سادہ ترین تصادم - `مستطیل۔
MonoGame میں سب سے بنیادی 2D تصادم کی جانچ Axis-Aligned Bounding Box (AABB) ٹیسٹ ہے۔ اصطلاح "محور سے منسلک" کا سیدھا مطلب ہے کہ مستطیل کے کنارے مکمل طور پر اسکرین کے \(X\) اور \(Y\) محور کے متوازی ہیں (کوئی گردش نہیں)۔
MonoGame ایک تیز، بلٹ ان طریقہ فراہم کرتا ہے: Rectangle.Intersects(Rectangle value)۔
کس طرح ہڈ کے نیچے 'مستطیل' انٹرسیکٹس کام کرتا ہے۔
سطح کے نیچے، MonoGame چار عددی موازنہ انجام دیتا ہے:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
اگر چاروں شرائط پوری ہو جائیں تو مستطیل اوورلیپ ہو جاتا ہے۔ اگر ایک شرط بھی ناکام ہو جاتی ہے تو، ایک خالی محور انہیں الگ کر دیتا ہے، اور کوئی تصادم ممکن نہیں ہے۔
اصلی گیم پلے کوڈ: گیم1. اپ ڈیٹ میں گولی بمقابلہ دشمن
یہ ہے کہ آپ فعال گولیوں کی فہرست اور اپنے مرکزی 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);
}
}
کارکردگی کا مشورہ: نوٹس کریں کہ ہم پیچھے کی طرف دہراتے ہیں (
(int i = list.Count - 1; i >= 0; i--))! اگر آپforeachاستعمال کرتے ہیں اور_bullets.Remove(bullet)کو کال کرنے کی کوشش کرتے ہیں تو C# ایکInvalidOperationException: Collection was modifiedپھینک دیتا ہے۔ پیچھے کی طرف اعادہ کرنا میموری کو دوبارہ اشاریہ سازی کے مسائل کے بغیر محفوظ عنصر کو ہٹانے کی اجازت دیتا ہے۔
آرکیڈ سیکریٹ: 'فیئر ہٹ باکسز' بذریعہ 'انفلیٹ'
ریٹرو گیمز جیسے Blocked: Pixel Panzer میں، اسپرائٹ ٹیکسچرز میں اکثر شفاف مارجن یا اینٹینا اسپائکس شامل ہوتے ہیں۔ اگر کھلاڑی کا ٹینک پھٹ جاتا ہے کیونکہ گولی اس کی ساخت کے خالی شفاف کونے کو چھوتی ہے، تو کھلاڑی خود کو دھوکہ دہی کا احساس کرے گا۔
تصادم کو ذمہ دار اور منصفانہ محسوس کرنے کے لیے، گیمز 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. سطح 2: دائرے سے دائرے کا تصادم (گھمنے والی قوت مدافعت)
مستطیل جامد بلاکس اور گرڈ ٹائلوں کے لیے بہت اچھا کام کرتا ہے، لیکن جب اسپرائٹس گھومتے ہیں تو وہ ناکام ہوجاتے ہیں۔ جب ایک غیر مربع جہاز پینٹ ٹریک میں گھومتا ہے، ایک محور سے منسلک باؤنڈنگ باکس کو گھومنے والے کونوں کو گھیرنے کے لیے پھیلانا چاہیے، جس کے نتیجے میں خالی ہوا میں مایوس کن "پریتی تصادم" ہوتا ہے۔
سرکلر ایسٹرائڈز، توانائی کے مداروں کو گھومنے اور گھومنے والے خلائی جہاز کے لیے، باؤنڈنگ سرکلز مثالی حل ہیں۔
اسکوائر روٹ ٹریپ
دو دائرے اس وقت آپس میں ٹکراتے ہیں جب ان کے مراکز کے درمیان فاصلہ ان کے ریڈیائی کے مجموعے سے کم یا برابر ہو:
\(\text{Distance}(C_A, C_B) \le r_A + r_B\)
کوڈ میں، Euclidean فاصلے کا حساب لگانے میں Math.Sqrt (یا Vector2.Distance) استعمال ہوتا ہے۔ تاہم، 200 پروجیکٹائل کے ساتھ ایک لوپ میں مربع جڑوں کا حساب لگانا سینکڑوں غیر ضروری CPU سائیکلوں کو جلا دیتا ہے!
مربع فاصلہ کا مربع رداس جمع سے موازنہ کرکے، ہم مربع جڑ کو مکمل طور پر ختم کرتے ہیں:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
MonoGame نفاذ: Circle بمقابلہ 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);
}
}
اب اسے براہ راست ایک ہستی میں ضم کریں:
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);
}
}
صفر مربع جڑیں، اسپرائٹ کی گردش سے محفوظ، اور بجلی کی تیز رفتار۔
4. سطح 3: مخلوط شکلیں – دائرہ بمقابلہ باکس (MathHelper.Clamp)
کیا ہوتا ہے جب پینٹ ٹریک میں ایک سرکلر اسپیس شپ مستطیل دفاعی رکاوٹوں کی ایک تنگ بھولبلییا سے گزرتا ہے، یا جب گول گولی بلاکڈ: پکسل پینزر میں مربع بلاک سے ٹکراتی ہے؟
ہمیں سرکل بمقابلہ مستطیل ٹکراؤ کی ضرورت ہے۔
کلیمپنگ الگورتھم
حکمت عملی یہ ہے کہ مستطیل پر اس نقطہ کو تلاش کریں جو دائرے کے مرکز کے قریب ترین ہے، اور پھر جانچیں کہ آیا اس قریب ترین نقطہ سے مرکز کا فاصلہ دائرے کے رداس سے کم ہے۔
MonoGame کا 'MathHelper.Clamp' اس کو معمولی بنا دیتا ہے:
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);
}
}
اب آپ صفر مختص اور اعلی درستگی کے ساتھ مستطیل اینٹوں کے خلاف پلیئر شیلڈز کی جانچ کر سکتے ہیں!
5. سطح 4: مسلسل تصادم کا پتہ لگانا (CCD) اور تیز شعاعیں
کیا آپ نے کبھی اپنے گیم میں ہائپر-ویلوسٹی سنائپر راؤنڈ یا ریلگن لیزر فائر کیا ہے، صرف گولی کو جادوئی طور پر دشمن کے پتلے جہاز سے بغیر کسی نقصان کے گزرتے ہوئے دیکھنے کے لیے؟
یہ بگ ٹنلنگ کے نام سے جانا جاتا ہے۔
کیونکہ مجرد گیمز وقت کے مراحل میں اپ ڈیٹ ہوتے ہیں (\(\Delta t = 16.6\text{ms}\) 60 FPS پر)، ایک شے 1,800 پکسلز فی سیکنڈ کی رفتار سے حرکت کرتی ہے **ایک فریم میں 30 پکسلز۔ اگر دشمن کا ہل صرف 15 پکسل موٹا ہے، تو گولی فریم 1 پر دشمن کے سامنے تھی، اور فریم 2 پر دشمن کے بالکل پیچھے تھی۔
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
حل: سویپٹ سیگمنٹ بمقابلہ باکس (سلیب کا طریقہ)
ایک پوائنٹ کو جانچنے کے بجائے، ہم فریم 1 (سابقہ پوزیشن) پر بلٹ کی پوزیشن کو فریم 2 (موجودہ پوزیشن) سے جوڑنے والے پورے لائن سیگمنٹ کی جانچ کرتے ہیں۔
ہمارے ساتھی ٹائٹل 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;
}
}
Blocked: Pixel Panzer میں، کھلاڑی کی مسلسل Laser Beam Skill اس عین مطابق raycast کو ایک بھی تصادم کھوئے بغیر اترتے ہوئے بلاکس کی قطاروں کو کاٹنے کے لیے استعمال کرتی ہے۔
6. لیول 5: پروڈکشن-گریڈ پکسل-پرفیکٹ تصادم کا پتہ لگانا
اب ہم 2D درستگی کی حتمی سطح پر پہنچ گئے ہیں: Pixel-Perfect Collision۔
ریٹرو ٹینک شوٹر یا اسپیس شپ ڈاگ فائٹ میں، فاسد شکلیں (ٹینک بیرل، ونگز، کاک پٹ کاک پٹ) سپرائٹ ٹیکسچر میں شفاف پکسلز سے گھری ہوتی ہیں۔ جب دشمن کا میزائل اس شفاف جگہ سے ٹکراتا ہے تو کھلاڑی فوراً نوٹس لیتے ہیں۔
پکسل پرفیکٹ تصادم اوور لیپنگ ٹیکسچرز کے اصل الفا (شفافیت) چینلز کا معائنہ کرتا ہے۔ اگر دو غیر شفاف پکسلز ایک ہی عالمی کوآرڈینیٹ پر اوورلیپ ہوتے ہیں تو ایک حقیقی جسمانی ہٹ واقع ہوئی ہے۔
مہلک غلطی: Update() کے اندر GetData
بہت سے سبق ابتدائیوں کو ایسا کرنے کی ہدایت کرتے ہیں:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
رن ٹائم کے دوران Texture2D.GetData() کال کرنا CPU کو GPU پائپ لائن کے فلش ہونے کا انتظار کرنے پر مجبور کرتا ہے، ٹیکسچر میموری کو بس پر واپس کھینچتا ہے۔ درجنوں اسپرائٹس کے لیے ایسا کرنے سے ہر فریم آپ کے گیم کو 60 FPS سے 5 FPS تک لے جائے گا اور فوری طور پر Android ANRs کو متحرک کر دے گا!
بلاکڈ: Pixel Panzer کی طرف سے جنگ کا تجربہ شدہ حل
Blocked: Pixel Panzer میں، ہمارا Sprite.cs ایک الٹرا آپٹمائزڈ IntersectsPixel روٹین کو نافذ کرتا ہے جو تقریباً 5 اہم اصولوں پر بنایا گیا ہے:
- Static Color Array Cache:
GetDataکو بالکل ایک بار کہا جاتا ہے جب ٹیکسچر لوڈ ہوتا ہے، اور اسےDictionary<Texture2D, Color[]>میں محفوظ کیا جاتا ہے۔ - AABB Early Exit Guard: اگر
Bounds.Intersects(other.Bounds)غلط ہے تو ہم فوری طور پر بیل آؤٹ کر دیتے ہیں۔ ایک پکسل کا معائنہ کرنے سے پہلے 99% چیک ختم کردیئے جاتے ہیں۔ - ٹیکچر اٹلس اور
SourceRectangleسپورٹ: ماخذ مستطیل آفسیٹس کا استعمال کرتے ہوئے ٹیکسچر اٹلس میں بھرے اسپرائٹس کو ہینڈل کرتا ہے۔ - کیلکولیٹڈ اوورلیپ ونڈو: ہم دونوں اسپرائٹس (
Math.Max(a.Top, b.Top)، وغیرہ) کے درمیان صرف عین تقطیع مستطیل پر لوپ کرتے ہیں۔ - Alpha Short-Circuiting: اگر Sprite A کا پکسل شفاف ہے (
A <= 20)، تو Sprite B کو مکمل طور پر چھوڑ دیا جاتا ہے۔ - پراپرٹی ہوسٹنگ: نیسٹڈ لوپس میں ورچوئل پراپرٹیز (
باؤنڈز) تک رسائی سے ہزاروں ڈھانچہ کاپیاں بنتی ہیں۔ لوپ میں داخل ہونے سے پہلے ہم انہیں مقامی اسٹیک متغیرات میں محفوظ کرتے ہیں۔
یہاں مکمل، پروڈکشن کے لیے تیار کوڈ ہے:
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;
}
}
یہ روٹین جدید اینڈرائیڈ ہارڈویئر اور ڈیسک ٹاپ سسٹمز پر 0.1 ملی سیکنڈ سے کم وقت میں مکمل ہوتی ہے۔
7. لیول 6: براڈ فیز اسپیشل گرڈ اور زیرو ایلوکیشن موبائل جی سی
تصور کریں کہ آپ کے گیم میں 100 بلاکس، 60 گولیاں اور 20 دشمن ہیں۔ اگر آپ nested for loops کا استعمال کرتے ہوئے ہر ہستی کو ہر دوسری ہستی کے خلاف جانچتے ہیں، تو آپ انجام دیتے ہیں:
\(\frac{180 \times 179}{2} = 16,110 \text{ چیک فی فریم!}\)
60 FPS پر، یہ تقریباً 1,000,000 تصادم کی جانچ پڑتال ہر سیکنڈ ہے۔
اپنے گیم کی پیمائش کرنے کے لیے، آپ کو براڈ فیز اسپیشل پارٹیشننگ کو لاگو کرنا ہوگا۔
یونیفارم اسپیشل ہیش گرڈ
ہم اسکرین کو یکساں سیلز کے 2D گرڈ میں تقسیم کرتے ہیں (Blocked: Pixel Panzer میں، ہر سیل \(150 \times 150\) پکسلز ہے)۔ ایک ہستی صرف دوسرے اداروں کے خلاف ٹکراؤ کی جانچ کرتی ہے جو ایک ہی گرڈ سیل میں رہتی ہیں۔
موبائل میموری کا مسئلہ: جی سی تھریشنگ
اگر آپ کا SpatialGrid ہر فریم میں نئی فہرست<پوائنٹ>() یا نئی فہرست<Block>() بناتا ہے، تو آپ منظم ڈھیر پر ہر منٹ میگا بائٹس کوڑا کرکٹ مختص کرتے ہیں۔
اینڈرائیڈ کے مونو رن ٹائم پر، یہ بار بار Gen-0 کوڑے کے مجموعے کو متحرک کرتا ہے، جس کی وجہ سے: `مونو رن ٹائم: مقامی لاک تنازعہ (مونو_کلاس_اس_سب کلاس_)'
گیم سٹٹرز اور گوگل پلے ANR وارننگز کے ساتھ آپ کے گیم کو جھنڈا لگاتے ہیں!
حل: دوبارہ قابل استعمال سکریچ بفرز
یہ ہے Blocked: Pixel Panzer میں CollisionManager.cs سے زیرو ایلوکیشن اسپیشل ہیش گرڈ:
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 اور _scratchCheckedEntities کو دوبارہ استعمال کرنے سے، تصادم کا پورا مرحلہ 0 بائٹس کے ہیپ ایلوکیشن فی فریم کے ساتھ چلتا ہے۔
8. خلاصہ موازنہ جدول
| تکنیک | ریاضی کی لاگت | گردشی معاونت | ٹنلنگ محفوظ؟ | MonoGame میں بہترین استعمال کیس |
|---|---|---|---|---|
AABB (مستطیل۔ ایک دوسرے کو کاٹتا ہے) |
الٹرا فاسٹ (~4 عددی موازنہ) | نہیں | نہیں | گرڈ بلاکس، بلٹ براڈ فیز، UI |
دائرہ (DistanceSquared) |
بہت تیز (3 mls, 0 sqrt) | ہاں (غیر متزلزل) | نہیں | گول جہاز، آگ کے گولے، توانائی کے مدار |
سرکل بمقابلہ باکس (کلیمپ) |
تیز (مقامی میتھ ہیلپر. کلیمپ) | جی ہاں | نہیں | سرکلر پلیئر تنگ دیواروں پر تشریف لے جاتا ہے |
| ** سویپٹ رے (سلیب سی سی ڈی)** | اعتدال پسند (پیرامیٹرک رے کاسٹ) | جی ہاں | جی ہاں | تیز رفتار سنائپر گولیاں، ریل گن، لیزر |
Pixel-Perfect (IntersectsPixel) |
سلیکٹیو (محدود ذیلی رییکٹ) | جی ہاں | نہیں | فاسد سپرائٹ شکلیں، منصفانہ ہٹ باکسز |
| مقامی گرڈ (زیرو-GC) | براڈ فیز (\(O(N^2) \to O(N)\)) | N/A | N/A | گھنی لہریں، بلٹ ہیلز، Android 60 FPS |
حقیقی دنیا کی پروڈکشن شوکیسز: ارار گیمز
یہ تصادم کے فن تعمیرات نظریاتی تجربات نہیں ہیں - یہ ہمارے تجارتی طور پر جاری کردہ عنوانات کو طاقت دینے والی حقیقی انجینئرنگ بنیاد ہیں:
- مسدود: Pixel Panzer: Google Play اور Microsoft Store پر ہمارا ریٹرو ٹینک بقا آرکیڈ گیم۔ اس میں ہمارے مکمل دو درجے کے تصادم کے نظام کی خصوصیات ہیں: ایک صفر مختص مقامی ہیش گرڈ فلٹرنگ گرنے والے بلاکس، اور ٹینکوں، لڑاکا طیاروں، برج کے گولے، اور کلر وہیل عنصری گولہ بارود کے لیے پکسل پرفیکٹ رابطہ چیکنگ۔
- پینٹ ٹریک: ہمارا تیز رفتار خلائی شوٹر جس میں گردشی دائرے کے تصادم، مسلسل رے کاسٹنگ، اور سویپٹ والیوم میزائل دفاعی نظام شامل ہیں۔
نتیجہ اور اگلے اقدامات
MonoGame آپ کو تصادم کا پتہ لگانے کا ڈیزائن بنانے کی طاقت دیتا ہے جو آپ کے گیم کی ضروریات سے بالکل میل کھاتا ہے۔ تیز رفتار AABB ٹیسٹوں کے پیچھے مہنگے Pixel-Perfect چیک کر کے، حلقوں کے لیے مربع فاصلوں کا استعمال کرتے ہوئے، اور دوبارہ قابل استعمال سکریچ بفر کے ساتھ کوڑا کرکٹ کو ختم کرکے، آپ ڈیسک ٹاپ اور موبائل دونوں پلیٹ فارمز پر کنسول کے لیے ہموار 60/120 FPS کارکردگی فراہم کر سکتے ہیں۔
تصادم کے ان نظاموں کو عملی شکل میں دیکھنے کے لیے ایپ اسٹورز پر ہمارے گیمز دیکھیں، اور آج ہی اپنے مونو گیم پروجیکٹس میں ان نمونوں کو نافذ کرنا شروع کریں!
اسٹور لنکس اور وسائل
- مسدود: Google Play پر Pixel Panzer: Android کے لیے ڈاؤن لوڈ کریں
- مسدود: مائیکروسافٹ اسٹور پر Pixel Panzer: Windows کے لیے ڈاؤن لوڈ کریں
- مونوگیم فریم ورک: monogame.net
SEO کلیدی الفاظ اور ہیش ٹیگز
مطلوبہ الفاظ: MonoGame 2D تصادم کا پتہ لگانے، C# گیم ڈیولپمنٹ، Rectangle.Intersects MonoGame، pixel perfect collision C#، مقامی ہیش گرڈ MonoGame، سویپٹ رے مسلسل تصادم، انڈی گیم پرفارمنس آپٹیمائزیشن، زیرو ایلوکیشن گیم، اینڈرائیڈ گیم اپلاک، اینڈرائیڈ گیمز اپلاک، بی آر اوز پینزر، پینٹ ٹریک۔
#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization``#CleanCode #BlockedPixelPanzers #Ars #گیم پروگرامنگ
Hiç yorum yok:
Yorum Gönder