27 Eylül 2026 Pazar

Di MonoGame de Tespîtkirina Pevçûna 2D: Ji Rektangên Bingehîn Berbi Pergalên Pixel-Pêkûpêk ên Zero-Veqetandin




Di MonoGame de Tespîtkirina Pevçûnek 2D: Ji Rektangên Bingehîn berbi Pergalên Pixel-Perfect-Paqijkirina Zero

Dema ku lîstikên 2D di motorên dîtbar ên mîna Unity an Godot de têne çêkirin, tespîtkirina pevçûnê bi gelemperî wekî rêzek qutiyên kontrolê yên çavdêran xuya dike: hûn li 'BoxCollider2D' an 'CircleCollider2D' dixin, 'Rigidbody' ve girêdidin, û hêvî dikin ku gavê fizîkî ya hundurîn li ser cîhazên desta neteqe.

Lêbelê, di MonoGame û C# de, hûn di bin kontrola tevahî de ne. Li ser serê fîzîka veşartî tune, bêhêziya zivirî ya nedilxwaz, û çîpên berhevkirina çopê (GC) ya razdar tune ku budceya çarçoweya we dizîne.

Li Arar Games, dema ku me Blocked: Pixel Panzer û Paint Trek ava kir, pêlên lîstika meya arcade hewce bû ku bi sedan guleyên dijmin ên bi leza bilind, torên kerpîçan diteqin, gerîdeyên tankê yên zivirî, firîna firokeyên şerker, û mertalên perçeyan di 60 heta 120 FPS de hem li ser cîhazên Windows-ê û hem jî li ser 120 FPS-ê bi kar bînin. Motorek fizîkî ya gelemperî ji pirsê dernakeve - ji me re pêdivî bi mîmariyek pevçûnê ya bi mebest çêkirî û pijyayî hebû.

Di vê rêberê berfireh, kod-rêveber de, em ê ji bingehên bêkêmasî yên pevçûna MonoGame (Rectangle.Tirsects bi 'Bullet' û 'Enemy' sprites" yên rastîn) dest pê bikin û heta kontrolên pêşkeftî yên çemberê, girtina tevlihev, raycastên dijî-tuneling, hilberîna-pola **Pixelsion-*Spaller, û Cool Grids ** ji bo zindîbûna GC-ya mobîl xweşbîn kirin.


1. Bingeh: Hiyerarşiya MonoLîstika Hêsan a Sprite

Berî ku pevçûnan tesbît bikin, em hewceyê lîstikên paqij hene. Di MonoGame de, hebûnek bi bingehîn xwedan pozîsyonek, tevnek û çargoşeyek sînordar e.

Li vir mîmariya saziya bingehîn e ku di lîstikên me de tê bikar anîn:

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

Naha em dersên konkret "Player", "Dijmin" û "Bullet" biafirînin:

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

Bi van saziyan re, emê vekolin ka meriv çawa pevçûnên di navbera wan de, ji rêbaza herî hêsan dest pê dike.


2. Asta 1: Lihevketina herî hêsan - Rectangle.Intersects (AABB)

Kontrola herî bingehîn a pevçûna 2D di MonoGame de testa Qutiya Sînorkirî ya Axis-Aligned (AABB) e. Gotina "tevlihevkirî bi eksê" bi hêsanî tê vê wateyê ku keviyên çargoşeyê bi eksên \(X\) û \(Y\) yên ekranê re bi tevahî paralel in (zivirandin tune).

MonoGame rêbazek bilez û çêkirî peyda dike: Rectangle.Intersects(Nirxa Rectangle).

Çawa Rectangle.Intersects Di bin Hoodê de dixebite

Di binê rûxê de, MonoGame çar berhevokên jimaran pêk tîne:

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

Ger her çar şert pêk bên, rectangles li hev dikevin. Ger şertek jî têk biçe, eksenek vala wan ji hev vediqetîne, û lihevketin ne pêkan e.

Koda lîstika rastîn: Bullet li hember Dijmin di Game1.Update

Li vir ev e ku hûn çawa pevçûnên di navbera navnîşek guleyên çalak û navnîşek dijminên çalak de di hundurê lûleya xweya sereke ya MonoGame Update de kontrol dikin:

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

** Serişteya Performansê:** Bala xwe bidinê ku em berevajî dubare dikin (ji bo (int i = list. Hejmar - 1; i >= 0; i--))! Heke hûn foreach bikar bînin û hewl bidin ku gazî _bullets.Remove(bullet), C# 'InvalidOperationException: Koleksiyon hate guhertin` derdixe. Berevajî dubarekirin rê dide rakirina hêmanên ewle bêyî pirsgirêkên ji nû ve-indekskirina bîranînê.

Veşartina Arcade: "Hitboxên Fair" bi rêya Inflate

Di lîstikên retro yên mîna Blocked: Pixel Panzer de, tevnvîsên sprite bi gelemperî marjînalên zelal an çîpên antenna hene. Ger tanka lîstikvan biteqe ji ber ku guleyek li quncikek vala ya zelal a tevna wê biteqe, lîstikvan dê hîs bike ku hatî xapandin.

Ji bo ku lihevhatinê bi bersiv û adil were hîs kirin, lîstik bi "Rectangle.Inflate" Hitbox piçûktir di hundurê sprite de bikar tînin:

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. Asta 2: Lihevketina çember-bi-dorê (Berxwedana zivirî)

Rectangles ji bo blokên statîk û pelikên torê pir baş dixebitin, lê gava ku sprites dizivirin ew têk diçin. Dema ku keştiyek fezayî ya ne çargoşe di Paint Trek de dizivire, pêdivî ye ku qutiyek sînorkirî ya bi eksê berbelav bibe da ku quncikên zivirî dorpêç bike, û di hewaya vala de "lihevhatinên fantastîk" xemgîn bibe.

Ji bo asteroîdên dorveger, gerîdeyên enerjiyê yên xwemalî, û keştiyên fezayê yên zivirî, **Çorkên Sînor ** çareseriya îdeal e.

Xefika Root Square

Dema ku dûrahiya navendên wan ji berhevoka tîrêjên wan kêmtir an wekhev be du dor li hev dikevin:

\(\text{Dûrahî}(C_A, C_B) \le r_A + r_B\)

Di kodê de, hesabkirina dûrahiya Euclidy 'Math.Sqrt' (an 'Vector2.Distance') bikar tîne. Lêbelê, hesabkirina rehên çargoşe di lûpekê de bi 200 fîşekan bi sedan çerxên CPU yên nepêwist dişewitîne!

Bi danberheva dûrahiya çargoşeyî li hember hevberheviya tîrêjê ya çargoşe, em bi tevahî koka çargoşe ji holê radikin:

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

Pêkanîna MonoLîstikê: Çircle beramberî Çircle

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

Naha vê rasterast di nav saziyekê de entegre bikin:

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

Kokên çargoşe sifir, ji zivirîna sprite bêpar, û birûsk-lez e.


4. Asta 3: Şêweyên Tevlihev – Çêrok li hember Box (MathHelper.Clamp)

Çi diqewime dema ku keştiyek fezayî ya dorhêl li Paint Trek di nav mazînek teng a astengên parastinê yên çargoşe de derbas dibe, an dema ku guleyek dor li bloka çargoşe di Blocked: Pixel Panzer de digere?

Pêdiviya me bi Circle vs Rectangle collision.

Algorîtmaya Clamping

Stratejî ev e ku meriv li ser çargoşeyê xala ku herî nêzikî navenda çemberê ye bibîne, û dûv re ceribandin ku dûrahiya ji wê xala herî nêzîk ji navendê re ji tîrêjê çemberê kêmtir e.

MonoGame 'MathHelper.Clamp' vê piçûktir dike:

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

Naha hûn dikarin mertalên lîstikvan li hember kerpîçên çargoşeyî bi veqetandinên sifir û rastbûna bilind biceribînin!


5. Asta 4: Tespîtkirina Pevçûna Berdewam (CCD) & Tîrêjên Swept

Ma we di lîstika xwe de çuçek sniper-lezgîn an lazerek hesinî gulebaran kiriye, tenê hûn temaşe bikin ku gule bi efsûnî rasterast di keştiyek dijminê zirav re derbas dibe bêyî ku zirarê bide?

Ev xeletî wekî **tunneling ** tê zanîn.

Ji ber ku lîstikên veqetandî di gavên demê de nûve dibin (\(\Delta t = 16,6\text{ms}\) bi 60 FPS), tiştek ku bi 1,800 pixel di çirkeyê de tevdigere 30 pixel di çarçoveyek yekane de digere. Ger qalikê dijmin tenê 15 pixel stûr be, gule li çarçoweya 1-ê li pêş dijmin, û li ser çarçoweya 2-ê bi tevahî li pişt dijmin bû.

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

Çareserî: Beşa Swept beramberî Box (Rêbaza Slab)

Li şûna ceribandina xalek yekane, em tevahiya beşa rêzê ya ku pozîsyona guleyê ya li ser Çarçoveya 1 (Pêşiya berê) bi Çarçoveya 2 (`Positiona niha) ve girêdide, ceribandin.

Li vir awayê hilberîna tîrêjê ya tîrêjê ya ji sernavê hevalbendê me **SpiralWar ** e:

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

Di Blocked: Pixel Panzer de, şarezatiya domdar ** Laser Beam** ya lîstikvan vê tîrêjê tam bikar tîne da ku di nav rêzên blokên daketî de perçe bike bêyî ku yek pevçûnek winda bike.


6. Asta 5: Hilberîn-Pola Pixel- Tespîtkirina Têkiliya Bêkêmasî

Naha em digihîjin asta herî dawî ya rastbûna 2D: ** Pevçûnek Pixel-Perfect **.

Di gulebarana tankê ya retro an jî şerê keştiya fezayê de, şeklên nerêkûpêk (bermîlên tankê, bask, kokpîtên kokêtê) bi pîxelên zelal ên di tevna sprite de têne dorpêç kirin. Dema ku mûşekek dijmin li wê cîhê zelal dikeve, lîstikvan tavilê ferq dikin.

Pevçûn-pixel-bêkêmasî kanalên alpha (şefafîtî) yên tevnhev ên li hevûdu teftîş dike. Ger du pixelên ne-şefaf di heman hevrêziya cîhanê de li hev bikevin, lêdanek laşî ya rastîn çêbûye.

Şaşiya Kujer: Daneyên Bistînin di hundurê Nûvekirin() de

Gelek tutorial ji destpêkeran re şîret dikin ku vê bikin:

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

Di dema xebitandinê de gazîkirina Texture2D.GetData() CPU neçar dike ku li benda rijandina lûleya GPU bisekine, bîranîna tevnvîsê li ser otobusê paşde bikişîne. Di her çarçovê de kirina bi dehan sprites dê lîstika we ji 60 FPS berbi 5 FPS vebike û tavilê ANR-ên Android-ê bide destpêkirin!

Çareseriya Ceribandina Şerê ji * Astengkirin: Pixel Panzer*

Di Blocked: Pixel Panzer de, Sprite.cs me rûtînek IntersectsPixel ya ultra-optimîzekirî ku li dora 5 qaîdeyên girîng hatî çêkirin bicîh tîne:

  1. Cache Array Rengê Statîkî: Dema ku tevnvîs tê barkirin, 'GetData' tam carekê tê gotin û di Ferhengek<Texture2D, Reng[]> de tê hilanîn.
  2. AABB Early Exit Guard: Ger Sînor.Intersects(Other.Bounds) derew be, em tavilê xilas dikin. 99% ji kontrolan berî ku yek pixelek kontrol bikin têne rakirin.
  3. Texture Atlas & SourceRectangle Piştgiriya: Spîtên ku di nav atlasên tevnvîsê de hatine pakij kirin bi karanîna guheztinên çargoşeya çavkaniyê digire.
  4. Pencereya Lihevderketinê ya Hesabkirî: Em tenê li ser çargoşeya hevberdana tam a di navbera her du tîpan de (`Math.Max(a.Top, b.Top)“ û hwd.
  5. Alpha Short-Circuiting: Ger pîxela Sprite A şefaf be (A <= 20), Sprite B bi tevahî tê paşguh kirin.
  6. Hêzkirina Taybetmendiyê: Gihîştina taybetmendiyên virtual (Sînor) di lûpên hêlînkirî de bi hezaran kopiyên strukturê diafirîne. Em wan di guhêrbarên stakê yên herêmî de hilînin berî ku têkevin lûkê.

Li vir koda bêkêmasî, hilberîn-amade ye:

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

Ev rûtîn di binê 0,1 mîlîçirkeyan de li ser pergalên hardware û sermaseyê yên nûjen ên Android-ê pêk tê.


7. Asta 6: Tûra Cihanî-Qonaxa Berfireh & GC-ya Mobîl Veqetandin-Sil

Bifikirin ku lîstika we 100 blok, 60 gule û 20 dijmin hene. Ger hûn her hebûnê li hember her saziyek din bi karanîna lûpên hêlînkirî yên for biceribînin, hûn pêk tînin:

\(\frac{180 \times 179}{2} = 16,110 \text{ kontrolên her çarçovê!}\)

Di 60 FPS de, ew e ku hema ** 1,000,000 pevçûnê her saniyeyê kontrol dike **.

Ji bo pîvandina lîstika xwe, divê hûn ** Parvekirina Cihanî ya Berfireh ** bicîh bînin.

Tora Haş a Cihanî ya Yekgirtî

Em ekranê di nav şebekek 2D ya hucreyên yekreng de dabeş dikin (di Blocked: Pixel Panzer de, her şaneyek \(150 \cara 150\) pixel e). Saziyek tenê pevçûnan li dijî pêkhateyên din ên ku di heman şaneyên torê de dimînin diceribîne.

Pirsgirêka Bîra Mobîl: GC Thrashing

Ger SpatialGrida we di her çarçoveyekê de Lîsteya nû<Point>() an jî Lîsteya nû<Block>() diafirîne, hûn her deqeyê megabytes çopê li ser girava birêvebirî veqetînin.

Di dema xebitandina Mono ya Android-ê de, ev yek carcaran Berhevkirinên Gen-0-ê çopê dike, ku dibe sedema: dema xebitandinê mono: Têkoşîna kilîtkirina xwemalî (mono_class_is_subclass_)

Lîstik dişewite û Google Play lîstika we bi hişyariyên ANR nîşan dide!

Çareserî: Tamponên Scratchê yên ji nû ve bi kar anîn

Li vir e Treka Haş a Cihanî ya Zero-Allocation ji CollisionManager.cs di Blocked: Pixel Panzer:

public class SpatialGridManager
{
    private const int CELL_SIZE = 150;
    private readonly Dictionary<Point, List<Sprite>> _grid = new();

    // Zero-GC: Reusable scratch buffers allocated ONCE at startup
    private readonly List<Point> _scratchCells = new();
    private readonly HashSet<Sprite> _scratchCheckedEntities = new();

    public void BuildGrid(List<Sprite> entities)
    {
        // Clear lists without re-allocating new List objects
        foreach (var list in _grid.Values)
        {
            list.Clear();
        }

        foreach (var entity in entities)
        {
            if (!entity.IsActive) continue;

            GetOccupiedCells(entity.Bounds, _scratchCells);
            foreach (var cell in _scratchCells)
            {
                if (!_grid.TryGetValue(cell, out var list))
                {
                    list = new List<Sprite>();
                    _grid[cell] = list;
                }
                list.Add(entity);
            }
        }
    }

    private void GetOccupiedCells(Rectangle bounds, List<Point> outCells)
    {
        outCells.Clear();

        int startX = bounds.Left / CELL_SIZE;
        int startY = bounds.Top / CELL_SIZE;
        int endX = bounds.Right / CELL_SIZE;
        int endY = bounds.Bottom / CELL_SIZE;

        for (int x = startX; x <= endX; x++)
        {
            for (int y = startY; y <= endY; y++)
            {
                outCells.Add(new Point(x, y));
            }
        }
    }

    public void CheckCollisions(Player player)
    {
        if (player.IsInvulnerable) return;

        GetOccupiedCells(player.Bounds, _scratchCells);
        _scratchCheckedEntities.Clear();

        foreach (var cell in _scratchCells)
        {
            if (_grid.TryGetValue(cell, out var entitiesInCell))
            {
                foreach (var other in entitiesInCell)
                {
                    if (other == player || _scratchCheckedEntities.Contains(other)) continue;
                    _scratchCheckedEntities.Add(other);

                    // 1. Broad-phase AABB test
                    if (player.Bounds.Intersects(other.Bounds))
                    {
                        // 2. High-precision Pixel-Perfect test
                        if (player is AdvancedSprite advPlayer && other is AdvancedSprite advOther)
                        {
                            if (advPlayer.IntersectsPixel(advOther))
                            {
                                player.TakeDamage(10);
                            }
                        }
                    }
                }
            }
        }
    }
}

Bi ji nû ve bikar anîna _scratchCells û _scratchCheckedEntities, tevaya gavê lihevketinê bi ** 0 byte veqetandina giravê li ser çarçoveyekê** dimeşe.


8. Kurte Tabloya Berawirdî

Teknîkî Mesrefa Matematîkî Piştgiriya Rotational Tunneling Safe? Di MonoGame de Doza Bikaranîna Baştirîn
AABB (Rectangle.Intersects) Ultra-Fast (~ 4 danberheva jimare) Na Na Blokên torê, guleya qonax-fireh, UI
Dor (DistanceQuard) Pir Zû (3 muls, 0 sqrt) Erê (Neguhêrbar) Na Keştiyên dor, topên agir, keştiyên enerjiyê
Circle vs Box (Clamp) Fast (Local MathHelper.Clamp) Erê Na Lîstikvanê dorhêl ku li dîwarên teng digere
Swept Ray (Slab CCD) Moderat (Raycast Parametrîk) Erê Erê Guleyên sekvanî yên bilez, tivingên hesinî, lazer
Pixel-Perfect (IntersectsPixel) Hilbijartî (Sîndora Bin-Rastkirî) Erê Na Rêzên sprite yên nerêkûpêk, hitboxên adil
Tora Cihanî (Sifir-GC) Qonaxa fireh (\(O(N^2) \to O(N)\)) N/A N/A Pêlên hişk, dojehên guleyan, Android 60 FPS

Pêşandanên Hilberîna Cîhana Rastîn: Lîstikên Arar

Van mîmarên pevçûnê ne ceribandinên teorîkî ne - ew bingeha endezyariya rastîn in ku sernavên me yên bazirganî yên hatine berdan hêz dikin:

  • Bê astengkirin: Pixel Panzer: Lîstika meya arcade ya rizgariya tankê ya retro li Google Play û Microsoft Store. Ew pergala meya tevliheviya du-astî ya bêkêmasî vedihewîne: tora haş-veqetandî ya zero-veqetandî ku blokên daketinê fîltre dike, û têkiliya pixel-bêkêmasî ji bo tank, firokeyên şerker, guleyên tûrikê, û cebilxaneya bingehîn a ColorWheel kontrol dike.
  • Paint Trek: Firokeya meya fezayê ya bilez a ku lihevketina dora zivirî, tîrêjên domdar, û pergalên berevaniya mûşekê yên bi cild-hilweşîn vedihewîne.

Encam & Gavên Pêşe

MonoGame hêzê dide we ku hûn tespîtkirina pevçûnê sêwiran bikin ku bi tevahî hewcedariyên lîstika we li hev dike. Bi girtina kontrolên biha Pixel-Perfect li pişt ceribandinên AABB yên bilez, bi karanîna Dûrahiya Çargoşe ji bo dorûberan, û rakirina berhevkirina çopê bi Bûferên Scratch-ê yên Ji nû ve bi kar anîn, hûn dikarin performansa 60/120 FPS-ya 60/120 FPS hem li ser platformên sermaseyê û hem jî li ser mobîl peyda bikin.

Lîstikên me yên li ser dikanên sepanê binihêrin da ku hûn van pergalên pevçûnê di çalakiyê de bibînin, û îro di projeyên xwe yên MonoGame de dest bi bicihanîna van nimûneyan bikin!


Zencîr û Çavkaniyên Firotanê


Keywords & Hashtags SEO

** Peyvên sereke:** Tespîtkirina pevçûnê ya MonoGame 2D, pêşkeftina lîstika C#, Rectangle.Intersects MonoGame, pixel têkçûnek bêkêmasî C#, tora haş a cîhê MonoGame, pevçûna domdar a tîrêjê, xweşbîniya performansa lîstika indie, veqetandina sifir lûp, Lîstikên Android Monolockê yên optîmîzekirî, Lîstikên Paxelme Paxela Trek.

#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixelPixelPaint #Bernameya Lîstikê




Hiç yorum yok:

Yorum Gönder