Google Ads etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
Google Ads etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

27 Eylül 2026 Pazar

مونو گیم میں 2D تصادم کا پتہ لگانا: بنیادی مستطیل سے زیرو الاوکیشن پکسل پرفیکٹ سسٹم تک

مونو گیم میں # 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 اہم اصولوں پر بنایا گیا ہے:

  1. Static Color Array Cache: GetData کو بالکل ایک بار کہا جاتا ہے جب ٹیکسچر لوڈ ہوتا ہے، اور اسے Dictionary<Texture2D, Color[]> میں محفوظ کیا جاتا ہے۔
  2. AABB Early Exit Guard: اگر Bounds.Intersects(other.Bounds) غلط ہے تو ہم فوری طور پر بیل آؤٹ کر دیتے ہیں۔ ایک پکسل کا معائنہ کرنے سے پہلے 99% چیک ختم کردیئے جاتے ہیں۔
  3. ٹیکچر اٹلس اور SourceRectangle سپورٹ: ماخذ مستطیل آفسیٹس کا استعمال کرتے ہوئے ٹیکسچر اٹلس میں بھرے اسپرائٹس کو ہینڈل کرتا ہے۔
  4. کیلکولیٹڈ اوورلیپ ونڈو: ہم دونوں اسپرائٹس (Math.Max(a.Top, b.Top)، وغیرہ) کے درمیان صرف عین تقطیع مستطیل پر لوپ کرتے ہیں۔
  5. Alpha Short-Circuiting: اگر Sprite A کا پکسل شفاف ہے (A <= 20)، تو Sprite B کو مکمل طور پر چھوڑ دیا جاتا ہے۔
  6. پراپرٹی ہوسٹنگ: نیسٹڈ لوپس میں ورچوئل پراپرٹیز (باؤنڈز) تک رسائی سے ہزاروں ڈھانچہ کاپیاں بنتی ہیں۔ لوپ میں داخل ہونے سے پہلے ہم انہیں مقامی اسٹیک متغیرات میں محفوظ کرتے ہیں۔

یہاں مکمل، پروڈکشن کے لیے تیار کوڈ ہے:

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 کارکردگی فراہم کر سکتے ہیں۔

تصادم کے ان نظاموں کو عملی شکل میں دیکھنے کے لیے ایپ اسٹورز پر ہمارے گیمز دیکھیں، اور آج ہی اپنے مونو گیم پروجیکٹس میں ان نمونوں کو نافذ کرنا شروع کریں!


اسٹور لنکس اور وسائل


SEO کلیدی الفاظ اور ہیش ٹیگز

مطلوبہ الفاظ: MonoGame 2D تصادم کا پتہ لگانے، C# گیم ڈیولپمنٹ، Rectangle.Intersects MonoGame، pixel perfect collision C#، مقامی ہیش گرڈ MonoGame، سویپٹ رے مسلسل تصادم، انڈی گیم پرفارمنس آپٹیمائزیشن، زیرو ایلوکیشن گیم، اینڈرائیڈ گیم اپلاک، اینڈرائیڈ گیمز اپلاک، بی آر اوز پینزر، پینٹ ٹریک۔

#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization``#CleanCode #BlockedPixelPanzers #Ars #گیم پروگرامنگ

మోనోగేమ్‌లో 2D కొలిషన్ డిటెక్షన్: ప్రాథమిక దీర్ఘచతురస్రాల నుండి జీరో-అలొకేషన్ పిక్సెల్-పర్ఫెక్ట్ సిస్టమ్స్ వరకు

మోనోగేమ్‌లో # 2D కొలిషన్ డిటెక్షన్: ప్రాథమిక దీర్ఘచతురస్రాల నుండి జీరో-అలొకేషన్ పిక్సెల్-పర్ఫెక్ట్ సిస్టమ్స్ వరకు

Unity లేదా Godot వంటి విజువల్ ఇంజిన్‌లలో 2D గేమ్‌లను రూపొందించేటప్పుడు, తాకిడిని గుర్తించడం అనేది తరచుగా ఇన్‌స్పెక్టర్ చెక్‌బాక్స్‌ల శ్రేణిలా అనిపిస్తుంది: మీరు BoxCollider2D లేదా CircleCollider2Dపై స్లాప్ చేసి, Rigidbodyని జోడించి, మొబైల్ పరికరాల్లో అంతర్గత భౌతిక దశ నత్తిగా ఉండదని ఆశిస్తున్నాము.

అయితే MonoGame మరియు **C#**లో, మీరు పూర్తి నియంత్రణలో ఉన్నారు. మీ ఫ్రేమ్ బడ్జెట్‌ను దొంగిలించే రహస్య భౌతిక శాస్త్రం, అవాంఛిత భ్రమణ జడత్వం మరియు రహస్యమైన చెత్త సేకరణ (GC) స్పైక్‌లు లేవు.

Arar Gamesలో, మేము బ్లాక్ చేయబడినవి: Pixel Panzer మరియు Paint Trekని నిర్మించినప్పుడు, మా ఆర్కేడ్ గేమ్ లూప్‌లు వందలాది హై-స్పీడ్ శత్రువుల బుల్లెట్‌లు, పేలుతున్న ఇటుక గ్రిడ్‌లు, తిరిగే ట్యాంక్ టర్రెట్‌లు, ఫైటర్ జెట్ ఫ్లైబైలు మరియు పార్టికల్ షీల్డ్‌లను ప్రాసెస్ చేయడానికి అవసరం. సాధారణ-ప్రయోజన భౌతిక శాస్త్ర ఇంజిన్ ప్రశ్నార్థకం కాదు-మాకు ఉద్దేశ్యంతో నిర్మించబడిన, టైర్డ్ తాకిడి నిర్మాణం అవసరం.

ఈ సమగ్రమైన, కోడ్-ఆధారిత గైడ్‌లో, మేము మోనోగేమ్ తాకిడి (నిజమైన బుల్లెట్ మరియు ఎనిమీ స్ప్రిట్‌లతో Rectangle.Intersects) యొక్క సంపూర్ణ బేసిక్స్ నుండి ప్రారంభిస్తాము మరియు అధునాతన సర్కిల్ తనిఖీలు, మిశ్రమ బిగింపు, యాంటీ టన్నెలింగ్ రేకాస్ట్‌లు, కొల్సెల్-గ్రేడ్ **P, ఉత్పత్తి-గ్రేడ్ **P జీరో-అలొకేషన్ స్పేషియల్ గ్రిడ్‌లు మొబైల్ GC మనుగడ కోసం ఆప్టిమైజ్ చేయబడింది.


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: ది సింప్లెస్ట్ కొలిజన్ – రెక్టాంగిల్.ఇంటర్‌సెక్ట్స్ (AABB)

మోనోగేమ్‌లో అత్యంత ప్రాథమిక 2D తాకిడి తనిఖీ యాక్సిస్-అలైన్డ్ బౌండింగ్ బాక్స్ (AABB) పరీక్ష. "యాక్సిస్-అలైన్డ్" అనే పదం అంటే దీర్ఘచతురస్రం యొక్క అంచులు స్క్రీన్ యొక్క \(X\) మరియు \(Y\) అక్షాలకు పూర్తిగా సమాంతరంగా ఉంటాయి (భ్రమణం లేదు).

MonoGame వేగవంతమైన, అంతర్నిర్మిత పద్ధతిని అందిస్తుంది: Rectangle.Intersects(Rectangle value).

హుడ్ కింద Rectangle.Intersects ఎలా పని చేస్తుంది

ఉపరితలం కింద, MonoGame నాలుగు పూర్ణాంక పోలికలను అమలు చేస్తుంది:

public bool Intersects(Rectangle value)
{
    return value.Left < this.Right &&
           this.Left < value.Right &&
           value.Top < this.Bottom &&
           this.Top < value.Bottom;
}

నాలుగు షరతులు నెరవేరినట్లయితే, దీర్ఘచతురస్రాలు అతివ్యాప్తి చెందుతాయి. ఒక షరతు కూడా విఫలమైతే, ఖాళీ అక్షం వాటిని వేరు చేస్తుంది మరియు ఘర్షణ సాధ్యం కాదు.

నిజమైన గేమ్‌ప్లే కోడ్: గేమ్1.అప్‌డేట్లో బుల్లెట్ వర్సెస్ ఎనిమీ

మీ ప్రధాన మోనోగేమ్ అప్‌డేట్ లూప్‌లోని క్రియాశీల బుల్లెట్‌ల జాబితా మరియు క్రియాశీల శత్రువుల జాబితా మధ్య ఘర్షణలను మీరు ఎలా తనిఖీ చేస్తారో ఇక్కడ ఉంది:

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: సేకరణ సవరించబడింది అని విసురుతుంది. వెనుకకు మళ్ళించడం మెమరీ రీ-ఇండెక్సింగ్ సమస్యలు లేకుండా సురక్షితమైన మూలకాన్ని తీసివేయడానికి అనుమతిస్తుంది.

ఆర్కేడ్ సీక్రెట్: ఇన్‌ఫ్లేట్ ద్వారా "ఫెయిర్ హిట్‌బాక్స్‌లు"

బ్లాక్ చేయబడింది: Pixel Panzer వంటి రెట్రో గేమ్‌లలో, స్ప్రైట్ అల్లికలు తరచుగా పారదర్శక మార్జిన్‌లు లేదా యాంటెన్నా స్పైక్‌లను కలిగి ఉంటాయి. బుల్లెట్ దాని ఆకృతిలోని ఖాళీ పారదర్శక మూలను తాకినందున ఆటగాడి ట్యాంక్ పేలినట్లయితే, ఆటగాడు మోసపోయినట్లు భావిస్తాడు.

తాకిడి ప్రతిస్పందించేలా మరియు సరసమైన అనుభూతిని కలిగించడానికి, గేమ్‌లు Rectangle.Inflateని ఉపయోగించి స్ప్రైట్ లోపల చిన్న Hitboxని ఉపయోగిస్తాయి:

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\)

కోడ్‌లో, యూక్లిడియన్ దూరాన్ని గణించడం Math.Sqrt (లేదా Vector2.Distance)ని ఉపయోగిస్తుంది. అయితే, 200 ప్రక్షేపకాలతో కూడిన లూప్‌లో వర్గమూలాలను లెక్కించడం వల్ల వందల కొద్దీ అనవసరమైన CPU సైకిళ్లు బర్న్ అవుతాయి!

స్క్వేర్డ్ దూరంని స్క్వేర్ వ్యాసార్థం మొత్తంతో పోల్చడం ద్వారా, మేము వర్గమూలాన్ని పూర్తిగా తొలగిస్తాము:

\(\text{DistanceSquared} \le (r_A + r_B)^2\)

మోనోగేమ్ అమలు: సర్కిల్ వర్సెస్ సర్కిల్

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 (ప్రస్తుత స్థానం)కి అనుసంధానించే మొత్తం లైన్ సెగ్మెంట్‌ను మేము పరీక్షిస్తాము.

మా సహచర శీర్షిక స్పైరల్ వార్ నుండి ఉత్పత్తి రేకాస్టింగ్ స్లాబ్-ఖండన పద్ధతి ఇక్కడ ఉంది:

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;
    }
}

బ్లాక్ చేయబడింది: పిక్సెల్ పంజెర్లో, ప్లేయర్ యొక్క నిరంతర లేజర్ బీమ్ నైపుణ్యం ఈ ఖచ్చితమైన రేకాస్ట్‌ని అవరోహణ బ్లాక్‌ల వరుసలను ఒక్క ఢీకొనకుండా స్లైస్ చేయడానికి ఉపయోగిస్తుంది.


6. స్థాయి 5: ఉత్పత్తి-గ్రేడ్ పిక్సెల్-పర్ఫెక్ట్ కొలిషన్ డిటెక్షన్

ఇప్పుడు మనం 2D ఖచ్చితత్వం యొక్క అంతిమ స్థాయికి చేరుకున్నాము: Pixel-Perfect Collision.

రెట్రో ట్యాంక్ షూటర్ లేదా స్పేస్‌షిప్ డాగ్‌ఫైట్‌లో, క్రమరహిత ఆకారాలు (ట్యాంక్ బారెల్స్, రెక్కలు, కాక్‌పిట్ కాక్‌పిట్‌లు) చుట్టూ స్ప్రైట్ ఆకృతిలో పారదర్శక పిక్సెల్‌లు ఉంటాయి. శత్రువు క్షిపణి ఆ పారదర్శక స్థలాన్ని తాకినప్పుడు, ఆటగాళ్ళు వెంటనే గమనిస్తారు.

Pixel-perfect collision అనేది అతివ్యాప్తి చెందుతున్న అల్లికల యొక్క వాస్తవ ఆల్ఫా (పారదర్శకత) ఛానెల్‌లను తనిఖీ చేస్తుంది. ఒకే ప్రపంచ కోఆర్డినేట్‌లో రెండు పారదర్శకత లేని పిక్సెల్‌లు అతివ్యాప్తి చెందితే, నిజమైన భౌతిక హిట్ ఏర్పడింది.

ది ఫాటల్ మిస్టేక్: అప్‌డేట్() లోపల 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 ANRలను ట్రిగ్గర్ చేస్తుంది!

బ్లాక్ చేయబడింది: పిక్సెల్ పంజెర్ నుండి యుద్ధం-పరీక్షించిన పరిష్కారం

బ్లాక్ చేయబడింది: Pixel Panzerలో, మా Sprite.cs 5 కీలకమైన నియమాలతో రూపొందించబడిన అల్ట్రా-ఆప్టిమైజ్ చేసిన IntersectsPixel దినచర్యను అమలు చేస్తుంది:

. 2. AABB ఎర్లీ ఎగ్జిట్ గార్డ్: బౌండ్స్.ఇంటర్‌సెక్ట్స్(ఇతర సరిహద్దులు) తప్పు అయితే, మేము వెంటనే బెయిల్ అవుట్ చేస్తాము. ఒకే పిక్సెల్‌ని తనిఖీ చేయడానికి ముందు 99% చెక్‌లు తొలగించబడతాయి. 3. టెక్చర్ అట్లాస్ & సోర్స్ రెక్టాంగిల్ సపోర్ట్: సోర్స్ రెక్టాంగిల్ ఆఫ్‌సెట్‌లను ఉపయోగించి ఆకృతి అట్లాస్‌లలో ప్యాక్ చేయబడిన స్ప్రిట్‌లను హ్యాండిల్ చేస్తుంది. 4. గణించబడిన అతివ్యాప్తి విండో: మేము రెండు స్ప్రిట్‌ల మధ్య ఖచ్చితమైన ఖండన దీర్ఘచతురస్రాన్ని మాత్రమే లూప్ చేస్తాము (Math.Max(a.Top, b.Top), మొదలైనవి). 5. ఆల్ఫా షార్ట్-సర్క్యూటింగ్: స్ప్రైట్ A యొక్క పిక్సెల్ పారదర్శకంగా ఉంటే (A <= 20), స్ప్రైట్ B పూర్తిగా దాటవేయబడుతుంది. 6. ప్రాపర్టీ హాయిస్టింగ్: సమూహ లూప్‌లలో వర్చువల్ ప్రాపర్టీలను (సరిహద్దులు) యాక్సెస్ చేయడం వల్ల వేలకొద్దీ స్ట్రక్ట్ కాపీలు ఏర్పడతాయి. లూప్‌లోకి ప్రవేశించే ముందు మేము వాటిని స్థానిక స్టాక్ వేరియబుల్స్‌లో నిల్వ చేస్తాము.

పూర్తి, ఉత్పత్తికి సిద్ధంగా ఉన్న కోడ్ ఇక్కడ ఉంది:

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: బ్రాడ్-ఫేజ్ స్పేషియల్ గ్రిడ్ & జీరో-అలొకేషన్ మొబైల్ GC

మీ గేమ్‌లో 100 బ్లాక్‌లు, 60 బుల్లెట్‌లు మరియు 20 మంది శత్రువులు ఉన్నట్లు ఊహించుకోండి. మీరు నెస్టెడ్ ఫర్ లూప్‌లను ఉపయోగించి ప్రతి ఇతర ఎంటిటీకి వ్యతిరేకంగా ప్రతి ఎంటిటీని పరీక్షిస్తే, మీరు ఇలా చేస్తారు:

\(\frac{180 \times 179}{2} = 16,110 \text{ ఒక్కో ఫ్రేమ్‌కి తనిఖీలు!}\)

60 FPS వద్ద, అంటే ప్రతి సెకనుకు దాదాపు 1,000,000 తాకిడి తనిఖీలు.

మీ గేమ్‌ను స్కేల్ చేయడానికి, మీరు తప్పనిసరిగా బ్రాడ్-ఫేజ్ స్పేషియల్ పార్టిషనింగ్ని అమలు చేయాలి.

ఏకరీతి ప్రాదేశిక హాష్ గ్రిడ్

మేము స్క్రీన్‌ను ఏకరీతి కణాల 2D గ్రిడ్‌గా విభజిస్తాము (బ్లాక్ చేయబడింది: Pixel Panzerలో, ప్రతి సెల్ \(150 \times 150\) పిక్సెల్‌లు). ఒక ఎంటిటీ అదే గ్రిడ్ సెల్‌లలో ఉండే ఇతర ఎంటిటీలకు వ్యతిరేకంగా ఘర్షణలను మాత్రమే పరీక్షిస్తుంది.

మొబైల్ మెమరీ సమస్య: GC త్రాషింగ్

మీ SpatialGrid ప్రతి ఫ్రేమ్‌లో కొత్త జాబితా<Point>() లేదా కొత్త జాబితా<Block>()ని సృష్టిస్తే, మీరు నిర్వహించబడే కుప్పలో ప్రతి నిమిషం మెగాబైట్‌ల చెత్తను కేటాయిస్తారు.

ఆండ్రాయిడ్ మోనో రన్‌టైమ్‌లో, ఇది తరచుగా Gen-0 చెత్త సేకరణలను ప్రేరేపిస్తుంది, దీనివల్ల: మోనో రన్‌టైమ్: స్థానిక లాక్ వివాదం (mono_class_is_subclass_)

గేమ్ నత్తిగా మాట్లాడుతుంది మరియు Google Play మీ గేమ్‌ను ANR హెచ్చరికలతో ఫ్లాగ్ చేస్తుంది!

పరిష్కారం: పునర్వినియోగ స్క్రాచ్ బఫర్‌లు

బ్లాక్ చేయబడింది: 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. సారాంశం పోలిక పట్టిక

సాంకేతికత గణిత వ్యయం భ్రమణ మద్దతు టన్నెలింగ్ సురక్షితమా? మోనోగేమ్‌లో బెస్ట్ యూజ్ కేస్
AABB (Rectangle.Intersects) అల్ట్రా-ఫాస్ట్ (~4 పూర్ణాంక పోలికలు) కాదు కాదు గ్రిడ్ బ్లాక్‌లు, బుల్లెట్ బ్రాడ్-ఫేజ్, UI
సర్కిల్ (డిస్టెన్స్ స్క్వేర్డ్) చాలా వేగంగా (3 ముల్లులు, 0 చ.వ.) అవును (ఇన్వేరియంట్) కాదు గుండ్రని నౌకలు, ఫైర్‌బాల్స్, ఎనర్జీ ఆర్బ్స్
సర్కిల్ vs బాక్స్ (క్లాంప్) ఫాస్ట్ (స్థానిక గణిత హెల్పర్.క్లాంప్) అవును కాదు గట్టి గోడలకు నావిగేట్ చేస్తున్న వృత్తాకార ప్లేయర్
స్వీప్ట్ రే (స్లాబ్ CCD) మితమైన (పారామెట్రిక్ రేకాస్ట్) అవును అవును వేగవంతమైన స్నిపర్ బుల్లెట్‌లు, రైల్‌గన్‌లు, లేజర్‌లు
Pixel-పర్ఫెక్ట్ (IntersectsPixel) సెలెక్టివ్ (పరిమితం చేయబడిన సబ్-రెక్ట్) అవును కాదు క్రమరహిత స్ప్రైట్ ఆకృతులు, సరసమైన హిట్‌బాక్స్‌లు
స్పేషియల్ గ్రిడ్ (జీరో-జిసి) విస్తృత-దశ (\(O(N^2) \ to O(N)\)) N/A N/A దట్టమైన అలలు, బుల్లెట్ హెల్స్, ఆండ్రాయిడ్ 60 FPS

రియల్-వరల్డ్ ప్రొడక్షన్ షోకేస్‌లు: అరార్ గేమ్‌లు

ఈ తాకిడి నిర్మాణాలు సైద్ధాంతిక ప్రయోగాలు కావు-అవి మా వాణిజ్యపరంగా విడుదల చేయబడిన శీర్షికలకు శక్తినిచ్చే నిజమైన ఇంజనీరింగ్ పునాది:

  • బ్లాక్ చేయబడింది: Pixel Panzer: Google Play మరియు Microsoft Storeలో మా రెట్రో ట్యాంక్ సర్వైవల్ ఆర్కేడ్ గేమ్. ఇది మా పూర్తి రెండు-స్థాయి తాకిడి వ్యవస్థను కలిగి ఉంది: జీరో-కేటాయింపు ప్రాదేశిక హాష్ గ్రిడ్ ఫిల్టరింగ్ ఫాలింగ్ బ్లాక్‌లు మరియు ట్యాంక్‌లు, ఫైటర్ జెట్‌లు, టరెట్ షెల్‌లు మరియు కలర్‌వీల్ ఎలిమెంటల్ మందుగుండు సామగ్రి కోసం పిక్సెల్-పర్ఫెక్ట్ కాంటాక్ట్ చెకింగ్.
  • పెయింట్ ట్రెక్: మా వేగవంతమైన స్పేస్ షూటర్ రొటేషనల్ సర్కిల్ తాకిడి, నిరంతర రేకాస్టింగ్ మరియు స్వెప్ట్-వాల్యూమ్ క్షిపణి రక్షణ వ్యవస్థలను కలిగి ఉంటుంది.

ముగింపు & తదుపరి దశలు

MonoGame మీ గేమ్ అవసరాలకు సరిగ్గా సరిపోయే ఘర్షణ గుర్తింపును రూపొందించే శక్తిని మీకు అందిస్తుంది. వేగవంతమైన AABB పరీక్షల వెనుక ఖరీదైన పిక్సెల్-పర్ఫెక్ట్ చెక్‌లను పొందడం ద్వారా, సర్కిల్‌ల కోసం స్క్వేర్డ్ డిస్టెన్స్ని ఉపయోగించడం ద్వారా మరియు పునరుపయోగించదగిన స్క్రాచ్ బఫర్‌లుతో చెత్త సేకరణను తొలగించడం ద్వారా, మీరు రెండు డె.స్క్‌టాప్ ప్లాట్‌ఫారమ్‌లలో కన్సోల్-స్మూత్ 60/120 FPS పనితీరును అందించవచ్చు.

ఈ తాకిడి సిస్టమ్‌లు చర్యలో ఉన్నాయని చూడటానికి యాప్ స్టోర్‌లలో మా గేమ్‌లను చూడండి మరియు ఈరోజు మీ స్వంత MonoGame ప్రాజెక్ట్‌లలో ఈ నమూనాలను అమలు చేయడం ప్రారంభించండి!


స్టోర్ లింక్‌లు & వనరులు


SEO కీవర్డ్‌లు & హ్యాష్‌ట్యాగ్‌లు

కీవర్డ్‌లు: MonoGame 2D కొలిషన్ డిటెక్షన్, C# గేమ్ డెవలప్‌మెంట్, Rectangle.Intersects MonoGame, pixel perfect collision C#, స్పేషియల్ హాష్ గ్రిడ్ MonoGame, స్వెప్ట్ రే కంటిన్యూస్ కొలిషన్, ఇండీ గేమ్ పెర్ఫార్మెన్స్ ఆప్టిమైజేషన్, లూప్రో గేమ్ ఆప్టిమైజేషన్, లూప్రో గేమ్ ఆండ్రాయిడ్ ఆప్టిమైజేషన్ బ్లాక్ చేయబడిన Pixel Panzer, Paint Trek.

#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixel``PanzerPixel #గేమ్‌ప్రోగ్రామింగ్

மோனோகேமில் 2டி மோதல் கண்டறிதல்: அடிப்படை செவ்வகங்களிலிருந்து பூஜ்ஜிய-ஒதுக்கீடு பிக்சல்-சரியான அமைப்புகள் வரை

மோனோகேமில் # 2டி மோதல் கண்டறிதல்: அடிப்படை செவ்வகங்களிலிருந்து பூஜ்ஜிய-ஒதுக்கீடு பிக்சல்-சரியான அமைப்புகள் வரை

Unity அல்லது Godot போன்ற விஷுவல் இன்ஜின்களில் 2D கேம்களை உருவாக்கும்போது, மோதல் கண்டறிதல் என்பது பல இன்ஸ்பெக்டர் தேர்வுப்பெட்டிகளைப் போல் உணர்கிறது: நீங்கள் BoxCollider2D அல்லது CircleCollider2D மீது அறைந்து, Rigidbody ஐ இணைக்கவும், மேலும் மொபைல் சாதனங்களில் உள்ளக இயற்பியல் படி தடுமாறாமல் இருக்கும் என நம்புகிறேன்.

இருப்பினும், MonoGame மற்றும் C# இல், நீங்கள் முழு கட்டுப்பாட்டில் உள்ளீர்கள். மறைக்கப்பட்ட இயற்பியல் எதுவும் இல்லை, தேவையற்ற சுழற்சி நிலைத்தன்மையும் இல்லை, மேலும் உங்கள் பிரேம் பட்ஜெட்டைத் திருடும் மர்மமான குப்பை சேகரிப்பு (GC) கூர்முனை எதுவும் இல்லை.

Arar Games இல், நாங்கள் கட்டமைத்த போது Blocked: Pixel Panzer மற்றும் Paint Trek, எங்கள் ஆர்கேட் கேம் லூப்கள் நூற்றுக்கணக்கான அதிவேக எதிரி தோட்டாக்கள், வெடிக்கும் செங்கல் கட்டங்கள், சுழலும் தொட்டி கோபுரங்கள், போர் ஜெட் ஃப்ளைபைகள் மற்றும் துகள் கவசங்கள் 1 Android மற்றும் Flybys ஆகிய 1 ஆண்ட்ராய்டு சாதனங்களில் Flybys 2 Windows மற்றும் Flybys. ஒரு பொது-நோக்க இயற்பியல் இயந்திரம் கேள்விக்குரியதாக இல்லை-எங்களுக்கு ஒரு நோக்கத்திற்காக கட்டமைக்கப்பட்ட, அடுக்கு மோதல் கட்டமைப்பு தேவை.

இந்த விரிவான, குறியீட்டு-உந்துதல் வழிகாட்டியில், மோனோகேம் மோதலின் முழுமையான அடிப்படைகளிலிருந்து (உண்மையான புல்லட் மற்றும் எனிமி ஸ்பிரைட்களுடன் `செவ்வகம். வெட்டும்') தொடங்கி, மேம்பட்ட வட்டச் சோதனைகள், கலப்பு கிளாம்பிங், ஆண்டி-டன்னலிங் ரேகாஸ்ட்கள், கோலிஸ்-கிரேடு **PPFfect ஆகியவற்றை உருவாக்குவோம். ஜீரோ-ஒதுக்கீடு ஸ்பேஷியல் கிரிட்ஸ் மொபைல் ஜிசி உயிர்வாழ்வதற்காக உகந்ததாக உள்ளது.


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-Alined Bounding Box (AABB) சோதனை ஆகும். "அச்சு-சீரமைக்கப்பட்டது" என்ற வார்த்தையின் அர்த்தம் செவ்வகத்தின் விளிம்புகள் திரையின் \(X\) மற்றும் \(Y\) அச்சுகளுக்கு முற்றிலும் இணையாக இருக்கும் (சுழற்சி இல்லை).

MonoGame ஒரு வேகமான, உள்ளமைக்கப்பட்ட முறையை வழங்குகிறது: செவ்வக. வெட்டும் (செவ்வக மதிப்பு).

எப்படி `செவ்வகம். வெட்டும்' ஹூட் கீழ் வேலை செய்கிறது

மேற்பரப்பின் கீழ், MonoGame நான்கு முழு எண் ஒப்பீடுகளை செயல்படுத்துகிறது:

public bool Intersects(Rectangle value)
{
    return value.Left < this.Right &&
           this.Left < value.Right &&
           value.Top < this.Bottom &&
           this.Top < value.Bottom;
}

நான்கு நிபந்தனைகளும் பூர்த்தி செய்யப்பட்டால், செவ்வகங்கள் ஒன்றுடன் ஒன்று. ஒரு நிபந்தனை கூட தோல்வியுற்றால், ஒரு வெற்று அச்சு அவற்றைப் பிரிக்கிறது, மேலும் மோதல் சாத்தியமில்லை.

உண்மையான விளையாட்டுக் குறியீடு: Game1.Update இல் புல்லட் எதிராக எதிரி

உங்கள் முக்கிய 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);
    }
}

செயல்திறன் உதவிக்குறிப்பு: நாம் பின்னோக்கிச் சொல்கிறோம் (for (int i = list. Count - 1; i >= 0; i--))! நீங்கள் foreach ஐப் பயன்படுத்தி, _bullets.Remove(bullet) ஐ அழைக்க முயற்சித்தால், C# ஆனது InvalidOperationException: Collection modified என்பதை எறிகிறது. பின்னோக்கிச் செயல்படுவது நினைவக மறு அட்டவணைப்படுத்தல் சிக்கல்கள் இல்லாமல் பாதுகாப்பான உறுப்புகளை அகற்ற அனுமதிக்கிறது.

ஆர்கேட் சீக்ரெட்: இன்ஃப்ளேட் வழியாக "ஃபேர் ஹிட்பாக்ஸ்கள்"

Blocked: Pixel Panzer போன்ற ரெட்ரோ கேம்களில், ஸ்ப்ரைட் அமைப்புகளில் பெரும்பாலும் வெளிப்படையான விளிம்புகள் அல்லது ஆண்டெனா ஸ்பைக்குகள் அடங்கும். ஒரு புல்லட் அதன் அமைப்பின் வெற்று வெளிப்படையான மூலையைத் தொட்டதால் வீரரின் தொட்டி வெடித்தால், வீரர் ஏமாற்றப்பட்டதாக உணருவார்.

மோதலைப் பதிலளிக்கக்கூடியதாகவும் நியாயமானதாகவும் உணர, கேம்கள் ஸ்ப்ரைட்டிற்குள் சிறிய ஹிட்பாக்ஸை ரெக்டாங்கிள்.இன்ஃப்ளேட் பயன்படுத்தி பயன்படுத்துகின்றன:

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\)

குறியீட்டில், யூக்ளிடியன் தூரத்தைக் கணக்கிடுவது Math.Sqrt (அல்லது Vector2.Distance) ஐப் பயன்படுத்துகிறது. இருப்பினும், 200 எறிகணைகள் கொண்ட ஒரு சுழற்சியில் சதுர வேர்களைக் கணக்கிடுவது நூற்றுக்கணக்கான தேவையற்ற CPU சுழற்சிகளை எரிக்கிறது!

சதுர தூரத்தை சதுர ஆரம் கூட்டுக்கு எதிராக ஒப்பிடுவதன் மூலம், வர்க்க மூலத்தை முற்றிலுமாக அகற்றுவோம்:

\(\text{DistanceSquared} \le (r_A + r_B)^2\)

மோனோ கேம் செயல்படுத்தல்: வட்டம் எதிராக வட்டம்

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)

பெயிண்ட் ட்ரெக்கில் வட்ட வடிவ விண்கலம் செவ்வக பாதுகாப்பு தடைகளின் இறுக்கமான பிரமை வழியாக செல்லும்போது அல்லது தடுக்கப்பட்ட: Pixel Panzer இல் ஒரு சதுரத் தொகுதியை வட்டமான புல்லட் தாக்கும்போது என்ன நடக்கும்?

நமக்கு வட்டம் மற்றும் செவ்வகம் மோதல் தேவை.

கிளாம்பிங் அல்காரிதம்

வட்டத்தின் மையத்திற்கு மிக அருகில் இருக்கும் செவ்வகத்தின் புள்ளியைக் கண்டறிந்து, அந்த நெருங்கிய புள்ளியிலிருந்து மையத்திற்கான தூரம் வட்டத்தின் ஆரத்தை விடக் குறைவாக உள்ளதா என்பதைச் சோதிப்பதே உத்தி.

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}\) at 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 இல், பிளேயரின் தொடர்ச்சியான லேசர் பீம் திறன் இந்த துல்லியமான ரேகாஸ்டைப் பயன்படுத்தி ஒரு மோதலையும் தவறவிடாமல் இறங்குத் தொகுதிகளின் வரிசைகளில் வெட்டுகிறது.


6. நிலை 5: உற்பத்தி-கிரேடு பிக்சல்-சரியான மோதல் கண்டறிதல்

இப்போது நாம் 2D துல்லியத்தின் இறுதி நிலையை அடைகிறோம்: பிக்சல்-பெர்ஃபெக்ட் மோதல்.

ரெட்ரோ டேங்க் ஷூட்டர் அல்லது ஸ்பேஸ்ஷிப் நாய் சண்டையில், ஒழுங்கற்ற வடிவங்கள் (டேங்க் பீப்பாய்கள், இறக்கைகள், காக்பிட் காக்பிட்கள்) ஸ்ப்ரைட் அமைப்பில் வெளிப்படையான பிக்சல்களால் சூழப்பட்டிருக்கும். எதிரி ஏவுகணை அந்த வெளிப்படையான இடத்தைத் தாக்கும் போது, ​​வீரர்கள் உடனடியாக கவனிக்கிறார்கள்.

பிக்சல்-பெர்ஃபெக்ட் மோதலானது, மேலெழும் அமைப்புகளின் உண்மையான ஆல்பா (வெளிப்படைத்தன்மை) சேனல்களை ஆய்வு செய்கிறது. ஒரே உலக ஒருங்கிணைப்பில் இரண்டு வெளிப்படைத்தன்மையற்ற பிக்சல்கள் ஒன்றுடன் ஒன்று சேர்ந்தால், உண்மையான உடல் பாதிப்பு ஏற்படும்.

அபாயகரமான தவறு: புதுப்பிப்பு() க்குள் GetData

பல பயிற்சிகள் ஆரம்பநிலைக்கு இதைச் செய்ய அறிவுறுத்துகின்றன:

// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!

இயக்க நேரத்தின் போது Texture2D.GetData() என்று அழைப்பது, GPU பைப்லைன் ஃப்ளஷ் ஆகும் வரை CPU காத்திருக்கும், பஸ்ஸின் மேல் டெக்ஸ்ச்சர் நினைவகத்தை இழுக்கிறது. ஒவ்வொரு ஃப்ரேமிலும் டஜன் கணக்கான ஸ்பிரிட்டுகளுக்கு இதைச் செய்வது உங்கள் கேமை 60 FPS இலிருந்து 5 FPS ஆக உயர்த்தி, Android ANRகளை உடனடியாகத் தூண்டும்!

Blocked: Pixel Panzer இலிருந்து போர்-சோதனை செய்யப்பட்ட தீர்வு

Blocked: Pixel Panzer இல், எங்கள் Sprite.cs ஆனது 5 முக்கியமான விதிகளைச் சுற்றி கட்டமைக்கப்பட்ட அல்ட்ரா-உகந்த IntersectsPixel வழக்கத்தை செயல்படுத்துகிறது:

  1. நிலையான வண்ண வரிசை கேச்: அமைப்பு ஏற்றப்படும்போது GetData சரியாக ஒருமுறை அழைக்கப்படுகிறது, மேலும் Dictionary<Texture2D, Color[]> இல் சேமிக்கப்படும்.
  2. AABB Early Exit Guard: Bounds.Intersects(other.Bounds) என்பது தவறானது எனில், நாங்கள் உடனடியாகப் பிணை எடுப்போம். ஒரு பிக்சலை ஆய்வு செய்வதற்கு முன் 99% காசோலைகள் அகற்றப்படும்.
  3. டெக்சர் அட்லஸ் & மூல செவ்வகம் ஆதரவு: மூல செவ்வக ஆஃப்செட்களைப் பயன்படுத்தி டெக்ஸ்சர் அட்லஸ்களில் பேக் செய்யப்பட்ட ஸ்பிரிட்களைக் கையாளுகிறது.
  4. கணக்கிடப்பட்ட ஒன்றுடன் ஒன்று சாளரம்: இரண்டு உருவங்களுக்கு இடையே உள்ள சரியான குறுக்குவெட்டு செவ்வகத்தின் மீது மட்டுமே நாம் லூப் செய்கிறோம் (Math.Max(a.Top, b.Top), முதலியன).
  5. ஆல்ஃபா ஷார்ட் சர்க்யூட்டிங்: ஸ்ப்ரைட் A இன் பிக்சல் வெளிப்படையானதாக இருந்தால் (A <= 20), Sprite B முற்றிலும் தவிர்க்கப்படும்.
  6. சொத்து ஏற்றுதல்: உள்ளமைக்கப்பட்ட சுழல்களில் மெய்நிகர் பண்புகளை (எல்லைகள்) அணுகுவது ஆயிரக்கணக்கான struct நகல்களை உருவாக்குகிறது. லூப்பில் நுழைவதற்கு முன் அவற்றை லோக்கல் ஸ்டேக் மாறிகளில் சேமித்து வைக்கிறோம்.

இங்கே முழுமையான, உற்பத்திக்கு தயாராக உள்ள குறியீடு:

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 எதிரிகள் இருப்பதாக கற்பனை செய்து பாருங்கள். உள்ளமைக்கப்பட்ட ஃபார் லூப்களைப் பயன்படுத்தி மற்ற ஒவ்வொரு நிறுவனத்திற்கும் எதிராக ஒவ்வொரு உட்பொருளையும் சோதித்தால், நீங்கள் இதைச் செய்கிறீர்கள்:

\(\frac{180 \times 179}{2} = 16,110 \text{ ஒரு சட்டகத்திற்கு சோதனைகள்!}\)

60 FPS இல், அதாவது ஒவ்வொரு வினாடிக்கும் கிட்டத்தட்ட 1,000,000 மோதல் சோதனைகள்.

உங்கள் விளையாட்டை அளவிட, நீங்கள் பரந்த-கட்ட இடப் பகிர்வை செயல்படுத்த வேண்டும்.

சீரான இடஞ்சார்ந்த ஹாஷ் கட்டம்

திரையை ஒரே மாதிரியான கலங்களின் 2D கட்டமாகப் பிரிக்கிறோம் (தடுக்கப்பட்டவை: Pixel Panzer இல், ஒவ்வொரு கலமும் \(150 \times 150\) pixels ஆகும்). ஒரு நிறுவனம், அதே கட்டக் கலங்களில் வசிக்கும் மற்ற நிறுவனங்களுக்கு எதிரான மோதல்களை மட்டுமே சோதிக்கிறது.

மொபைல் நினைவகப் பிரச்சனை: GC த்ராஷிங்

உங்கள் SpatialGrid புதிய பட்டியல்<Point>() அல்லது புதிய பட்டியல்<Block>() ஒவ்வொரு ஃப்ரேமிலும் உருவாக்கினால், நிர்வகிக்கப்படும் குவியலில் ஒவ்வொரு நிமிடமும் மெகாபைட் குப்பைகளை ஒதுக்குவீர்கள்.

ஆண்ட்ராய்டின் மோனோ இயக்க நேரத்தில், இது அடிக்கடி Gen-0 குப்பை சேகரிப்பைத் தூண்டுகிறது, இதனால்: மோனோ இயக்க நேரம்: நேட்டிவ் லாக் கன்டெண்டிஷன் (mono_class_is_subclass_)

கேம் தடுமாறுகிறது மற்றும் ANR எச்சரிக்கைகளுடன் உங்கள் கேமை Google Play கொடியிடுகிறது!

தீர்வு: மீண்டும் பயன்படுத்தக்கூடிய கீறல் இடையகங்கள்

இதோ தடுக்கப்பட்டது: 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. சுருக்க ஒப்பீட்டு அட்டவணை

நுட்பம் கணித செலவு சுழற்சி ஆதரவு சுரங்கப்பாதை பாதுகாப்பானதா? மோனோ கேமில் சிறந்த பயன்பாட்டு வழக்கு
AABB (`செவ்வக. வெட்டும்') அல்ட்ரா-ஃபாஸ்ட் (~4 முழு எண் ஒப்பீடுகள்) இல்லை இல்லை கட்டம் தொகுதிகள், புல்லட் பரந்த-கட்டம், UI
வட்டம் (DistanceSquared) மிக வேகமாக (3 muls, 0 sqrt) ஆம் (மாறாத) இல்லை சுற்று கப்பல்கள், தீப்பந்தங்கள், ஆற்றல் உருண்டைகள்
வட்டம் vs பெட்டி (கிளாம்ப்) வேகமாக (உள்ளூர் கணித உதவி.கிளாம்ப்) ஆம் இல்லை இறுக்கமான சுவர்களை வழிநடத்தும் வட்ட வீரர்
ஸ்வெப்ட் ரே (ஸ்லாப் சிசிடி) மிதமான (Parametric Raycast) ஆம் ஆம் வேகமான துப்பாக்கி சுடும் தோட்டாக்கள், ரெயில்கன்கள், லேசர்கள்
பிக்சல்-பெர்ஃபெக்ட் (IntersectsPixel) தேர்ந்தெடுக்கப்பட்ட (கட்டுப்படுத்தப்பட்ட துணைத் தொடர்) ஆம் இல்லை ஒழுங்கற்ற ஸ்ப்ரைட் வரையறைகள், நியாயமான ஹிட்பாக்ஸ்கள்
ஸ்பேஷியல் கிரிட் (ஜீரோ-ஜிசி) பரந்த-கட்டம் (\(O(N^2) \to O(N)\)) N/A N/A அடர்த்தியான அலைகள், புல்லட் ஹெல்ஸ், ஆண்ட்ராய்டு 60 FPS

நிஜ உலக உற்பத்தி காட்சிகள்: அரார் கேம்கள்

இந்த மோதல் கட்டமைப்புகள் கோட்பாட்டு சோதனைகள் அல்ல - அவை வணிக ரீதியாக வெளியிடப்பட்ட தலைப்புகளை இயக்கும் உண்மையான பொறியியல் அடித்தளம்:

  • தடுக்கப்பட்டது: Pixel Panzer: Google Play மற்றும் Microsoft Store இல் எங்கள் ரெட்ரோ டேங்க் சர்வைவல் ஆர்கேட் கேம். இது எங்களின் முழுமையான இரு-அடுக்கு மோதல் அமைப்பைக் கொண்டுள்ளது: ஒரு பூஜ்ஜிய-ஒதுக்கீடு இடஞ்சார்ந்த ஹாஷ் கட்டம் வடிகட்டுதல் வீழ்ச்சித் தொகுதிகள், மற்றும் டாங்கிகள், போர் விமானங்கள், சிறு கோபுரம் குண்டுகள் மற்றும் கலர்வீல் அடிப்படை வெடிமருந்துகளுக்கான பிக்சல்-சரியான தொடர்பு சோதனை.
  • பெயிண்ட் ட்ரெக்: எங்கள் வேகமான ஸ்பேஸ் ஷூட்டர் சுழற்சி வட்ட மோதல், தொடர்ச்சியான ரேகாஸ்டிங் மற்றும் ஸ்வீப்ட்-வால்யூம் ஏவுகணை பாதுகாப்பு அமைப்புகளைக் கொண்டுள்ளது.

முடிவு & அடுத்த படிகள்

மோனோகேம் உங்கள் விளையாட்டின் தேவைகளுக்குப் பொருந்தக்கூடிய மோதல் கண்டறிதலை வடிவமைக்கும் ஆற்றலை உங்களுக்கு வழங்குகிறது. வேகமான AABB சோதனைகளுக்குப் பின்னால் விலையுயர்ந்த பிக்சல்-பெர்ஃபெக்ட் சோதனைகள், வட்டங்களுக்கு சதுர தூரங்களைப் பயன்படுத்துதல் மற்றும் மீண்டும் பயன்படுத்தக்கூடிய ஸ்க்ராட்ச் பஃபர்கள் மூலம் குப்பை சேகரிப்பை நீக்குதல், கன்சோல்-மென்மையான 60/120 FPS இயங்குதளங்கள் மற்றும் மொபைல் இயங்குதளங்கள் இரண்டிலும் நீங்கள் வழங்கலாம்.

இந்த மோதல் அமைப்புகளை செயலில் காண ஆப் ஸ்டோர்களில் உள்ள எங்கள் கேம்களைப் பார்க்கவும், மேலும் இந்த வடிவங்களை உங்கள் சொந்த மோனோ கேம் திட்டங்களில் இன்றே செயல்படுத்தத் தொடங்குங்கள்!


ஸ்டோர் இணைப்புகள் & ஆதாரங்கள்


எஸ்சிஓ முக்கிய வார்த்தைகள் & ஹேஷ்டேக்குகள்

** முக்கிய வார்த்தைகள்:** MonoGame 2D மோதலை கண்டறிதல், C# கேம் மேம்பாடு, செவ்வகம். குறுக்கிடுகிறது MonoGame, பிக்சல் சரியான மோதல் C#, ஸ்பேஷியல் ஹாஷ் கிரிட் MonoGame, ஸ்வீப்ட் ரே தொடர்ச்சியான மோதல், இண்டி கேம் செயல்திறன் மேம்படுத்தல், லூப்ரோ கேம் ஆப்டிமைசேஷன், லூப்ரோ கேம் ஆப்டிமைசேஷன், ஆண்ட்ராய்ட் ஆண்ட்ராய்டு ஒதுக்கீடு தடுக்கப்பட்ட Pixel Panzer, Paint Trek.

#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixelPanzarG #கேம் புரோகிராமிங்

2Д детекција колизије у МоноГаме-у: од основних правоугаоника до савршених система пиксела са нултом алокацијом

2Д детекција колизије у МоноГаме-у: од основних правоугаоника до савршених система пиксела са нултом алокацијом

Када правите 2Д игре у визуелним машинама као што су Унити или Годот, откривање колизије често изгледа као низ контролних поља за потврду: ударите БокЦоллидер2Д или ЦирцлеЦоллидер2Д, причврстите Ригидбоди и надате се да интерни корак физике неће застој на мобилним уређајима.

У МоноГаме и Ц#, међутим, имате потпуну контролу. Нема скривених физичких трошкова, нема нежељене ротационе инерције и нема мистериозних скокова за сакупљање смећа (ГЦ) који краду ваш буџет за оквир.

У Арар Гамес, када смо направили Блоцкед: Пикел Панзер и Паинт Трек, наше петље аркадних игара биле су потребне за обраду стотина брзих непријатељских метака, експлодирајуће мреже од цигли, ротирајућих тенкова, прелетања борбених авиона и штитова од честица на Виндовс 1 и 60 ФПС-а на ПЦ уређајима. Физички мотор опште намене није долазио у обзир - била нам је потребна наменска, вишеслојна архитектура судара.

У овом свеобухватном водичу вођеном кодом, почећемо од апсолутних основа колизије МоноГаме-а („Правоугаоник. Укршта се“ са правим Метак и Непријатељ спријтовима) и надоградити до напредних провера кругова, мешовитог стезања, анти-тунелирања раицаст-а, продукционог нивоа **П-ерфецтион-а, Алло Пик Просторне мреже оптимизоване за преживљавање мобилног ГЦ-а.


1. Тхе Фоундатион: Једноставна МоноГаме Сприте хијерархија

Пре откривања судара, потребни су нам чисти ентитети игре. У МоноГаме-у, ентитет у основи поседује позицију, текстуру и гранични правоугаоник.

Ево основне архитектуре ентитета која се користи у нашим играма:

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: Најједноставнији судар – Правоугаоник. Пресецања (ААББ)

Најосновнија 2Д провера колизије у МоноГаме-у је тест Акис-Алигнед Боундинг Бок (ААББ). Термин „поравнано по оси“ једноставно значи да су ивице правоугаоника потпуно паралелне са осама \(Кс\) и \(И\) екрана (без ротације).

МоноГаме пружа брзу, уграђену методу: Рецтангле.Интерсецтс(Рецтангле валуе).

Како Рецтангле.Интерсетс ради испод хаубе

Испод површине, МоноГаме извршава четири целобројна поређења:

public bool Intersects(Rectangle value)
{
    return value.Left < this.Right &&
           this.Left < value.Right &&
           value.Top < this.Bottom &&
           this.Top < value.Bottom;
}

Ако су испуњена сва четири услова, правоугаоници се преклапају. Ако чак и један услов не успе, празна оса их раздваја и колизија није могућа.

Прави код игре: Метак против Непријатеља у Гаме1.Упдате

Ево како проверавате колизије између листе активних метака и листе активних непријатеља унутар ваше главне МоноГаме Упдате петље:

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);
    }
}

Савет за перформансе: Приметите да понављамо уназад (фор (инт и = лист.Цоунт - 1; и >= 0; и--))! Ако користите фореацх и покушате да позовете _буллетс.Ремове(буллет), Ц# избацује ИнвалидОператионЕкцептион: колекција је измењена. Итерација уназад омогућава безбедно уклањање елемената без проблема са поновним индексирањем меморије.

Тхе Арцаде Сецрет: "Фаир Хитбокес" преко Инфлате

У ретро играма као што је Блоцкед: Пикел Панзер, текстуре сприте-а често укључују прозирне маргине или антенске шиљке. Ако резервоар играча експлодира јер је метак додирнуо празан провидни угао његове текстуре, играч ће се осећати превареним.

Да би се судар осећао осетљиво и поштено, игре користе мањи Хитбок унутар сприте-а користећи Рецтангле.Инфлате:

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: судар од круга до круга (отпорност на ротацију)

Правоугаоници одлично функционишу за статичне блокове и плочице мреже, али не успевају када се спријтови ротирају. Када се свемирски брод који није квадрата ротира у Паинт Трек, гранична кутија поравната по оси мора да се прошири да би обухватила окретне углове, што доводи до фрустрирајућих „фантомских судара“ у празном ваздуху.

Боундинг Цирцлес су идеално решење за кружне астероиде, кугле за навођење енергије и ротирајуће летелице.

Замка квадратног корена

Два круга се сударају када је растојање између њихових центара мање или једнако збиру њихових полупречника:

\(\тект{Удаљеност}(Ц_А, Ц_Б) \ле р_А + р_Б\)

У коду, израчунавање еуклидске удаљености користи Матх.Скрт (или Вецтор2.Дистанце). Међутим, израчунавање квадратних корена у петљи са 200 пројектила сагорева стотине непотребних ЦПУ циклуса!

Упоређивањем квадрата растојања са збиром квадрата полупречника, потпуно елиминишемо квадратни корен:

\(\тект{ДистанцеСкуаред} \ле (р_А + р_Б)^2\)

Имплементација МоноГаме-а: Круг против Круг

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: Мешани облици – круг против кутије (МатхХелпер.Цламп)

Шта се дешава када се кружни свемирски брод у Паинт Трек креће кроз уски лавиринт правоугаоних одбрамбених баријера или када округли метак погоди квадратни блок у Блоцкед: Пикел Панзер?

Потребан нам је судар Круг против правоугаоника.

Алгоритам стезања

Стратегија је пронаћи тачку на правоугаонику која је најближа центру круга, а затим тестирати да ли је растојање од те најближе тачке до центра мање од полупречника круга.

МоноГаме-ов МатхХелпер.Цламп чини ово тривијалним:

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: Континуирана детекција судара (ЦЦД) и зраци

Да ли сте икада у својој игри испалили снајперски метак хипер-брзине или ласер на шинама, само да бисте гледали како метак магично пролази право кроз танки непријатељски брод без наношења штете?

Ова грешка је позната као тунелирање.

Пошто се дискретне игре ажурирају у временским корацима (\(\Делта т = 16,6\тект{мс}\) при 60 ФПС), објекат који се креће брзином од 1800 пиксела у секунди путује 30 пиксела у једном кадру. Ако је непријатељски труп дебео само 15 пиксела, метак је био испред непријатеља у оквиру 1, а потпуно иза непријатеља на оквиру 2.

Frame 1:  [ Bullet ]  --->       | Enemy Wall |
Frame 2:                          | Enemy Wall |       --->  [ Bullet ]
                               (NO HIT DETECTED!)

Решење: Свепт сегмент вс. Бок (метод плоча)

Уместо да тестирамо једну тачку, тестирамо цео сегмент линије који повезује позицију метка на оквиру 1 (претходна позиција) са оквиром 2 (тренутна позиција).

Ево производне методе пресека плоча за ливење зрака из нашег пратећег наслова СпиралВар:

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;
    }
}

У Блоцкед: Пикел Панзер, играчева континуирана вештина ласерског снопа користи овај тачан раицаст за пресецање низова силазних блокова без пропуштања ниједног судара.


6. Ниво 5: Детекција савршене колизије пиксела у производњи

Сада достижемо врхунски ниво 2Д тачности: Пикел-Перфецт Цоллисион.

У ретро тенковској пуцачи или борби свемирских бродова, неправилни облици (цеви тенкова, крила, кабине кокпита) су окружени провидним пикселима у текстури сприте-а. Када непријатељски пројектил погоди тај провидни простор, играчи то одмах примећују.

Пикел-перфецт цоллисион проверава стварне алфа (транспарентност) канале текстура које се преклапају. Ако се два нетранспарентна пиксела преклапају на истој светској координати, догодио се прави физички погодак.

Фатална грешка: ГетДата унутар Упдате()

Многи туторијали упућују почетнике да то ураде:

// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!

Позивање Тектуре2Д.ГетДата() током времена извршавања приморава ЦПУ да сачека да се ГПУ цевовод испразни, повлачећи меморију текстуре назад преко магистрале. Ако ово урадите за десетине спријтова у сваком кадру, ваша игра ће се убрзати са 60 ФПС на 5 ФПС и одмах покренути Андроид АНР-ове!

Решење тестирано у борби од Блоцкед: Пикел Панзер

У Блоцкед: Пикел Панзер, наш Сприте.цс имплементира ултраоптимизовану ИнтерсецтсПикел рутину изграђену око 5 кључних правила:

  1. Кеш статичког низа боја: ГетДата се позива тачно једном када се текстура учита и чува у Речник<Тектуре2Д, Цолор[]>.
  2. Заштита раног изласка ААББ-а: Ако је Боундс.Интерсецтс(отхер.Боундс) лажна, одмах се ослобађамо. 99% провера се елиминише пре прегледа једног пиксела.
  3. Подршка за Атлас текстуре и Изворни правоугаоник: Рукује спрајтовима упакованим у атласе текстура користећи помаке изворног правоугаоника.
  4. Прозор израчунатог преклапања: Прелазимо само преко тачног правоугаоника пресека између оба спритеа (Матх.Мак(а.Топ, б.Топ), итд.).
  5. Алфа кратко спајање: Ако је пиксел Сприте А провидан (А <= 20), Сприте Б је потпуно прескочен.
  6. Подизање својстава: Приступ виртуелним својствима (Границе) у угнежђеним петљама ствара хиљаде копија структуре. Чувамо их у локалним променљивим стека пре уласка у петљу.

Ево комплетног кода који је спреман за производњу:

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 непријатеља. Ако тестирате сваки ентитет у односу на сваки други ентитет користећи угнежђене фор петље, изводите:

\(\фрац{180 \тимес 179}{2} = 16.110 \тект{ провера по кадру!}\)

При 60 ФПС, то је скоро 1.000.000 провера судара сваке секунде.

Да бисте скалирали своју игру, морате да примените просторно партиционисање широких фаза.

Униформна просторна хеш мрежа

Екран делимо на 2Д мрежу униформних ћелија (у Блокирано: Пикел Панзер, свака ћелија је 150 \(\ пута 150\) пиксела). Ентитет само тестира колизије против других ентитета који се налазе у истим ћелијама мреже.

Проблем са мобилном меморијом: ГЦ Тхрасхинг

Ако ваш СпатиалГрид креира нову листу<тачка>() или нову листу<блок>() сваки оквир, ви додељујете мегабајте смећа сваког минута на управљаној хрпи.

У Андроид-овом Моно времену извођења, ово покреће честа прикупљања смећа Ген-0, узрокујући: моно рунтиме: нативно закључавање сукоба (моно_цласс_ис_субцласс_)

Игра запиње, а Гоогле Плаи означава вашу игру АНР упозорењима!

Решење: вишекратни бафери за огреботине

Ево просторне хеш мреже нулте алокације из ЦоллисионМанагер.цс у Блокирано: Пикел Панзер:

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);
                            }
                        }
                    }
                }
            }
        }
    }
}

Поновним коришћењем _сцратцхЦеллс и _сцратцхЦхецкедЕнтитиес, цео корак колизије се покреће са 0 бајтова алокације гомиле по оквиру.


8. Табела поређења резимеа

Техника Математички трошак Ротациона подршка Туннелинг Сафе? Најбољи случај употребе у МоноГаме-у
ААББ (Правоугаоник. Пресече) Ултра-брзо (~4 поређења целих бројева) Не Не Мрежни блокови, буллет широка фаза, УИ
Круг (ДистанцеСкуаред) Вери Фаст (3 мулс, 0 скрт) Да (Инваријантно) Не Округли бродови, ватрене кугле, енергетске кугле
Круг против кутије (Кламп) Фаст (Лоцал МатхХелпер.Цламп) Да Не Кружни играч који се креће кроз тесне зидове
Свепт Раи (Слаб ЦЦД) Умерено (Параметриц Раицаст) Да Да Брзи снајперски меци, шинске пушке, ласери
Пикел-Перфецт (ИнтерсецтсПикел) Селективно (ограничени под-рек) Да Не Неправилне контуре сприте-а, поштени хитбоксови
Просторна мрежа (Зеро-ГЦ) Широка фаза (\(О(Н^2) \до О(Н)\)) Н/А Н/А Густи таласи, пакао од метака, Андроид 60 ФПС

Реал-Ворлд Продуцтион Сховцасес: Арар Гамес

Ове архитектуре судара нису теоретски експерименти – оне су права инжењерска основа која покреће наше комерцијално објављене наслове:

  • Блокирано: Пикел Панзер: Наша ретро аркадна игра за преживљавање тенка на Гоогле Плаи-у и Мицрософт Сторе-у. Поседује наш комплетан двослојни систем судара: просторну хеш мрежу са нултом алокацијом која филтрира падајуће блокове и проверу савршеног контакта за тенкове, борбене авионе, гранате куполе и елементарну муницију ЦолорВхеел.
  • Паинт Трек: Наша брза свемирска пуцачина која садржи судар ротационих кругова, континуирано емитовање зрака и противракетне одбрамбене системе велике запремине.

Закључак и следећи кораци

МоноГаме вам даје моћ да дизајнирате детекцију судара која савршено одговара потребама ваше игре. Увођењем скупих Пикел-Перфецт провера иза брзих ААББ тестова, коришћењем Квадратних растојања за кругове и елиминисањем сакупљања смећа помоћу Бафера за гребање за вишекратну употребу, можете да испоручите перформансе од 60/120 ФПС у конзоли и на десктоп и мобилним платформама.

Погледајте наше игре у продавницама апликација да бисте видели ове системе колизије у акцији и почните да примењујете ове обрасце у својим сопственим МоноГаме пројектима већ данас!


Везе и ресурси продавнице


СЕО кључне речи и хештегови

Кључне речи: МоноГаме 2Д детекција колизије, Ц# развој игара, Рецтангле.Интерсетс МоноГаме, пикел перфецт цоллисион Ц#, просторна хеш мрежа МоноГаме, континуална колизија свепт раи, оптимизација перформанси индие игре, нулта алокација игре петља, оптимизација игре са нултом алокацијом, оптимизација игре за Андроид, Блоцк Ар МоноГ Паинт Трек.

#МоноГаме #ЦСхарп #ГамеДев #ИндиеДев #ГамеПхисицс #ДотНет #2ДГамеДев #МобилеГамеДев #ПерформанцеОптимизатион #ЦлеанБлоцкПани#ЦлеанБлоцкПанк #ПаинтТрек #АрарГамес #ГамеПрограмминг