27 Eylül 2026 Pazar

2D zaznavanje trkov v igri MonoGame: od osnovnih pravokotnikov do sistemov z ničelno dodelitvijo slikovnih pik




2D zaznavanje trkov v igri MonoGame: od osnovnih pravokotnikov do sistemov z ničelno dodelitvijo slikovnih pik

Pri izdelavi 2D iger v vizualnih motorjih, kot sta Unity ali Godot, se zaznavanje trka pogosto zdi kot vrsta inšpektorskih potrditvenih polj: udarite po BoxCollider2D ali CircleCollider2D, pritrdite Rigidbody in upate, da korak notranje fizike ne bo zatikal na mobilnih napravah.

V MonoGame in C# pa imate popoln nadzor. Ni nobenih skritih fizikalnih stroškov, nobene neželene rotacijske vztrajnosti in nobenih skrivnostnih konic zbiranja smeti (GC), ki bi ukradli vaš proračun za okvir.

Pri Arar Games, ko smo zgradili Blocked: Pixel Panzer in Paint Trek, so naše zanke arkadnih iger morale obdelati na stotine hitrih sovražnih nabojev, eksplozivnih opečnih mrež, vrtečih se tankovskih kupol, preletov bojnih letal in ščitov proti delcem pri 60 do 120 FPS tako v računalnikih z operacijskim sistemom Windows kot v napravah Android. Splošni fizikalni motor ni prišel v poštev – potrebovali smo namensko izdelano večstopenjsko arhitekturo trkov.

V tem obsežnem vodniku, ki temelji na kodi, bomo začeli z absolutnimi osnovami trka MonoGame ('Rectangle.Intersects' z resničnimi spriti 'Bullet' in 'Enemy') in nadaljevali z naprednimi preverjanji krogov, mešanim vpenjanjem, žarkovnimi oddajami proti tuneliranju, Pixel-Perfect collision in Zero-Allocation Spatial proizvodne stopnje. Mreže** optimizirane za mobilno preživetje GC.


1. Osnova: preprosta hierarhija Sprite MonoGame

Preden zaznamo trke, potrebujemo čiste entitete igre. V igri MonoGame ima entiteta položaj, teksturo in mejni pravokotnik.

Tukaj je osnovna arhitektura entitete, ki se uporablja v naših igrah:

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

Zdaj pa ustvarimo konkretne razrede Player, Enemy in Bullet:

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

Ko so te entitete na mestu, raziščimo, kako zaznati trke med njimi, začenši z najpreprostejšo metodo.


2. 1. stopnja: Najpreprostejši trk – Pravokotnik.Preseka (AABB)

Najbolj temeljno preverjanje dvodimenzionalnih kolizij v igri MonoGame je test Axis-Aligned Bounding Box (AABB). Izraz "poravnano z osjo" preprosto pomeni, da so robovi pravokotnika popolnoma vzporedni z osema \(X\) in \(Y\) zaslona (brez vrtenja).

MonoGame ponuja hitro, vgrajeno metodo: Rectangle.Intersects(Rectangle value).

Kako Rectangle.Intersects deluje pod pokrovom

Pod površjem MonoGame izvede štiri primerjave celih števil:

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

Če so izpolnjeni vsi štirje pogoji, se pravokotnika prekrivata. Če vsaj en pogoj ni izpolnjen, ju loči prazna os in kolizija ni možna.

Resnična koda igranja: Bullet proti Enemy v Game1.Update

Tukaj je opisano, kako preverite kolizije med seznamom aktivnih nabojev in seznamom aktivnih sovražnikov v vaši glavni zanki 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);
    }
}

Nasvet za učinkovitost: Upoštevajte, da ponavljamo nazaj (for (int i = list.Count - 1; i >= 0; i--))! Če uporabite foreach in poskusite poklicati _bullets.Remove(bullet), C# vrže InvalidOperationException: Collection was modified. Ponavljanje nazaj omogoča varno odstranjevanje elementov brez težav s ponovnim indeksiranjem pomnilnika.

The Arcade Secret: "Fair Hitboxes" prek Inflate

V retro igrah, kot je Blocked: Pixel Panzer, teksture sprite pogosto vključujejo prozorne robove ali antenske konice. Če igralčev rezervoar eksplodira, ker se je krogla dotaknila praznega prozornega vogala njegove teksture, se bo igralec počutil prevaranega.

Da bi bilo trčenje odzivno in pošteno, igre uporabljajo manjši Hitbox znotraj sprite z uporabo Rectangle.Inflate:

public class EnemyTank : Enemy
{
    // Shrink the bounding box by 6 pixels on all sides for fair collision
    public override Rectangle Bounds
    {
        get
        {
            Rectangle raw = base.Bounds;
            raw.Inflate(-6, -6); // Reduces width and height by 12px
            return raw;
        }
    }
}

3. 2. stopnja: Trčenje kroga v krog (rotacijska odpornost)

Pravokotniki delujejo odlično za statične bloke in mrežne ploščice, vendar ne uspejo, ko se spriti vrtijo. Ko se nekvadratna vesoljska ladja vrti v Paint Trek, se mora z osjo poravnano omejevalno polje razširiti, da zajame vrteče se vogale, kar povzroči frustrirajoče "fantomske trke" v praznem zraku.

Za krožne asteroide, usmerjene energijske krogle in vrteča se vesoljska plovila so Omejevalni krogi idealna rešitev.

Past kvadratnega korena

Dva kroga trčita, ko je razdalja med njunima središčema manjša ali enaka vsoti njunih polmerov:

\(\besedilo{Razdalja}(C_A, C_B) \le r_A + r_B\)

V kodi se za izračun evklidske razdalje uporablja Math.Sqrt (ali Vector2.Distance). Vendar pa računanje kvadratnih korenov v zanki z 200 izstrelki porabi na stotine nepotrebnih ciklov procesorja!

Če primerjamo kvadrat razdalje s kvadratom vsote radijev, popolnoma odstranimo kvadratni koren:

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

Implementacija MonoGame: Circle proti 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);
    }
}

Zdaj integrirajte to neposredno v entiteto:

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

Nič kvadratnih korenin, imun na vrtenje sprite in bliskovito hiter.


4. Raven 3: Mešane oblike – krog proti škatli (MathHelper.Clamp)

Kaj se zgodi, ko okrogla vesoljska ladja v Paint Trek pluje skozi tesen labirint pravokotnih obrambnih ovir ali ko okrogla krogla zadene kvadratni blok v Blocked: Pixel Panzer?

Potrebujemo trk Krog proti pravokotniku.

Algoritem vpenjanja

Strategija je najti točko na pravokotniku, ki je najbližje središču kroga, in nato preizkusiti, ali je razdalja od te najbližje točke do središča manjša od polmera kroga.

MonoGameov MathHelper.Clamp naredi to trivialno:

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

Zdaj lahko preizkusite ščite igralcev pred pravokotnimi kockami z ničelnimi dodelitvami in visoko natančnostjo!


5. Stopnja 4: Neprekinjeno zaznavanje trčenja (CCD) in prestreljeni žarki

Ste že kdaj v svoji igri izstrelili visokohitrostni ostrostrelni naboj ali laserski railgun, samo da bi opazovali, kako je krogla čarobno prešla naravnost skozi tanko sovražnikovo ladjo, ne da bi povzročila škodo?

Ta hrošč je znan kot tuneliranje.

Ker se diskretne igre posodabljajo v časovnih korakih (\(\Delta t = 16,6\text{ms}\) pri 60 sličicah na sekundo), predmet, ki se premika s hitrostjo 1800 slikovnih pik na sekundo, potuje 30 slikovnih pik v enem okvirju. Če je sovražnikov trup debel samo 15 slikovnih pik, je bila krogla na 1. sličici pred sovražnikom, na 2. sličici pa povsem za sovražnikom.

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

Rešitev: prečrtani segment proti škatli (metoda plošče)

Namesto testiranja ene same točke testiramo celoten odsek črte, ki povezuje položaj krogle na okvirju 1 (»previousPosition«) z okvirjem 2 (»currentPosition«).

Tukaj je produkcijska metoda raycasting plošče presečišča iz našega spremljevalnega naslova 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;
    }
}

V igri Blocked: Pixel Panzer igralčeva neprekinjena spretnost laserskega žarka uporablja to natančno oddajanje žarkov za rezanje skozi vrstice padajočih blokov, ne da bi zgrešil niti en sam trk.


6. Stopnja 5: zaznavanje kolizij s popolno slikovno piko proizvodnega razreda

Zdaj smo dosegli najvišjo raven 2D natančnosti: Pixel-Perfect Collision.

V retro streljačini s tanki ali zračnem boju z vesoljsko ladjo so nepravilne oblike (cevi tankov, krila, pilotske kabine) obdane s prozornimi slikovnimi pikami v teksturi sprite. Ko sovražnikova raketa zadene ta prozorni prostor, igralci takoj opazijo.

Popolno trčenje pikslov pregleda dejanske alfa (prosojnost) kanale prekrivajočih se tekstur. Če se dve neprozorni slikovni piki prekrivata na isti svetovni koordinati, je prišlo do pravega fizičnega zadetka.

Usodna napaka: GetData znotraj Update()

Številne vadnice navajajo začetnike, da to storijo:

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

Klic Texture2D.GetData() med izvajanjem prisili CPE, da počaka, da se cevovod GPE izprazni, pri čemer potegne teksturni pomnilnik nazaj preko vodila. Če to storite za desetine sprite vsak okvir, boste svojo igro znižali s 60 FPS na 5 FPS in takoj sprožili ANR-je za Android!

V bitkah preizkušena rešitev iz Blokirano: Pixel Panzer

V Blocked: Pixel Panzer naš Sprite.cs implementira izjemno optimizirano rutino IntersectsPixel, zgrajeno okoli 5 ključnih pravil:

  1. Statični barvni predpomnilnik: GetData se pokliče točno enkrat, ko se tekstura naloži, in shrani v `Dictionary<Texture2D, Color[]>``.
  2. AABB Early Exit Guard: Če je Bounds.Intersects(other.Bounds) napačen, se takoj rešimo. 99 % pregledov je izločenih pred pregledom posamezne slikovne pike.
  3. Podpora za atlas tekstur in SourceRectangle: obravnava sprite, zapakirane v atlase tekstur z odmiki izvornega pravokotnika.
  4. Izračunano okno prekrivanja: Zankamo samo čez natančen pravokotnik presečišča med obema spriteom ('Math.Max(a.Top, b.Top)' itd.).
  5. Alpha Short-Circuiting: Če je piksel Sprite A prozoren (A <= 20), je Sprite B popolnoma preskočen.
  6. Property Hoisting: Dostop do virtualnih lastnosti ('Meje') v ugnezdenih zankah ustvari na tisoče kopij strukture. Pred vstopom v zanko jih shranimo v spremenljivke lokalnega sklada.

Tukaj je popolna koda, pripravljena za proizvodnjo:

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

Ta rutina se v sodobni strojni opremi Android in namiznih sistemih izvede v manj kot 0,1 milisekunde.


7. Raven 6: širokofazna prostorska mreža in mobilni GC z ničelno dodelitvijo

Predstavljajte si, da ima vaša igra 100 blokov, 60 nabojev in 20 sovražnikov. Če preizkusite vsako entiteto glede na vsako drugo entiteto z uporabo ugnezdenih zank za, izvedete:

\(\frac{180 \times 179}{2} = 16.110 \text{ preverjanj na okvir!}\)

Pri 60 FPS je to skoraj 1.000.000 preverjanj trkov vsako sekundo.

Če želite razširiti svojo igro, morate implementirati Broad-Phase Spatial Partitioning.

Enotna prostorska zgoščena mreža

Zaslon razdelimo na 2D mrežo enotnih celic (v Blocked: Pixel Panzer je vsaka celica \(150 \krat 150\) slikovnih pik). Entiteta preizkuša samo trke z drugimi entitetami, ki se nahajajo v istih celicah mreže.

Težava mobilnega pomnilnika: GC Thrashing

Če vaš SpatialGrid ustvari new List<Point>() ali new List<Block>() vsak okvir, dodelite megabajte smeti vsako minuto upravljanemu kupu.

V Androidovem izvajalnem okolju Mono to sproži pogosto zbiranje smeti Gen-0, kar povzroči: mono runtime: Native lock spor (mono_class_is_subclass_)

Igra jeclja in Google Play vašo igro označi z opozorili o ANR!

Rešitev: medpomnilniki za večkratno uporabo

Tukaj je Zero-Allocation Spatial Hash Grid iz CollisionManager.cs v 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);
                            }
                        }
                    }
                }
            }
        }
    }
}

S ponovno uporabo _scratchCells in _scratchCheckedEntities teče celoten korak trka z 0 bajti dodelitve kopice na okvir.


8. Povzetek primerjalne tabele

Tehnika Matematični stroški Rotacijska podpora Tuneliranje varno? Najboljši primer uporabe v MonoGame
AABB (Pravokotnik. Presekanja) Izjemno hiter (~4 celih primerjav) Ne Ne Mrežni bloki, krogla široke faze, UI
Krog (Kvadrat razdalje) Zelo hitro (3 muls, 0 sqrt) Da (Nespremenljivo) Ne Okrogle ladje, ognjene krogle, energijske krogle
Krog proti škatli (Sponka) Hitro (lokalni MathHelper.Clamp) Da Ne Krožni igralec, ki krmari po tesnih stenah
Swept Ray (Slab CCD) Zmerno (Parametric Raycast) Da Da Hitre ostrostrelske krogle, tračnice, laserji
Pixel-Perfect ('IntersectsPixel`) Selektivno (omejen podnaslov) Da Ne Nepravilne konture sprite, pošteni hit boxes
Prostorska mreža (Zero-GC) Široka faza (\(O(N^2) \do O(N)\)) ni na voljo ni na voljo Gosti valovi, peklenski pekli, Android 60 FPS

Prikazi produkcije v resničnem svetu: igre Arar

Te kolizijske arhitekture niso teoretični eksperimenti – so pravi inženirski temelj, ki poganja naše komercialno izdane naslove:

  • Blokirano: Pixel Panzer: Naša retro arkadna igra za preživetje v tankih v trgovinah Google Play in Microsoft Store. Vsebuje naš celoten dvonivojski sistem za trčenje: prostorsko zgoščevalno mrežo z ničelno dodelitvijo, ki filtrira padajoče bloke, in popolno preverjanje kontakta za tanke, bojna letala, kupole in elementarno strelivo ColorWheel.
  • Paint Trek: Naša hitra vesoljska strelska igra s trčenjem v rotacijskem krogu, neprekinjenim oddajanjem žarkov in protiraketnimi obrambnimi sistemi s pokritim obsegom.

Zaključek in naslednji koraki

MonoGame vam daje moč za oblikovanje zaznavanja trkov, ki popolnoma ustreza potrebam vaše igre. Z uporabo dragih Pixel-Perfect preverjanj za hitrimi AABB testi, uporabo Kvadratnih razdalj za kroge in odpravo zbiranja smeti z Reusable Scratch Buffers, lahko zagotovite gladko delovanje konzole 60/120 FPS na namiznih in mobilnih platformah.

Oglejte si naše igre v trgovinah z aplikacijami, če si želite ogledati te sisteme trkov v akciji, in začnite izvajati te vzorce v svojih projektih MonoGame še danes!


Shranjujte povezave in vire


SEO ključne besede in hashtagi

Ključne besede: MonoGame 2D zaznavanje trkov, razvoj iger C#, Rectangle.Intersects MonoGame, pixel perfect collision C#, prostorska zgoščena mreža MonoGame, neprekinjeno trčenje swept ray, optimizacija zmogljivosti neodvisnih iger, zanka igre z ničelno dodelitvijo, optimizacija Android MonoGame, Arar Games, Blocked Pixel Panzer, Paint Trek.

#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixelPanzer #PaintTrek #ArarGames #GameProgramming




Hiç yorum yok:

Yorum Gönder