27 Eylül 2026 Pazar

MonoGame-da 2D to'qnashuvni aniqlash: asosiy to'rtburchaklardan nol taqsimlangan pikselli mukammal tizimlargacha




MonoGame-da 2D to'qnashuvni aniqlash: asosiy to'rtburchaklardan nol taqsimlangan pikselli mukammal tizimlargacha

Unity yoki Godot kabi vizual dvigatellarda 2D oʻyinlarni yaratishda toʻqnashuvni aniqlash koʻpincha inspektorning katakchalari qatoriga oʻxshab ketadi: siz “BoxCollider2D” yoki “CircleCollider2D” ni bosing, “Rigidbody”ni biriktirasiz va mobil qurilmalarda ichki fizika qadami qoqilmasligiga umid qilasiz.

MonoGame va C#-da esa siz to'liq nazorat qilasiz. Hech qanday yashirin fizika yuki, istalmagan aylanish inertsiyasi va sizning ramka byudjetingizni o'g'irlaydigan sirli axlat yig'ish (GC) yo'q.

Arar Games da, Blocked: Pixel Panzer va Paint Trek-ni yaratganimizda, bizning arkada o‘yin halqalarimiz yuzlab yuqori tezlikdagi dushman o‘qlarini, portlovchi g‘isht to‘rlarini, aylanuvchi tank minoralarini, qiruvchi samolyotlarning parvozlarini va zarracha qalqonlarini Windows va Android qurilmalarida 60 dan 120 PS gacha tezlikda qayta ishlash uchun kerak edi. Umumiy maqsadli fizika dvigateli haqida gap bo'lishi mumkin emas edi - bizga maqsadli qurilgan, bosqichli to'qnashuv arxitekturasi kerak edi.

Ushbu keng qamrovli, kodga asoslangan qo'llanmada biz MonoGame to'qnashuvining mutlaq asoslaridan boshlaymiz ("To'rtburchak. Kesishlar" haqiqiy "O'q" va "Dushman" spritlari bilan) va ilg'or aylana tekshiruvlari, aralash qisish, tunnelga qarshi raykaslar, ishlab chiqarish darajasidagi **Pixelsion-Zero-Pixelsion-Zo'lik va Alli-Pixel-Pixel-Pixel-Pixel-Pixel-Pixel-Pixel-Pixel-Pixel-Pixel-Pixel-Pixel-Zo'ga qarshi ilg'or tekshiruvlarni o'tkazamiz. Grids mobil GC omon qolish uchun optimallashtirilgan.


1. Jamg'arma: oddiy MonoGame "Sprite" ierarxiyasi

To'qnashuvlarni aniqlashdan oldin bizga toza o'yin ob'ektlari kerak. MonoGame-da ob'ekt asosan pozitsiyaga, teksturaga va chegaralovchi to'rtburchakka ega.

Mana bizning o'yinlarimizda ishlatiladigan asosiy ob'ekt arxitekturasi:

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

Keling, aniq “Player”, “Dushman” va “Bullet” sinflarini yarataylik:

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

Ushbu ob'ektlar mavjud bo'lganda, keling, eng oddiy usuldan boshlab ular orasidagi to'qnashuvlarni qanday aniqlashni o'rganamiz.


2. 1-daraja: Eng oddiy toʻqnashuv – “Toʻrtburchaklar kesishadi” (AABB)

MonoGame-dagi eng asosiy ikki o'lchovli to'qnashuv tekshiruvi bu Axis-Aligned Bounding Box (AABB) testidir. "O'qga tekislangan" atamasi oddiygina to'rtburchakning qirralari ekranning \(X\) va \(Y\) o'qlariga to'liq parallel ekanligini bildiradi (aylanish yo'q).

MonoGame tezkor, o'rnatilgan usulni taqdim etadi: To'rtburchak. Kesishadi (To'rtburchak qiymati).

To'rtburchak. Kesishadi kaput ostida qanday ishlaydi

Sirt ostida MonoGame to'rtta butun son taqqoslashni amalga oshiradi:

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

Agar to'rtta shart bajarilsa, to'rtburchaklar bir-biriga yopishadi. Agar bitta shart bajarilmasa, bo'sh o'q ularni ajratib turadi va hech qanday to'qnashuv mumkin emas.

Haqiqiy oʻyin kodi: “Game1.Update”dagi “Bullet” va “Dushman” oʻrtasidagi

Asosiy MonoGame Update tsiklingizdagi faol o'qlar ro'yxati va faol dushmanlar ro'yxati o'rtasidagi to'qnashuvni qanday tekshirishingiz mumkin:

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

Ishlash bo'yicha maslahat: E'tibor bering, biz orqaga qarab takrorlaymiz (for (int i = list.Count - 1; i >= 0; i--))! Agar siz "foreach" dan foydalansangiz va "_bullets.Remove(bullet)" ga qo'ng'iroq qilmoqchi bo'lsangiz, C# "InvalidOperationException: Collection o'zgartirildi" ni chiqaradi. Orqaga takrorlash xotirani qayta indekslash muammosisiz elementni xavfsiz olib tashlash imkonini beradi.

Arkada siri: "Inflate" orqali "Fair Hitboxes"

Blocked: Pixel Panzer kabi retro o‘yinlarda sprite teksturalari ko‘pincha shaffof chegaralar yoki antenna tirqishlarini o‘z ichiga oladi. Agar o'yinchining tanki o'q uning teksturasining bo'sh shaffof burchagiga tegishi sababli portlasa, o'yinchi o'zini aldangandek his qiladi.

Toʻqnashuvni sezgir va adolatli qilish uchun oʻyinlar “Rectangle.Inflate” yordamida sprite ichidagi kichikroq Hitbox dan foydalanadi:

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-daraja: Aylanadan aylanaga to‘qnashuv (aylanish immuniteti)

To'rtburchaklar statik bloklar va panjara plitalari uchun juda yaxshi ishlaydi, lekin spritlar aylanganda ular muvaffaqiyatsizlikka uchraydi. Kvadrat bo'lmagan kosmik kema Paint Trek da aylansa, aylanuvchi burchaklarni o'rab olish uchun o'q bo'ylab tekislangan chegara qutisi kengayishi kerak, bu esa bo'sh havoda "fantom to'qnashuvlari" ga olib keladi.

Dumaloq asteroidlar, maqsadli energiya sharlari va aylanuvchi kosmik kemalar uchun Bounding Circles ideal yechimdir.

Kvadrat ildiz tuzog'i

Ikki doira markazlari orasidagi masofa radiuslari yig‘indisidan kichik yoki teng bo‘lganda to‘qnashadi:

\(\text{Distance}(C_A, C_B) \le r_A + r_B\)

Kodda Evklid masofasini hisoblashda "Math.Sqrt" (yoki "Vektor2.Distance") ishlatiladi. Biroq, kvadrat ildizlarni 200 ta snaryadli halqada hisoblash yuzlab keraksiz CPU tsikllarini yoqib yuboradi!

kvadrat masofani kvadrat radius yig‘indisi bilan solishtirib, biz kvadrat ildizni butunlay yo‘q qilamiz:

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

MonoGame Amalga oshirish: "Circle" va "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);
    }
}

Endi buni to'g'ridan-to'g'ri ob'ektga integratsiya qiling:

public class PaintTrekFighter : Sprite
{
    public float CollisionRadius = 18f;
    public Vector2 Center => Position + new Vector2(Texture.Width * 0.5f, Texture.Height * 0.5f);

    public Circle BoundingCircle => new Circle(Center, CollisionRadius);

    public bool CollidesWith(PaintTrekFighter other)
    {
        return this.BoundingCircle.Intersects(other.BoundingCircle);
    }
}

Nol kvadrat ildizlar, sprite aylanishiga qarshi immunitet va chaqmoq tezligida.


4. 3-daraja: Aralash shakllar – doira va quti (MathHelper.Clamp)

Paint Trek da dumaloq kosmik kema to‘rtburchaklar shaklidagi mudofaa to‘siqlaridan iborat qattiq labirintdan o‘tib ketsa yoki Bloklangan: Pixel Panzer da yumaloq o‘q kvadrat blokga tegsa nima bo‘ladi?

Bizga Circle vs. Rectangle toʻqnashuvi kerak.

Siqish algoritmi

Strategiya to'rtburchakda aylananing markaziga eng yaqin nuqtani topib, so'ngra eng yaqin nuqtadan markazgacha bo'lgan masofa doira radiusidan kichikroq yoki yo'qligini tekshirishdan iborat.

MonoGame 'MathHelper.Clamp' buni ahamiyatsiz qiladi:

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

Endi siz o'yinchi qalqonlarini to'rtburchaklar g'ishtlarga qarshi nol taqsimot va yuqori aniqlik bilan sinab ko'rishingiz mumkin!


5. 4-daraja: Uzluksiz toʻqnashuvni aniqlash (CCD) va oʻchirilgan nurlar

Siz o'z o'yiningizda o'qning sehrli tarzda yupqa dushman kemasidan to'g'ridan-to'g'ri o'tib ketishini tomosha qilish uchun gipertezlikdagi snayper yoki relsli to'pponchani o'qqa tutganmisiz?

Bu xato tunnellash nomi bilan tanilgan.

Diskret o'yinlar vaqt bosqichlarida yangilanganligi sababli (60 FPSda \(\Delta t = 16,6\text{ms}\)), soniyada 1800 piksel tezlikda harakatlanuvchi ob'ekt bir kadrda 30 piksel sayohat qiladi. Agar dushman korpusining qalinligi atigi 15 piksel bo'lsa, o'q 1-ramkada dushman oldida, 2-ramkada esa butunlay dushman orqasida edi.

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

Yechim: Supurilgan segment va quti (plitalar usuli)

Bitta nuqtani sinab ko'rish o'rniga, biz o'qning 1-kadrdagi o'rnini (oldingiPosition) 2-framega (joriyPosition) bog'laydigan butun chiziq segmentini sinab ko'ramiz.

SpiralWar nomli hamrohimizdagi ishlab chiqarish raycasting plita kesish usuli:

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 o‘yinida o‘yinchining uzluksiz Laser Beam mahorati bitta to‘qnashuvni o‘tkazib yubormasdan pastga tushuvchi bloklar qatorini kesib o‘tish uchun aynan shu raycastdan foydalanadi.


6. 5-daraja: ishlab chiqarish darajasidagi piksel-mukammal to‘qnashuvni aniqlash

Endi biz 2D aniqlikning eng yuqori darajasiga erishdik: Pixel-Perfect Collision.

Retro tank otuvchi yoki kosmik kema it jangida tartibsiz shakllar (tank bochkalari, qanotlar, kokpit kabinalari) sprite teksturasida shaffof piksellar bilan o'ralgan. Dushman raketasi shaffof bo'shliqqa tushganda, o'yinchilar darhol buni sezadilar.

Piksel-mukammal to'qnashuv bir-biriga yopishgan teksturalarning haqiqiy alfa (shaffoflik) kanallarini tekshiradi. Agar ikkita shaffof bo'lmagan piksel bir xil dunyo koordinatasida bir-biriga to'g'ri kelsa, haqiqiy jismoniy zarba sodir bo'ldi.

Fatal xato: Update() ichidagi GetData

Ko'pgina darsliklar yangi boshlanuvchilarga buni qilishni buyuradi:

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

Ishlash vaqtida "Texture2D.GetData()" ga qo'ng'iroq qilish protsessorni GPU quvur liniyasining tozalanishini kutishga majbur qiladi va tekstura xotirasini avtobus orqali orqaga tortadi. Buni har bir kadrda oʻnlab spritlar uchun qilish oʻyiningizni 60 FPS dan 5 FPS gacha oshiradi va Android ANR’larini darhol ishga tushiradi!

Bloklangan: Pixel Panzer dan jangovar sinovdan o'tgan yechim

Bloklangan: Pixel Panzer ilovasida ‘Sprite.cs’ 5 ta muhim qoida atrofida tuzilgan ultra optimallashtirilgan “IntersectsPixel” dasturini amalga oshiradi:

  1. Statik rang massivi keshi: GetData tekstura yuklanganda aynan bir marta chaqiriladi va Dictionary<Texture2D, Color[]>da saqlanadi.
  2. AABB Early Exit Guard: Agar Bounds.Intersects(other.Bounds) noto'g'ri bo'lsa, biz darhol kafolat beramiz. Bitta pikselni tekshirishdan oldin tekshiruvlarning 99% yo'q qilinadi.
  3. Texture Atlas va SourceRectangle qoʻllab-quvvatlashi: Toʻgʻri toʻrtburchaklar ofset manbalaridan foydalangan holda tekstura atlaslariga oʻralgan spritlarni boshqaradi.
  4. Hisoblangan ustma-ust oyna: Biz faqat ikkala spritlar (Math.Max(a.Top, b.Top) va h.k.) oʻrtasidagi aniq kesishgan toʻrtburchak ustidan aylanamiz.
  5. Alfa qisqa tutashuvi: Agar Sprite A pikseli shaffof bo'lsa (A <= 20), Sprite B butunlay o'tkazib yuboriladi.
  6. Mulkni ko'tarish: Ichki ko'chadan virtual xususiyatlarga (Chegaralar) kirish minglab struktura nusxalarini yaratadi. Loopga kirishdan oldin ularni mahalliy stek o'zgaruvchilarida saqlaymiz.

Mana to'liq, ishlab chiqarishga tayyor kod:

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class AdvancedSprite : Sprite
{
    private static readonly Dictionary<Texture2D, Color[]> _textureDataCache = new();

    public Rectangle? SourceRectangle { get; set; }

    public static Color[] GetCachedTextureData(Texture2D texture)
    {
        if (!_textureDataCache.TryGetValue(texture, out var data))
        {
            data = new Color[texture.Width * texture.Height];
            texture.GetData(data);
            _textureDataCache[texture] = data;
        }
        return data;
    }

    public bool IntersectsPixel(AdvancedSprite other)
    {
        // 1. Hoist bounds into local stack variables
        Rectangle a = this.Bounds;
        Rectangle b = other.Bounds;

        // 2. Fast AABB Broad-Phase check
        if (!a.Intersects(b)) return false;

        // Fallback to rectangle check if textures are missing
        if (this.Texture == null || other.Texture == null) return true;

        // 3. Retrieve pre-cached color arrays (Zero GPU latency)
        Color[] dataA = GetCachedTextureData(this.Texture);
        Color[] dataB = GetCachedTextureData(other.Texture);

        int aTexW = this.Texture.Width;
        int bTexW = other.Texture.Width;

        // Atlas / SourceRectangle offset mapping
        bool aHasSrc = this.SourceRectangle.HasValue;
        Rectangle aSrc = aHasSrc ? this.SourceRectangle.Value : Rectangle.Empty;
        int aOriginX = aHasSrc ? aSrc.X : 0;
        int aOriginY = aHasSrc ? aSrc.Y : 0;
        int aSpanX   = aHasSrc ? aSrc.Width  : this.Texture.Width;
        int aSpanY   = aHasSrc ? aSrc.Height : this.Texture.Height;

        bool bHasSrc = other.SourceRectangle.HasValue;
        Rectangle bSrc = bHasSrc ? other.SourceRectangle.Value : Rectangle.Empty;
        int bOriginX = bHasSrc ? bSrc.X : 0;
        int bOriginY = bHasSrc ? bSrc.Y : 0;
        int bSpanX   = bHasSrc ? bSrc.Width  : other.Texture.Width;
        int bSpanY   = bHasSrc ? bSrc.Height : other.Texture.Height;

        // 4. Calculate the overlapping sub-rectangle
        int top    = Math.Max(a.Top, b.Top);
        int bottom = Math.Min(a.Bottom, b.Bottom);
        int left   = Math.Max(a.Left, b.Left);
        int right  = Math.Min(a.Right, b.Right);

        // 5. Scan only overlapping pixels
        for (int y = top; y < bottom; y++)
        {
            // Row offsets hoisted outside the inner X loop!
            int colorA_Y = aOriginY + (int)((y - a.Y) / (float)a.Height * aSpanY);
            int colorB_Y = bOriginY + (int)((y - b.Y) / (float)b.Height * bSpanY);
            int rowA = colorA_Y * aTexW;
            int rowB = colorB_Y * bTexW;

            for (int x = left; x < right; x++)
            {
                int colorA_X = aOriginX + (int)((x - a.X) / (float)a.Width * aSpanX);

                // Early Bailout: If Pixel A is transparent, skip B entirely!
                if (dataA[colorA_X + rowA].A <= 20) continue;

                int colorB_X = bOriginX + (int)((x - b.X) / (float)b.Width * bSpanX);

                // If Pixel B is also opaque, we have confirmed contact!
                if (dataB[colorB_X + rowB].A > 20)
                {
                    return true;
                }
            }
        }

        return false;
    }
}

Bu tartib zamonaviy Android apparat va ish stoli tizimlarida 0,1 millisekunddan kamroq vaqt ichida amalga oshiriladi.


7. 6-daraja: Keng fazali fazoviy tarmoq va nol taqsimlangan mobil GC

Tasavvur qiling-a, sizning o'yiningizda 100 ta blok, 60 ta o'q va 20 ta dushman bor. Agar siz har bir ob'ektni har bir boshqa ob'ektga qarshi o'rnatilgan "for" tsikllari yordamida sinab ko'rsangiz, quyidagilarni bajarasiz:

\(\frac{180 \times 179}{2} = 16,110 \text{ har bir kadr uchun tekshiruv!}\)

60 FPS da, bu har soniyada deyarli 1 000 000 to'qnashuv tekshiruvi.

O'yinni kengaytirish uchun siz Keng fazali fazoviy qismlarga ajratish-ni amalga oshirishingiz kerak.

Yagona fazoviy xesh panjara

Biz ekranni bir xil hujayralardan iborat 2D panjaraga ajratamiz (Bloklangan: Pixel Panzer ichida, har bir hujayra 150$\150$ pikselga teng). Tashkilot faqat bir xil katakchalarda joylashgan boshqa ob'ektlarga qarshi to'qnashuvlarni sinovdan o'tkazadi.

Mobil xotira muammosi: GC Thrashing

Agar SpatialGrid har bir freymda yangi Royxat()yokiyangi Royxat<Blok>() yaratsa, siz boshqariladigan to`plamda har daqiqada megabayt axlatni ajratasiz.

Android-ning Mono ish vaqtida, bu tez-tez Gen-0 axlat yig'ishlarini ishga tushiradi, bu esa quyidagilarga sabab bo'ladi: mono ish vaqti: mahalliy blokirovka bahsi (mono_class_is_subclass_)

O'yin qoqilib ketadi va Google Play o'yiningizni ANR ogohlantirishlari bilan belgilaydi!

Yechim: Qayta foydalanish mumkin bo'lgan skretch buferlari

Mana Bloklangan: Pixel Panzer ichidagi CollisionManager.cs dan nol taqsimlangan fazoviy xesh panjara:

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" va "_scratchCheckedEntities" dan qayta foydalanish orqali to'qnashuvning to'liq bosqichi har bir kadr uchun 0 bayt yig'ish taqsimoti bilan ishlaydi.


8. Xulosa solishtirish jadvali

Texnika Matematik narx Rotatsion yordam Tunnel xavfsizmi? MonoGame-da eng yaxshi foydalanish holati
AABB (Tortburchak. Kesishadi`) Ultra-tezkor (~4 ta butun son taqqoslash) Yo'q Yo'q Grid bloklari, o'q keng fazali, UI
Doira (DistanceSquared) Juda tez (3 muls, 0 sqrt) Ha (Invariant) Yo'q Dumaloq kemalar, olov sharlari, energiya sharlari
Circle vs Box (Clamp) Tez (Mahalliy MathHelper.Clamp) Ha Yo'q Qattiq devorlarda aylanma o'yinchi
Soflangan nur (Slab CCD) O'rtacha (Parametrik Raycast) Ha Ha Tezkor snayper o'qlari, relsli qurollar, lazerlar
Pixel-Perfect (IntersectsPixel) Tanlangan (cheklangan sub-rect) Ha Yo'q Noqonuniy sprite konturlari, adolatli xitbokslar
Fazoviy tarmoq (nol-GC) Keng fazali (\(O(N^2) \to O(N)\)) Yo'q Yo'q Zich to'lqinlar, o'q jahannamlari, Android 60 FPS

Haqiqiy dunyo ishlab chiqarish ko'rgazmalari: Arar o'yinlari

Ushbu to'qnashuv arxitekturalari nazariy eksperimentlar emas - ular bizning tijorat maqsadlarida chiqarilgan nomlarimizni quvvatlaydigan haqiqiy muhandislik asosidir:

  • Bloklangan: Pixel Panzer: Google Play va Microsoft Store do‘konidagi retro tank omon qolish arkada o‘yinimiz. U bizning to'liq ikki bosqichli to'qnashuv tizimimizni o'z ichiga oladi: tushib qolgan bloklarni filtrlaydigan nol taqsimlangan fazoviy xesh panjara va tanklar, qiruvchi samolyotlar, minora snaryadlari va ColorWheel elementar o'q-dorilari uchun piksel-mukammal kontaktni tekshirish.
  • Paint Trek: Aylanma aylana to‘qnashuvi, uzluksiz raykasting va keng ko‘lamli raketaga qarshi mudofaa tizimlarini o‘z ichiga olgan tez sur’atli kosmik otishmamiz.

Xulosa va keyingi qadamlar

MonoGame sizga o'yiningiz ehtiyojlariga to'liq mos keladigan to'qnashuvni aniqlashni loyihalash quvvatini beradi. Qimmatbaho Pixel-Perfect tekshiruvlarini tezkor AABB sinovlaridan o‘tkazish, doiralar uchun Kvadrat masofalar-dan foydalanish va Qayta foydalanish mumkin bo‘lgan skretch buferlari bilan axlat yig‘ishni yo‘q qilish orqali siz ish stoli va mobil platformalarda konsolning 60/120 FPS ishlashini ta’minlay olasiz.

Ushbu to'qnashuv tizimlarini ishlayotganini ko'rish uchun ilovalar do'konlaridagi o'yinlarimizni ko'rib chiqing va bugun o'zingizning MonoGame loyihalaringizda ushbu naqshlarni qo'llashni boshlang!


Do'kon havolalari va manbalari


SEO kalit so'zlari va hashtaglari

Kalit so‘zlar: MonoGame 2D to‘qnashuvni aniqlash, C# o‘yinini ishlab chiqish, Rectangle.Intersects MonoGame, piksel mukammal to‘qnashuvi C#, fazoviy xesh-grid MonoGame, to‘xtovsiz uzatilgan to‘qnashuv, indie o‘yin samaradorligini optimallashtirish, nol taqsimlangan o‘yin tsikli, Android MonoG’ni optimallashtirish, o‘yinni optimallashtirish. Paint Trek.

#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization ``#Clean#GameDev #PaintTrek #ArarGames #GameProgramming`




Hiç yorum yok:

Yorum Gönder