Detectarea coliziunilor 2D în MonoGame: de la dreptunghiuri de bază la sisteme de pixeli perfecte cu alocare zero
Când construiți jocuri 2D în motoare vizuale precum Unity sau Godot, detectarea coliziunilor se simte adesea ca o serie de casete de selectare a inspectorului: apăsați un „BoxCollider2D” sau „CircleCollider2D”, atașați un „Rigidbody” și sperați că pasul de fizică internă nu se bâlbâie pe dispozitivele mobile.
În MonoGame și C#, totuși, aveți controlul total. Nu există nicio suprasarcină de fizică ascunsă, nicio inerție de rotație nedorită și nicio vârfuri misterioase de colectare a gunoiului (GC) care să vă fure bugetul de cadru.
La Arar Games, când am construit Blocked: Pixel Panzer și Paint Trek, buclele noastre de jocuri arcade au fost necesare pentru a procesa sute de gloanțe inamice de mare viteză, grile de cărămidă care explodează, turnulețe rotative ale tancurilor, survolări ale avioanelor de luptă și scuturi de particule la 60 până la 120 de dispozitive PC și Android FPS. Nu se punea în discuție un motor de fizică de uz general – aveam nevoie de o arhitectură de coliziune pe niveluri, construită special.
În acest ghid cuprinzător, bazat pe cod, vom începe de la elementele de bază absolute ale coliziunii MonoGame (Rectangle.Intersects cu sprite-uri Bullet și Enemy) și vom construi până la verificări avansate ale cercurilor, strângere mixtă, raycasts anti-tunel, Pixel-Perfect colisionare, coliziune optimizată pentru mobil și Zottiero de producție. Supraviețuirea GC.
1. Fundația: O ierarhie simplă „Sprite” MonoGame
Înainte de a detecta coliziunile, avem nevoie de entități de joc curate. În MonoGame, o entitate posedă în mod fundamental o poziție, o textură și un dreptunghi de delimitare.
Iată arhitectura de bază utilizată în jocurile noastre:
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);
}
}
Acum să creăm clase concrete „Player”, „Enemy” și „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;
}
}
Cu aceste entități la locul lor, să explorăm cum să detectăm coliziunile între ele, pornind de la cea mai simplă metodă.
2. Nivelul 1: Cea mai simplă coliziune – „Rectangle.Intersects” (AABB)
Cea mai fundamentală verificare a coliziunilor 2D din MonoGame este testul Axis-Aligned Bounding Box (AABB). Termenul „aliniat pe axă” înseamnă pur și simplu că marginile dreptunghiului sunt complet paralele cu axele \(X\) și \(Y\) ale ecranului (fără rotație).
MonoGame oferă o metodă rapidă, încorporată: Rectangle.Intersects(Rectangle value).
Cum funcționează „Rectangle.Intersects” sub capotă
Sub suprafață, MonoGame execută patru comparații întregi:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
Dacă sunt îndeplinite toate cele patru condiții, dreptunghiurile se suprapun. Dacă chiar și o condiție eșuează, o axă goală le separă și nu este posibilă nicio coliziune.
Cod de joc real: Bullet vs. Enemy în Game1.Update
Iată cum verificați coliziunile dintre o listă de gloanțe active și o listă de inamici activi în bucla principală de „Actualizare” MonoGame:
public class Game1 : Game
{
private List<Bullet> _bullets = new List<Bullet>();
private List<Enemy> _enemies = new List<Enemy>();
protected override void Update(GameTime gameTime)
{
// 1. Update bullet and enemy positions
foreach (var bullet in _bullets) bullet.Update(gameTime);
// 2. Collision Check: Bullets vs Enemies
for (int b = _bullets.Count - 1; b >= 0; b--)
{
var bullet = _bullets[b];
if (!bullet.IsActive) continue;
for (int e = _enemies.Count - 1; e >= 0; e--)
{
var enemy = _enemies[e];
if (!enemy.IsActive) continue;
// The AABB check!
if (bullet.Bounds.Intersects(enemy.Bounds))
{
// Collision occurred!
enemy.TakeDamage(bullet.Damage);
bullet.IsActive = false;
// Remove inactive bullet immediately
_bullets.RemoveAt(b);
if (!enemy.IsActive)
{
_enemies.RemoveAt(e);
}
// A bullet can only hit one enemy; break the inner loop
break;
}
}
}
base.Update(gameTime);
}
}
Sfat de performanță: Observați că repetăm înapoi (
for (int i = list.Count - 1; i >= 0; i--))! Dacă utilizațiforeachși încercați să apelați_bullets.Remove(bullet), C# aruncă oInvalidOperationException: Collection was modified. Iterarea înapoi permite eliminarea sigură a elementului fără probleme de reindexare a memoriei.
Secretul Arcade: „Fair Hitboxes” prin „Inflate”.
În jocurile retro precum Blocked: Pixel Panzer, texturile sprite includ adesea margini transparente sau vârfuri de antenă. Dacă rezervorul jucătorului explodează deoarece un glonț a atins un colț transparent gol al texturii sale, jucătorul se va simți înșelat.
Pentru ca coliziunea să se simtă receptivă și corectă, jocurile folosesc un Hitbox mai mic în interiorul sprite-ului folosind 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. Nivelul 2: Ciocnire cerc la cerc (imunitate la rotație)
Dreptunghiurile funcționează grozav pentru blocurile statice și plăcile de grilă, dar eșuează atunci când sprite-urile se rotesc. Când o navă spațială non-pătrată se rotește în Paint Trek, o casetă de delimitare aliniată pe axă trebuie să se extindă pentru a cuprinde colțurile care se rotesc, rezultând în „coliziuni fantomă” frustrante în aerul gol.
Pentru asteroizii circulari, globurile de energie orientate și navele spațiale rotative, Bounding Circles sunt soluția ideală.
Capcana rădăcinii pătrate
Două cercuri se ciocnesc atunci când distanța dintre centrele lor este mai mică sau egală cu suma razelor lor:
\(\text{Distanța}(C_A, C_B) \le r_A + r_B\)
În cod, calcularea distanței euclidiene folosește Math.Sqrt (sau Vector2.Distance). Cu toate acestea, calcularea rădăcinilor pătrate într-o buclă cu 200 de proiectile arde sute de cicluri inutile ale procesorului!
Comparând distanța pătrată cu suma razei pătrate, eliminăm complet rădăcina pătrată:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
Implementare MonoGame: Cerc vs. Cerc
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);
}
}
Acum integrați acest lucru direct într-o entitate:
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);
}
}
Zero rădăcini pătrate, imun la rotația sprite-urilor și fulgerător.
4. Nivelul 3: Forme mixte – Cerc vs. Cutie (MathHelper.Clamp)
Ce se întâmplă când o navă spațială circulară din Paint Trek navighează printr-un labirint strâns de bariere de apărare dreptunghiulare sau când un glonț rotund lovește un bloc pătrat în Blocat: Pixel Panzer?
Avem nevoie de coliziune Cerc vs. Dreptunghi.
Algoritmul de prindere
Strategia este de a găsi punctul de pe dreptunghi care este cel mai aproape de centrul cercului și apoi de a testa dacă distanța de la acel punct cel mai apropiat de centru este mai mică decât raza cercului.
MathHelper.Clamp de la MonoGame face acest lucru banal:
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);
}
}
Acum puteți testa scuturile jucătorilor împotriva cărămizilor dreptunghiulare cu alocări zero și precizie ridicată!
5. Nivelul 4: Detectare continuă a coliziunilor (CCD) și Raze măturate
Ai tras vreodată în jocul tău o cartușă de lunetist de hiper-viteză sau un laser cu pistol cu șină, doar ca să vezi cum glonțul trece prin magie direct printr-o navă inamică subțire fără a provoca daune?
Această eroare este cunoscută sub numele de tunnel.
Deoarece jocurile discrete se actualizează în pași de timp (\(\Delta t = 16,6\text{ms}\) la 60 FPS), un obiect care se mișcă cu 1.800 de pixeli pe secundă parcurge 30 de pixeli într-un singur cadru. Dacă corpul inamicului are o grosime de numai 15 pixeli, glonțul a fost în fața inamicului pe Cadrul 1 și complet în spatele inamicului pe Cadrul 2.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
Soluția: Segment măturat vs. Cutie (Metoda Slab)
În loc să testăm un singur punct, testăm întregul segment de linie care conectează poziția glonțului pe Cadrul 1 (previousPosition) la Cadrul 2 (currentPosition).
Iată metoda de producție de radiație a plăcii-intersecție din titlul nostru însoțitor 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;
}
}
În Blocked: Pixel Panzer, abilitatea continuă a jucătorului Laser Beam folosește acest raycast exact pentru a tăia șirurile de blocuri descendente fără a rata nici o singură coliziune.
6. Nivelul 5: Detectarea coliziunilor perfectă a pixelilor de nivel de producție
Acum atingem nivelul suprem de precizie 2D: Coliziune Pixel-Perfect.
Într-un shooter retro cu tancuri sau o luptă cu nave spațiale, formele neregulate (butoaie de tanc, aripi, carlinge) sunt înconjurate de pixeli transparenți în textura sprite. Când o rachetă inamică lovește acel spațiu transparent, jucătorii observă imediat.
Ciocnirea pixelilor perfecte inspectează canalele alfa (transparență) reale ale texturilor care se suprapun. Dacă doi pixeli netransparenti se suprapun la aceeași coordonată mondială, a avut loc o adevărată lovitură fizică.
Greșeala fatală: GetData în Update()
Multe tutoriale îi instruiesc pe începători să facă acest lucru:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
Apelarea Texture2D.GetData() în timpul rulării forțează procesorul să aștepte ca conducta GPU să se epuizeze, trăgând memoria texturii înapoi peste magistrală. Făcând acest lucru pentru zeci de sprites, fiecare cadru vă va reduce jocul de la 60 FPS la 5 FPS și va declanșa imediat ANR-urile Android!
Soluția testată în luptă de la Blocat: Pixel Panzer
În Blocked: Pixel Panzer, Sprite.cs nostru implementează o rutină IntersectsPixel ultra-optimizată construită în jurul a 5 reguli cruciale:
- Static Color Array Cache:
GetDataeste apelat exact o dată când textura se încarcă și este stocat într-unDictionary<Texture2D, Color[]>. - AABB Early Exit Guard: Dacă
Bounds.Intersects(other.Bounds)este fals, eliberăm imediat. 99% dintre verificări sunt eliminate înainte de inspectarea unui singur pixel. - Texture Atlas &
SourceRectangleSuport: Se ocupă de sprite-urile împachetate în atlasuri de texturi folosind offset-uri de dreptunghi sursă. - Fereastra de suprapunere calculată: Facem doar bucla peste dreptunghiul exact de intersecție dintre ambele sprite-uri (
Math.Max(a.Top, b.Top), etc.). - Scurtcircuit Alpha: Dacă pixelul lui Sprite A este transparent (
A <= 20), Sprite B este complet omis. - Property Hoisting: Accesarea proprietăților virtuale (
Bounds) în bucle imbricate creează mii de copii struct. Le stocăm în variabilele stivei locale înainte de a intra în buclă.
Iată codul complet, gata de producție:
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;
}
}
Această rutină se execută în mai puțin de 0,1 milisecunde pe hardware-ul și sistemele desktop moderne Android.
7. Nivelul 6: Grilă spațială în fază largă și GC mobil cu alocare zero
Imaginează-ți că jocul tău are 100 de blocuri, 60 de gloanțe și 20 de inamici. Dacă testați fiecare entitate față de orice altă entitate folosind bucle imbricate „for”, efectuați:
\(\frac{180 \times 179}{2} = 16.110 \text{ verificări pe cadru!}\)
La 60 FPS, adică aproape 1.000.000 de verificări de coliziune în fiecare secundă.
Pentru a vă scala jocul, trebuie să implementați Broad-Phase Spatial Partitioning.
Grila Uniform Spatial Hash
Împărțim ecranul într-o grilă 2D de celule uniforme (în Blocat: Pixel Panzer, fiecare celulă este de 150 \(\times 150\) pixeli). O entitate testează doar coliziunile cu alte entități care locuiesc în aceleași celule ale grilei.
Problema memoriei mobile: GC Thrashing
Dacă SpatialGrid dvs. creează listă nouă<Point>() sau listă nouă<Block>() la fiecare cadru, alocați megaocteți de gunoi în fiecare minut pe heap-ul gestionat.
Pe timpul de rulare Mono al Android, acest lucru declanșează colectări frecvente de gunoi Gen-0, provocând:
mono runtime: native lock contestation (mono_class_is_subclass_)
Jocul se bâlbâie și Google Play vă semnalează jocul cu avertismente ANR!
Soluția: Scratch Buffers reutilizabile
Iată Zero-Allocation Spatial Hash Grid de la CollisionManager.cs în 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);
}
}
}
}
}
}
}
}
Prin reutilizarea _scratchCells și _scratchCheckedEntities, întregul pas de coliziune rulează cu 0 octeți de alocare heap per cadru.
8. Tabel de comparație rezumat
| Tehnica | Costul matematic | Suport rotativ | Tuneling sigur? | Cel mai bun caz de utilizare în MonoGame |
|---|---|---|---|---|
AABB (Dreptunghi.Intersectări) |
Ultra-rapid (~4 comparații întregi) | Nu | Nu | Blocuri grilă, glonț cu fază largă, UI |
Cerc (DistanțaPătrat) |
Foarte rapid (3 muls, 0 sqrt) | Da (Invariant) | Nu | Nave rotunde, bile de foc, globuri de energie |
Cerc vs Box (Clamp) |
Rapid (Local MathHelper.Clamp) | Da | Nu | Jucător circular navigând pe pereți strânși |
| Swept Ray (Slab CCD) | Moderat (Raycast parametric) | Da | Da | Gloanțe de lunetist rapid, pistoale cu șină, lasere |
Pixel-Perfect (IntersectsPixel) |
Selectiv (Sub-rect restricționat) | Da | Nu | Contururi sprite neregulate, hitboxuri corecte |
| Grilă spațială (Zero-GC) | Fază largă (\(O(N^2) \la O(N)\)) | N/A | N/A | Valuri dense, iad de gloanțe, Android 60 FPS |
Vitrine de producție din lumea reală: Arar Games
Aceste arhitecturi de coliziune nu sunt experimente teoretice – ele sunt adevărata fundație de inginerie care alimentează titlurile noastre lansate comercial:
- Blocat: Pixel Panzer: Jocul nostru arcade de supraviețuire cu tancuri retro pe Google Play și Microsoft Store. Dispune de sistemul nostru complet de coliziune pe două niveluri: o rețea de hash spațială cu alocare zero, care filtrează blocurile care cad și o verificare a contactului perfectă a pixelilor pentru tancuri, avioane de luptă, carcase de turelă și muniție elementară ColorWheel.
- Paint Trek: shooter-ul nostru spațial rapid, care include coliziuni cu cerc rotațional, difuzare continuă de raze și sisteme de apărare împotriva rachetei cu volum redus.
Concluzie și pași următori
MonoGame îți oferă puterea de a proiecta detectarea coliziunilor care se potrivește perfect nevoilor jocului tău. Prin efectuarea verificărilor costisitoare Pixel-Perfect în spatele testelor rapide AABB, utilizând Squared Distances pentru cercuri și eliminând colectarea gunoiului cu Reutilizabile Scratch Buffers, puteți oferi performanțe de 60/120 FPS netede pentru consolă atât pe platforme desktop, cât și pe cele mobile.
Consultați jocurile noastre din magazinele de aplicații pentru a vedea aceste sisteme de coliziune în acțiune și începeți să implementați aceste modele în propriile proiecte MonoGame astăzi!
Link-uri și resurse pentru magazin
- Blocat: Pixel Panzer pe Google Play: Descărcare pentru Android
- Blocat: Pixel Panzer pe Microsoft Store: Descărcare pentru Windows
- Cadru MonoGame: monogame.net
Cuvinte cheie și hashtag-uri SEO
Cuvinte cheie: Detectare coliziuni 2D MonoGame, dezvoltare joc C#, Rectangle.Intersects MonoGame, coliziune perfectă a pixelilor C#, grilă hash spațială MonoGame, coliziune continuă swept ray, optimizare a performanței jocului indie, buclă de joc cu alocare zero, optimizare Android MonoGame, Arar Games, Paint Trek Pixel Panzer, Paint Trek Panzer.
#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #GameDev #GameDev #GameDev #GameProgramming
Hiç yorum yok:
Yorum Gönder