27 Eylül 2026 Pazar

MonoGame ойынындағы 2D соқтығысты анықтау: негізгі тіктөртбұрыштардан нөлдік бөлу пиксельді-мінсіз жүйелерге дейін




MonoGame ойынындағы 2D соқтығысты анықтау: негізгі тіктөртбұрыштардан нөлдік бөлу пиксельді-мінсіз жүйелерге дейін

Unity немесе Godot сияқты визуалды қозғалтқыштарда 2D ойындарын құрастырған кезде, соқтығысты анықтау жиі инспектордың құсбелгілері қатары сияқты сезіледі: сіз 'BoxCollider2D' немесе 'CircleCollider2D' қағып, 'Rigidbody' тіркейсіз және ішкі физика қадамы мобильді құрылғыларда кекірмейді деп үміттенесіз.

MonoGame және C# ойындарында, алайда, сіз толық бақылаудасыз. Жасырын физиканың үстеме шығындары, қажетсіз айналу инерциясы және кадр бюджетін ұрлайтын жұмбақ қоқыс жинау (GC) жоқ.

Arar Games ойынында, Blocked: Pixel Panzer және Paint Trek құрастырған кезде, біздің аркада ойын циклдері Windows және Android құрылғыларында 60-тан 120PS-ке дейінгі жылдамдықта жаудың жүздеген жоғары жылдамдықтағы оқтарын, жарылған кірпіш торларды, айналмалы танк мұнараларын, жойғыш ұшақтардың ұшуын және бөлшектерден қорғайтын қалқандарды өңдеу үшін қажет болды. Жалпы мақсаттағы физика қозғалтқышы мүмкін емес еді - бізге мақсатты, деңгейлі соқтығысу архитектурасы қажет болды.

Бұл жан-жақты, кодқа негізделген нұсқаулықта біз MonoGame соқтығысудың абсолютті негіздерінен бастаймыз («Тіктөртбұрыш. Қиылысады» нақты «Bullet» және «Дұшпан» спрайттары) және кеңейтілген шеңберді тексерулерге, аралас қысқыштарға, туннельден өтуге қарсы сәулелерге, өндірістік деңгейдегі Pixelsion-ZeroPixelsion-Alfects **Pixelsion-Alfects Pixel-Zo-Alfect. Торлар мобильді GC өмір сүру үшін оңтайландырылған.


1. Негіз: Қарапайым моноойын Sprite иерархиясы

Соқтығыстарды анықтау алдында бізге таза ойын нысандары қажет. 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)

MonoGame-тегі ең негізгі 2D соқтығысты тексеру - Остерге тураланған шектеу қорабы (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;
}

Барлық төрт шарт орындалса, төртбұрыштар қабаттасады. Тіпті бір шарт орындалмаса, бос ось оларды ажыратады және соқтығысуы мүмкін емес.

Нағыз ойын коды: "Game1.Update" ішіндегі "Bullet" мен "Жау"

Негізгі MonoGame «Жаңарту» цикліндегі белсенді таңбалар тізімі мен белсенді жаулар тізімі арасындағы қақтығыстарды мына жолмен тексересіз:

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: Жинақ өзгертілді шығарады. Кері қайталау жадты қайта индекстеу мәселелерінсіз элементті қауіпсіз жоюға мүмкіндік береді.

Аркада құпиясы: "Inflate" арқылы "Әділ хитбокстар"

Блокталған: 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-деңгей: шеңберден шеңберге соқтығыс (айналмалы иммунитет)

Тіктөртбұрыштар статикалық блоктар мен тор тақтайшалары үшін тамаша жұмыс істейді, бірақ олар спрайттарды айналдырған кезде сәтсіздікке ұшырайды. Шаршы емес ғарыш кемесі Paint Trek ішінде айналғанда, оське теңестірілген шектегіш қорап айналмалы бұрыштарды қоршау үшін кеңеюі керек, нәтижесінде бос ауада «фантомдық соқтығыстар» пайда болады.

Дөңгелек астероидтар, энергия шарлары және айналмалы ғарыш аппараттары үшін Шектеу шеңберлері тамаша шешім болып табылады.

Шаршы түбір тұзағы

Екі шеңбер центрлері арасындағы қашықтық олардың радиустарының қосындысынан кіші немесе тең болғанда соқтығысады:

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

Кодта евклидтік қашықтықты есептеу Math.Sqrt (немесе Vector2.Distance) пайдаланады. Дегенмен, 200 снарядтары бар циклде квадрат түбірлерді есептеу жүздеген қажет емес процессорлық циклдарды жояды!

шаршы қашықтықты квадрат радиус қосындысымен салыстыру арқылы біз квадрат түбірін толығымен жоямыз:

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

Paint Trek жүйесіндегі дөңгелек ғарыш кемесі тікбұрышты қорғаныс кедергілерінің тығыз лабиринтінен өткенде немесе дөңгелек оқ Блокталған: 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}\) 60 FPS), секундына 1800 пикселмен қозғалатын нысан бір кадрда 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 дәлдігінің ең жоғарғы деңгейіне жеттік: Пиксель-Мінсіз соқтығысу.

Ретро танк атқышында немесе ғарыш кемесі ит төбелесінде дұрыс емес пішіндер (танк бөшкелері, қанаттар, кабинаның кабиналары) спрайт текстурасындағы мөлдір пикселдермен қоршалған. Жау зымыраны мөлдір кеңістікке тиген кезде, ойыншылар бірден байқайды.

Пиксель-мінсіз соқтығысу қабаттасатын текстуралардың нақты альфа (мөлдірлік) арналарын тексереді. Егер екі мөлдір емес пикселдер бір әлемдік координатада қабаттасса, шынайы физикалық соққы орын алды.

Қате қате: 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() шақыру процессорды GPU құбырының тазартылуын күтуге мәжбүрлейді, бұл текстуралық жадты шина арқылы кері тартады. Мұны әр кадрдың ондаған спрайттары үшін жасау ойыныңызды 60 FPS-тен 5 FPS-ке дейін төмендетеді және Android ANR-ін бірден іске қосады!

Блокталған: Pixel Panzer ұсынған шайқаста сыналған шешім

Блокталған: Pixel Panzer ішінде біздің "Sprite.cs" 5 маңызды ережеге негізделген ультра оңтайландырылған "IntersectsPixel" режимін жүзеге асырады:

  1. Тұрақты түс массивінің кэші: GetData текстура жүктелген кезде дәл бір рет шақырылады және Dictionary<Texture2D, Color[]> ішінде сақталады.
  2. AABB Early Exit Guard: Егер "Шектеулер.Қиылысады(басқа.Шектер)" жалған болса, біз дереу кепілдік береміз. Тексерулердің 99% бір пиксельді тексеру алдында жойылады.
  3. Текстура атласы және SourceRectangle қолдауы: Бастапқы тіктөртбұрышты ығыстыру арқылы текстура атластарына оралған спрайттарды өңдейді.
  4. Есептелген қабаттасу терезесі: Біз тек екі спрайт (Math.Max(a.Top, b.Top) және т.б.) арасындағы дәл қиылысу тіктөртбұрышының үстінен айналдырамыз.
  5. Альфа қысқа тұйықталу: 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;
    }
}

Бұл процедура заманауи Android аппараттық құралында және жұмыс үстелі жүйелерінде 0,1 миллисекундта орындалады.


7. 6-деңгей: кең фазалы кеңістіктік тор және нөлдік бөлу мобильді МК

Ойыныңызда 100 блок, 60 оқ және 20 жау бар деп елестетіп көріңіз. Егер сіз кірістірілген «for» циклдерін пайдаланып әрбір нысанды басқа нысандарға қарсы сынасаңыз, сіз:

\(\frac{180 \times 179}{2} = 16,110 \text{ әр кадрға тексеру!}\)

60 FPS кезінде бұл секунд сайын шамамен 1 000 000 соқтығысты тексеру.

Ойыныңызды масштабтау үшін Кең фазалық кеңістікті бөлу енгізу керек.

Бірыңғай кеңістіктік хэш торы

Біз экранды біркелкі ұяшықтардың 2D торына бөлеміз (Блокталған: Pixel Panzer ішінде, әрбір ұяшық \(150 \рет 150\) пиксель). Нысан тек бір тор ұяшықтарында орналасқан басқа нысандарға қарсы соқтығысуды тексереді.

Мобильді жад мәселесі: GC Thrashing

Егер SpatialGrid әр кадр сайын жаңа Тізім<Нүкте>() немесе жаңа Тізім<Блок>() жасаса, басқарылатын үймеде әр минут сайын мегабайт қоқысты бөлесіз.

Android Mono жұмыс уақытында бұл жиі 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. Жиынтық салыстыру кестесі

Техника Математикалық құн Ротациялық қолдау Туннельдеу қауіпсіз бе? MonoGame қолданбасындағы ең жақсы жағдай
AABB (Тіктөртбұрыш.Қиылысады) Өте жылдам (~4 бүтін салыстыру) Жоқ Жоқ Тор блоктары, маркер кең фазасы, UI
Шеңбер (DistanceSquared) Өте жылдам (3 мөл., 0 шаршы) Иә (Инвариантты) Жоқ Дөңгелек кемелер, отты шарлар, энергетикалық шарлар
Шеңбер және қорап (Қысқыш) Жылдам (Local MathHelper.Clamp) Иә Жоқ Қатты қабырғаларды шарлайтын дөңгелек ойнатқыш
Swept Ray (Slab CCD) Орташа (параметрлік Raycast) Иә Иә Жылдам снайперлік оқтар, рельсті мылтықтар, лазерлер
Pixel-Perfect (IntersectsPixel) Таңдамалы (шектелген қосалқы) Иә Жоқ Тұрақты емес спрайт контурлары, әділ хитбокстар
Кеңістіктік тор (Нөл-GC) Кең фазалы (\(O(N^2) \to O(N)\)) Жоқ Жоқ Тығыз толқындар, оқ тозақтары, Android 60 FPS

Нақты әлемдік өндіріс көрмелері: Арар ойындары

Бұл соқтығыс архитектуралары теориялық эксперименттер емес - олар біздің коммерциялық түрде шығарылған атауларымызға қуат беретін нақты инженерлік негіз болып табылады:

  • Блокталған: Pixel Panzer: Google Play және Microsoft Store дүкендеріндегі ретро танктен аман қалу аркада ойыны. Онда біздің толық екі деңгейлі соқтығыс жүйесі бар: құлап жатқан блоктарды сүзетін нөлдік бөлінген кеңістіктік хэш торы және танктер, жойғыш ұшақтар, мұнара снарядтары және ColorWheel элементтік оқ-дәрілері үшін пиксельдік мінсіз контактіні тексеру.
  • Paint Trek: Айналмалы шеңбер соқтығысуы, үздіксіз радиакстинг және кең көлемді зымыранға қарсы қорғаныс жүйелері бар жылдам қарқынмен ғарыштық атқыш.

Қорытынды және келесі қадамдар

MonoGame сізге ойынның қажеттіліктеріне толық сәйкес келетін соқтығысты анықтауды жобалау мүмкіндігін береді. Қымбат Pixel-Perfect тексерулерін жылдам AABB сынақтарынан өткізу, шеңберлер үшін Квадраттық қашықтықтарды пайдалану және Қайта пайдалануға болатын сызат буферлері арқылы қоқыс жинауды жою арқылы сіз жұмыс үстелінде де, мобильді платформаларда да консольдік 60/120 FPS өнімділігін қамтамасыз ете аласыз.

Қолданбалар дүкендеріндегі біздің ойындарды қарап шығыңыз және осы соқтығыс жүйелерінің әрекетін көріңіз және осы үлгілерді жеке MonoGame жобаларыңызға бүгіннен бастап енгізіңіз!


Дүкен сілтемелері мен ресурстары


SEO кілт сөздері және хэштегтері

Кілт сөздер: MonoGame 2D соқтығысты анықтау, C# ойынын әзірлеу, Rectangle.Intersects MonoGame, C# пиксельдік мінсіз соқтығысу, MonoGame кеңістіктік хэш торы, үздіксіз сәулелік соқтығысу, инди ойынының өнімділігін оңтайландыру, нөлдік бөлу ойын циклі, Android MonoGame, ArmaGame ойынын оңтайландыру Paint Trek.

#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #Clean#PanzerBlocked #PaintTrek #ArarGames #Ойын бағдарламалау




Hiç yorum yok:

Yorum Gönder