27 Eylül 2026 Pazar

MonoGame හි 2D ඝට්ටන හඳුනාගැනීම: මූලික සෘජුකෝණාස්‍රයේ සිට ශුන්‍ය වෙන්කිරීමේ පික්සෙල්-පරිපූර්ණ පද්ධති දක්වා




MonoGame හි 2D ඝට්ටන හඳුනාගැනීම: මූලික සෘජුකෝණාස්‍රයේ සිට Zero-Allocation Pixel-Perfect Systems දක්වා

Unity හෝ Godot වැනි දෘෂ්‍ය එන්ජින්වල 2D ක්‍රීඩා ගොඩනඟන විට, ගැටීම් හඳුනාගැනීම බොහෝ විට පරීක්ෂක පිරික්සුම් කොටු මාලාවක් ලෙස හැඟේ: ඔබ BoxCollider2D හෝ CircleCollider2D මත තට්ටු කර, Rigidbody අමුණන්න, සහ අභ්‍යන්තර භෞතික විද්‍යා පියවර ජංගම උපාංගවල පැකිලී නොයනු ඇතැයි බලාපොරොත්තු වේ.

MonoGame සහ C# තුළ, කෙසේ වෙතත්, ඔබ සම්පූර්ණ පාලනයේ සිටී. සැඟවුණු භෞතික විද්‍යාව උඩින් නැත, අනවශ්‍ය භ්‍රමණ අවස්ථිති භාවයක් නැත, සහ ඔබේ රාමු අයවැය සොරකම් කරන අද්භූත කුණු එකතු කිරීම් (GC) කරල් නොමැත.

Arar Games හිදී, අපි Blocked: Pixel Panzer සහ Paint Trek තැනූ විට, අපගේ ආර්කේඩ් ක්‍රීඩා ලූපවලට අධිවේගී සතුරු උණ්ඩ සිය ගණනක්, පිපිරෙන ගඩොල් ජාල, කැරකෙන ටැංකි ටර්ට්, ප්‍රහාරක ජෙට් ෆ්ලයිබයි, සහ අංශු shields 1 Android සහ Flybys යන අංශ දෙකෙහිම Flybys and particle shields 2 Windows 2 ට Windows 6 PS 2 ට සැකසීමට අවශ්‍ය විය. සාමාන්‍ය-කාර්ය භෞතික විද්‍යා එන්ජිමක් ප්‍රශ්නයෙන් තොර විය - අපට අරමුණ-සාදන ලද, ස්ථර ඝට්ටන ගෘහ නිර්මාණ ශිල්පයක් අවශ්‍ය විය.

මෙම විස්තීර්ණ, කේත-ධාවන මාර්ගෝපදේශය තුළ, අපි MonoGame ඝට්ටනයේ නිරපේක්ෂ මූලික කරුණු වලින් ආරම්භ කරන්නෙමු ('Rectangle.Intersects' real 'Bullet' සහ 'Enimy' sprites) සහ උසස් කව පරීක්ෂා කිරීම, මිශ්‍ර කලම්ප කිරීම, ප්‍රති-උමං රේකාස්ට්, collision P**Pfect දක්වා ගොඩනඟමු. Zero-Allocation Spatial Grids ජංගම GC පැවැත්ම සඳහා ප්‍රශස්ත කර ඇත.


1. පදනම: සරල MonoGame 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 ඝට්ටන පරීක්ෂාව වන්නේ Axis-Aligned Bounding Box (AABB) පරීක්ෂණයයි. "අක්ෂයට පෙළගස්වන ලද" යන යෙදුමේ සරලව අදහස් වන්නේ සෘජුකෝණාස්‍රයේ දාර තිරයේ \(X\) සහ \(Y\) අක්ෂවලට සම්පූර්ණයෙන්ම සමාන්තර වන බවයි (භ්‍රමණයක් නොමැත).

MonoGame වේගවත්, ගොඩනඟන ලද ක්‍රමයක් සපයයි: 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;
}

කොන්දේසි හතරම සපුරා ඇත්නම්, සෘජුකෝණාස්රා අතිච්ඡාදනය වේ. එක් කොන්දේසියක් පවා අසමත් වුවහොත්, හිස් අක්ෂයක් ඒවා වෙන් කරයි, සහ ගැටීමක් කළ නොහැක.

සැබෑ ක්‍රීඩා කේතය: '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 එකක් විසි කරයි. ආපස්සට පුනරාවර්තනය කිරීම මතක නැවත සුචිගත කිරීමේ ගැටළු නොමැතිව ආරක්ෂිත මූලද්‍රව්‍ය ඉවත් කිරීමට ඉඩ සලසයි.

ආකේඩ් රහස: "Fair Hitboxes" Inflate හරහා

Blocked: 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 හි භ්‍රමණය වන විට, හිස් වාතය තුළ කලකිරවන "phantom collisions" ඇති කරවන, කැරකෙන කොන වැසීමට අක්ෂ-පෙළගැසූ මායිම් පෙට්ටියක් ප්‍රසාරණය විය යුතුය.

වෘත්තාකාර ග්‍රහක, ස්වදේශික ශක්ති කක්ෂ සහ භ්‍රමණය වන අභ්‍යවකාශ යානා සඳහා සීමා කව කදිම විසඳුම වේ.

වර්ග මූල උගුල

ඒවායේ කේන්ද්‍ර අතර දුර ඒවායේ අරයවල එකතුවට වඩා අඩු හෝ සමාන වූ විට කව දෙකක් ගැටේ.

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

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)

Paint Trek හි වෘත්තාකාර අභ්‍යවකාශ යානයක් සෘජුකෝණාස්‍රාකාර ආරක්ෂක බාධකවල තද ප්‍රහේලිකාවක් හරහා ගමන් කරන විට හෝ Blocked: 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 දී), තත්පරයකට පික්සල 1,800කින් චලනය වන වස්තුවක් තනි රාමුවක පික්සල 30ක් ගමන් කරයි. සතුරාගේ බඳෙහි ඝනකම පික්සල 15ක් පමණක් නම්, උණ්ඩය 1 රාමුවේ සතුරා ඉදිරියෙන්ද, 2 වන රාමුවේ සතුරාට සම්පූර්ණයෙන්ම පිටුපසින්ද විය.

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

විසඳුම: Swept Segment vs. Box (Slab Method)

තනි ලක්ෂ්‍යයක් පරීක්‍ෂා කරනවා වෙනුවට, අපි රාමු 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 නිරවද්‍යතාවයේ අවසාන මට්ටමට ළඟා වෙමු: 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 නල මාර්ගය ෆ්ලෂ් වන තෙක් බලා සිටීමට බල කරයි, වයන මතකය බසය මතට ඇද දමයි. සෑම රාමුවක්ම ස්ප්‍රයිට් දුසිම් ගණනක් සඳහා මෙය කිරීමෙන් ඔබේ ක්‍රීඩාව FPS 60 සිට 5 FPS දක්වා අඩු කර වහාම Android ANR ක්‍රියාරම්භ කරයි!

Blocked: Pixel Panzer වෙතින් සටන-පරීක්‍ෂිත විසඳුම

Blocked: Pixel Panzer හි, අපගේ Sprite.cs තීරණාත්මක නීති 5 ක් වටා ගොඩනගා ඇති අතිශය ප්‍රශස්ත IntersectsPixel දින චර්යාවක් ක්‍රියාත්මක කරයි:

  1. ස්ථිතික වර්ණ අරා හැඹිලිය: 'GetData' වයනය පූරණය වන විට හරියටම එක් වරක් කැඳවනු ලැබේ, සහ 'Dictionary<Texture2D, Color[]>` තුළ ගබඩා කර ඇත.
  2. AABB කලින් පිටවීමේ ආරක්ෂකයා: Bounds.Intersects(other.Bounds) අසත්‍ය නම්, අපි වහාම ඇප ලබා ගනිමු. තනි පික්සලයක් පරීක්ෂා කිරීමට පෙර චෙක්පත් වලින් 99% ක් ඉවත් කරනු ලැබේ.
  3. ** වයනය ඇට්ලස් සහ මූලාශ්‍ර සෘජුකෝණාස්‍රය සහාය:** මූලාශ්‍ර සෘජුකෝණාස්‍ර ඕෆ්සෙට් භාවිතයෙන් වයනය ඇට්ලස්වලට අසුරා ඇති ස්ප්‍රයිට් හසුරුවයි.
  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 වන මට්ටම: පුළුල්-අදියර අවකාශීය ජාලකය සහ ශුන්‍ය වෙන්කිරීමේ ජංගම GC

ඔබේ ක්‍රීඩාවට කුට්ටි 100ක්, උණ්ඩ 60ක් සහ සතුරන් 20ක් ඇතැයි සිතන්න. ඔබ කැදැලි for ලූප භාවිතයෙන් අනෙක් සෑම ආයතනයකටම එරෙහිව සෑම ආයතනයක්ම පරීක්ෂා කරන්නේ නම්, ඔබ කරන්නේ:

\(\frac{180 \times 179}{2} = 16,110 \text{ රාමුවකට චෙක්පත්!}\)

60 FPS දී, එනම් සෑම තත්පරයකටම 1,000,000 ඝට්ටන චෙක්පත් පමණ වේ.

ඔබේ ක්‍රීඩාව පරිමාණය කිරීමට, ඔබ Broad-Phase Spatial Partition ක්‍රියාත්මක කළ යුතුය.

ඒකාකාර අවකාශීය හැෂ් ජාලකය

අපි තිරය ඒකාකාර සෛලවල 2D ජාලකයකට බෙදන්නෙමු (අවහිර කළ: Pixel Panzer තුළ, සෑම කොටුවක්ම \(150 \times 150\) පික්සල වේ). ආයතනයක් එකම ජාල සෛල තුළ පවතින අනෙකුත් ආයතනවලට එරෙහිව ගැටීම් පමණක් පරීක්ෂා කරයි.

ජංගම මතක ගැටලුව: GC Thrashing

ඔබේ SpatialGrid සෑම රාමුවක්ම නව List<Point>() හෝ new List<Block>() නිර්මාණය කරන්නේ නම්, ඔබ කළමනාකරණය කළ ගොඩට සෑම විනාඩියකටම මෙගාබයිට් කසළ වෙන් කරයි.

Android හි Mono ධාවන කාලය තුළ, මෙය නිතර නිතර Gen-0 කසළ එකතු කිරීම් අවුලුවයි, මෙයට හේතු වේ: මොනෝ ධාවන කාලය: ස්වදේශීය අගුළු විවාදය (mono_class_is_subclass_)

ක්‍රීඩාව පැකිළෙන අතර Google Play ANR අනතුරු ඇඟවීම් සමඟින් ඔබේ ක්‍රීඩාව සලකුණු කරයි!

විසඳුම: නැවත භාවිතා කළ හැකි සීරීම් බෆර

මෙන්න Blocked: Pixel Panzer හි CollisionManager.cs වෙතින් Zero-Allocation Spatial Hash Grid:

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 (සෘජුකෝණාස්රය. ඡේදනය) Ultra-Fast (~4 පූර්ණ සංඛ්‍යා සැසඳීම්) නැත නැත ජාල කුට්ටි, බුලට් බ්‍රෝඩ්-ෆේස්, UI
කවය (Distancesquared) ඉතා වේගවත් (මුල් 3, වර්ග 0) ඔව් (විචල්‍ය) නැත වටකුරු නැව්, ගිනි බෝල, ශක්ති ගෝල
Circle vs Box (Clamp) වේගවත් (Local MathHelper.Clamp) ඔව් නැත තද බිත්ති සැරිසරන වෘත්තාකාර වාදකය
Swept Ray (Slab CCD) මධ්‍යස්ථ (පරාමිතික රේකාස්ට්) ඔව් ඔව් වේගවත් ස්නයිපර් උණ්ඩ, රේල් තුවක්කු, ලේසර්
Pixel-Perfect (IntersectsPixel) වරණාත්මක (සීමා සහිත උප-රෙක්ට්) ඔව් නැත අක්‍රමවත් ස්ප්‍රයිට් සමෝච්ඡ, සාධාරණ පහර පෙට්ටි
Spatial Grid (Zero-GC) පුළුල්-අදියර (\(O(N^2) \ සිට O(N)\)) N/A N/A ඝන තරංග, බුලට් හෙල්ස්, Android 60 FPS

සැබෑ ලෝක නිෂ්පාදන ප්‍රදර්ශන: අරාර් ක්‍රීඩා

මෙම ඝට්ටන ගෘහ නිර්මාණ ශිල්පය න්‍යායික අත්හදා බැලීම් නොවේ - ඒවා අපගේ වාණිජමය වශයෙන් නිකුත් කරන ලද මාතෘකා බලගන්වන සැබෑ ඉංජිනේරු පදනම වේ:

  • අවහිර කළ: Pixel Panzer: Google Play සහ Microsoft Store හි අපගේ රෙට්‍රෝ ටැංකි පැවැත්මේ ආකේඩ් ක්‍රීඩාව. එය අපගේ සම්පූර්ණ ද්වි-ස්ථර ඝට්ටන පද්ධතියකින් සමන්විත වේ: ශුන්‍ය-විභේදන අවකාශීය හැෂ් ග්‍රිඩ් පෙරහන වැටෙන කුට්ටි සහ පික්සල්-පරිපූර්ණ සම්බන්ධතා ටැංකි, ප්‍රහාරක ජෙට්, ටර්ට් ෂෙල් සහ ColorWheel මූලද්‍රව්‍ය පතොරම් පරීක්ෂා කිරීම.
  • Paint Trek: අපගේ වේගවත් අභ්‍යවකාශ වෙඩික්කරු භ්‍රමණ කවයේ ගැටීම, අඛණ්ඩ කිරණ විකාශනය සහ ස්වීප්-පරිමා මිසයිල ආරක්ෂණ පද්ධති ඇතුළත් වේ.

නිගමන සහ ඊළඟ පියවර

MonoGame ඔබට ඔබේ ක්‍රීඩාවේ අවශ්‍යතාවලට හොඳින් ගැලපෙන ගැටුම් හඳුනාගැනීම සැලසුම් කිරීමේ බලය ලබා දෙයි. වේගවත් AABB පරීක්ෂණ පිටුපස මිල අධික Pixel-Perfect චෙක්පත් ලබා ගැනීමෙන්, කව සඳහා වර්ග දුර භාවිත කිරීමෙන් සහ නැවත භාවිත කළ හැකි Scratch Buffers සමඟින් කසළ එකතු කිරීම ඉවත් කිරීමෙන්, ඔබට කොන්සෝලයෙන් සුමට 60/120 FPS වේදිකා සහ ඩෙස්ක්ටොප් වේදිකා දෙකෙහිම කාර්ය සාධනය ලබා දිය හැක.

මෙම ඝට්ටන පද්ධති ක්‍රියාත්මක වන ආකාරය බැලීමට යෙදුම් වෙළඳසැල් වල අපගේ ක්‍රීඩා පරීක්ෂා කර බලන්න, සහ අද ඔබේම MonoGame ව්‍යාපෘති තුළ මෙම රටා ක්‍රියාත්මක කිරීම ආරම්භ කරන්න!


ගබඩා සබැඳි සහ සම්පත්


SEO මූලපද සහ හැෂ් ටැග්

** මූලපද:** MonoGame 2D ඝට්ටන හඳුනාගැනීම, C# ක්‍රීඩා සංවර්ධනය, සෘජුකෝණාස්‍රය. ඡේදනය MonoGame, pixel perfect collision C#, spatial hash grid MonoGame, swept ray Continue collision, indie game performance optimization, loopar Games Optimisation, Loopar Games Optimisation, Android zero allocation අවහිර කළ Pixel Panzer, Paint Trek.

#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixelPanzarPanzer #ක්‍රීඩා වැඩසටහන්කරණය




Hiç yorum yok:

Yorum Gönder