2D-kollisjonsdeteksjon i MonoGame: Fra grunnleggende rektangler til nullallokeringspiksel-perfekte systemer
Når du bygger 2D-spill i visuelle motorer som Unity eller Godot, føles kollisjonsdeteksjon ofte som en serie av inspektøravmerkingsbokser: du slår på en BoxCollider2D eller CircleCollider2D, fester en Rigidbody og håper det interne fysikktrinnet ikke hakker på mobile enheter.
I MonoGame og C# har du imidlertid full kontroll. Det er ingen skjult fysikk overhead, ingen uønsket rotasjonstreghet, og ingen mystiske søppelinnsamling (GC) pigger som stjeler rammebudsjettet ditt.
På Arar Games, da vi bygde Blocked: Pixel Panzer og Paint Trek, trengte arkadespillløkkene våre for å behandle hundrevis av høyhastighets fiendtlige kuler, eksploderende murnett, roterende tanktårn, jagerfly og partikkelskjold med 60 til 120 FPS-enheter på både Windows PC og Android-enheter. En generell fysikkmotor var uaktuelt – vi trengte en spesialbygget, lagdelt kollisjonsarkitektur.
I denne omfattende, kodedrevne guiden vil vi starte fra det absolutte grunnleggende om MonoGame-kollisjon (Rectangle.Intersects med ekte Bullet og Enemy sprites) og bygge opp til avanserte sirkelsjekker, blandet klemme, anti-tunneling raycasts, produksjonsgrad Pixel-Zero-Per, All-Fact Spa and All Grids optimalisert for mobil GC-overlevelse.
1. The Foundation: A Simple MonoGame Sprite Hierarki
Før vi oppdager kollisjoner, trenger vi rene spillenheter. I MonoGame har en enhet grunnleggende en posisjon, en tekstur og et avgrensende rektangel.
Her er den grunnleggende enhetsarkitekturen som brukes på tvers av spillene våre:
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);
}
}
La oss nå lage konkrete "spiller", "fiende" og "Bullet"-klasser:
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;
}
}
Med disse enhetene på plass, la oss utforske hvordan du oppdager kollisjoner mellom dem, med utgangspunkt i den enkleste metoden.
2. Nivå 1: Den enkleste kollisjonen – Rectangle.Intersects (AABB)
Den mest grunnleggende 2D-kollisjonskontrollen i MonoGame er Axis-Aligned Bounding Box (AABB)-testen. Begrepet "akse-justert" betyr ganske enkelt at rektangelets kanter er helt parallelle med skjermens \(X\)- og \(Y\)-akser (ingen rotasjon).
MonoGame gir en rask, innebygd metode: Rectangle.Intersects(Rectangle value).
Hvordan Rectangle.Intersects fungerer under panseret
Under overflaten utfører MonoGame fire heltallssammenlikninger:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
Hvis alle fire betingelsene er oppfylt, overlapper rektanglene. Hvis til og med én tilstand mislykkes, skiller en tom akse dem, og ingen kollisjon er mulig.
Ekte spillkode: "Bullet" vs. "Enemy" i "Game1.Update"
Slik sjekker du kollisjoner mellom en liste over aktive kuler og en liste over aktive fiender i hovedsløyfen i 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);
}
}
Ytelsetips: Legg merke til at vi itererer bakover (
for (int i = list.Count - 1; i >= 0; i--))! Hvis du brukerforeachog prøver å kalle_bullets.Remove(bullet), kaster C# enInvalidOperationException: Collection was modified. Iterering bakover tillater sikker fjerning av elementer uten problemer med reindeksering av minnet.
The Arcade Secret: "Fair Hitboxes" via Inflate
I retrospill som Blocked: Pixel Panzer inkluderer sprite-teksturer ofte gjennomsiktige marger eller antennepigger. Hvis spillerens tank eksploderer fordi en kule berørte et tomt gjennomsiktig hjørne av teksturen, vil spilleren føle seg lurt.
For å få kollisjonen til å føles responsiv og rettferdig, bruker spill en mindre Hitbox inne i spriten ved å bruke 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. Nivå 2: Sirkel-til-sirkel-kollisjon (rotasjonsimmunitet)
Rektangler fungerer utmerket for statiske blokker og rutenettfliser, men de mislykkes når sprites roterer. Når et romskip som ikke er kvadratisk roterer i Paint Trek, må en aksejustert avgrensningsboks utvides for å omslutte de snurrende hjørnene, noe som resulterer i frustrerende "fantomkollisjoner" i tom luft.
For sirkulære asteroider, målsøkende energikuler og roterende romfartøyer er Bounding Circles den ideelle løsningen.
Kvadratrotfellen
To sirkler kolliderer når avstanden mellom sentrene deres er mindre enn eller lik summen av radiene deres:
\(\tekst{Avstand}(C_A, C_B) \le r_A + r_B\)
I kode bruker beregning av euklidisk avstand Math.Sqrt (eller Vector2.Distance). Å beregne kvadratrøtter i en løkke med 200 prosjektiler brenner imidlertid hundrevis av unødvendige CPU-sykluser!
Ved å sammenligne kvadratavstanden med kvadratradiussummen, eliminerer vi kvadratroten fullstendig:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
MonoGame-implementering: Circle vs. 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);
}
}
Integrer dette direkte i en enhet:
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);
}
}
Null kvadratrøtter, immun mot sprite-rotasjon og lynrask.
4. Nivå 3: Blandede former – Circle vs. Box (MathHelper.Clamp)
Hva skjer når et sirkulært romskip i Paint Trek navigerer gjennom en tett labyrint av rektangulære forsvarsbarrierer, eller når en rund kule treffer en firkantet blokk i Blocked: Pixel Panzer?
Vi trenger sirkel mot rektangel kollisjon.
Klemmealgoritmen
Strategien er å finne punktet på rektangelet som er nærmest sirkelens sentrum, og deretter teste om avstanden fra det nærmeste punktet til sentrum er mindre enn sirkelens radius.
MonoGames MathHelper.Clamp gjør dette trivielt:
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);
}
}
Du kan nå teste spillerskjold mot rektangulære klosser med null tildelinger og høy nøyaktighet!
5. Nivå 4: Kontinuerlig kollisjonsdeteksjon (CCD) og feide stråler
Har du noen gang skutt en snikskytterrunde med høy hastighet eller en skinnepistollaser i spillet ditt, bare for å se kulen på magisk vis passere rett gjennom et tynt fiendtlig skip uten å gjøre skade?
Denne feilen er kjent som tunneling.
Fordi diskrete spill oppdateres i tidstrinn (\(\Delta t = 16,6\text{ms}\) ved 60 FPS), beveger et objekt som beveger seg med 1800 piksler per sekund 30 piksler i et enkelt bilde. Hvis fiendens skrog er bare 15 piksler tykt, var kulen foran fienden på Frame 1, og helt bak fienden på Frame 2.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
Løsningen: Swept segment vs. boks (platemetode)
I stedet for å teste et enkelt punkt, tester vi hele linjesegmentet som forbinder kulens posisjon på Frame 1 (previousPosition) til Frame 2 (currentPosition).
Her er produksjonen raycasting slab-skjæringsmetoden fra vår ledsagertittel 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;
}
}
I Blocked: Pixel Panzer bruker spillerens kontinuerlige Laser Beam-ferdighet denne eksakte strålen til å skjære gjennom rader med synkende blokker uten å gå glipp av en eneste kollisjon.
6. Nivå 5: Piksel-perfekt kollisjonsdeteksjon i produksjonsgrad
Nå når vi det ultimate nivået av 2D-nøyaktighet: Pixel-Perfect Collision.
I et retro-tankskytespill eller romskip-dogfight er uregelmessige former (tankløp, vinger, cockpit-cockpiter) omgitt av gjennomsiktige piksler i sprite-teksturen. Når et fiendtlig missil treffer det gjennomsiktige rommet, merker spillerne det umiddelbart.
Pixel-perfect kollisjon inspiserer de faktiske alfakanalene (transparens) til de overlappende teksturene. Hvis to ikke-transparente piksler overlapper på samme verdenskoordinat, har et ekte fysisk treff skjedd.
Den fatale feilen: GetData i Update()
Mange opplæringsprogrammer instruerer nybegynnere til å gjøre dette:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
Å kalle Texture2D.GetData() under kjøretid tvinger CPU-en til å vente på at GPU-rørledningen skal skylles, og trekker teksturminne tilbake over bussen. Hvis du gjør dette for dusinvis av sprites, vil hver frame fylle spillet ditt fra 60 FPS til 5 FPS og utløse Android ANR-er umiddelbart!
Den kamptestede løsningen fra Blocked: Pixel Panzer
I Blocked: Pixel Panzer implementerer 'Sprite.cs' vår en ultraoptimalisert 'IntersectsPixel'-rutine bygget rundt 5 avgjørende regler:
- Static Color Array Cache:
GetDatakalles nøyaktig én gang når teksturen lastes, og lagres i enDictionary<Texture2D, Color[]>. - AABB Early Exit Guard: Hvis
Bounds.Intersects(other.Bounds)er falsk, vil vi redde ut umiddelbart. 99 % av kontrollene elimineres før en enkelt piksel inspiseres. - Teksturatlas og 'SourceRectangle'-støtte: Håndterer sprites pakket inn i teksturatlas ved å bruke kilderektangelforskyvninger.
- Kalkulert overlappingsvindu: Vi går bare over det nøyaktige skjæringsrektangelet mellom begge sprites (
Math.Max(a.Top, b.Top), etc.). - Alpha Short-Circuiting: Hvis Sprite A sin piksel er gjennomsiktig (
A <= 20), hoppes Sprite B fullstendig over. - Property Hoisting: Tilgang til virtuelle egenskaper ('Bounds') i nestede løkker skaper tusenvis av strukturkopier. Vi lagrer dem i lokale stabelvariabler før vi går inn i loopen.
Her er den komplette, produksjonsklare koden:
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;
}
}
Denne rutinen kjøres på under 0,1 millisekunder på moderne Android-maskinvare og skrivebordssystemer.
7. Nivå 6: Broad-Phase Spatial Grid & Zero-Allocation Mobile GC
Tenk deg at spillet ditt har 100 blokker, 60 kuler og 20 fiender. Hvis du tester hver enhet mot alle andre enheter ved å bruke nestede 'for'-løkker, utfører du:
\(\frac{180 \times 179}{2} = 16 110 \text{ sjekker per ramme!}\)
Ved 60 FPS er det nesten 1 000 000 kollisjonskontroller hvert sekund.
For å skalere spillet ditt, må du implementere Bred-Phase Spatial Partitioning.
The Uniform Spatial Hash Grid
Vi deler skjermen inn i et 2D-rutenett med ensartede celler (i Blokkert: Pixel Panzer er hver celle \(150 x 150\) piksler). En enhet tester bare kollisjoner mot andre enheter som ligger i de samme rutenettcellene.
Mobilminneproblemet: GC Thrashing
Hvis SpatialGrid oppretter new List<Point>() eller new List<Block>() hver ramme, tildeler du megabyte med søppel hvert minutt på den administrerte haugen.
På Androids Mono runtime utløser dette hyppige Gen-0 søppelinnsamlinger, noe som forårsaker: "mono runtime: Native lock-konflikt (mono_class_is_subclass_)".
Spillet hakker og Google Play flagger spillet ditt med ANR-advarsler!
Løsningen: Gjenbrukbare skrapebuffere
Her er Zero-Allocation Spatial Hash Grid fra CollisionManager.cs i 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);
}
}
}
}
}
}
}
}
Ved å gjenbruke _scratchCells og _scratchCheckedEntities, kjører hele kollisjonstrinnet med 0 byte med heap-allokering per ramme.
8. Sammendragstabell for sammenligning
| Teknikk | Matematiske kostnader | Rotasjonsstøtte | Tunnelkjøring trygt? | Beste brukstilfelle i MonoGame |
|---|---|---|---|---|
AABB (Rektangel. Krysser) |
Ultrarask (~4 heltallssammenlikninger) | Nei | Nei | Grid blokker, kule bredfase, UI |
Sirkel (DistanceSquared) |
Veldig rask (3 muls, 0 sqrt) | Ja (invariant) | Nei | Runde skip, ildkuler, energikuler |
Circle vs Box (Clamp) |
Rask (Local MathHelper.Clamp) | Ja | Nei | Sirkulær spiller som navigerer i tette vegger |
| Swept Ray (Slab CCD) | Moderat (Parametrisk Raycast) | Ja | Ja | Raske snikskytterkuler, skinnevåpen, lasere |
Pixel-Perfect (IntersectsPixel) |
Selektiv (Restricted Sub-rect) | Ja | Nei | Uregelmessige spritekonturer, rettferdige hitboxes |
| Romlig rutenett (null-GC) | Bredfase (\(O(N^2) \to O(N)\)) | N/A | N/A | Tette bølger, kulehelvete, Android 60 FPS |
Real-World Production Showcases: Arar Games
Disse kollisjonsarkitekturene er ikke teoretiske eksperimenter – de er det virkelige ingeniørgrunnlaget som driver våre kommersielt utgitte titler:
- Blokkert: Pixel Panzer: Vårt retro-tankoverlevelsesarkadespill på Google Play og Microsoft Store. Den har vårt komplette to-lags kollisjonssystem: en null-allokering romlig hash-rutenett som filtrerer fallende blokker, og piksel-perfekt kontaktsjekking for stridsvogner, jagerfly, tårnskaller og ColorWheel elementær ammunisjon.
- Paint Trek: Vårt fartsfylte romskytespill med rotasjonssirkelkollisjon, kontinuerlig strålekasting og rakettforsvarssystemer med feid volum.
Konklusjon og neste trinn
MonoGame gir deg kraften til å designe kollisjonsdeteksjon som perfekt matcher behovene til spillet ditt. Ved å legge inn dyre Pixel-Perfect-sjekker bak raske AABB-tester, bruke Squared Distances for sirkler, og eliminere søppeloppsamling med Reusable Scratch Buffers, kan du levere konsoll-jevn 60/120 FPS-ytelse på både stasjonære og mobile plattformer.
Sjekk ut spillene våre i appbutikkene for å se disse kollisjonssystemene i aksjon, og begynn å implementere disse mønstrene i dine egne MonoGame-prosjekter i dag!
Butikklenker og ressurser
- Blokkert: Pixel Panzer på Google Play: Last ned for Android
- Blokkert: Pixel Panzer på Microsoft Store: Last ned for Windows
- MonoGame Framework: monogame.net
SEO nøkkelord og Hashtags
Søkeord: MonoGame 2D-kollisjonsdeteksjon, C#-spillutvikling, Rectangle.Skjærer MonoGame, pixel perfect collision C#, spatial hash-rutenett MonoGame, swept ray-kontinuerlig kollisjon, indiespillytelsesoptimalisering, nullallokeringsspillløkke, Android Pixel MonoGame-optimalisering, Trekint.
#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixelPanzer `##GameGame```##Paintrek``
Hiç yorum yok:
Yorum Gönder