2D otkrivanje sudara u MonoGameu: od osnovnih pravokutnika do sustava savršenih piksela bez dodjele nule
Kada gradite 2D igre u vizualnim motorima kao što su Unity ili Godot, otkrivanje sudara često izgleda kao niz potvrdnih okvira inspektora: stavite BoxCollider2D ili CircleCollider2D, pričvrstite Rigidbody i nadate se da korak unutarnje fizike neće zastajkivati na mobilnim uređajima.
U MonoGame i C#, međutim, vi imate potpunu kontrolu. Nema skrivenih fizičkih troškova, nema neželjene rotacijske inercije i nema tajanstvenih skokova skupljanja smeća (GC) koji vam kradu budžet za okvir.
U Arar Games, kada smo izgradili Blocked: Pixel Panzer i Paint Trek, naše petlje arkadnih igara trebale su obraditi stotine brzih neprijateljskih metaka, eksplodirajućih mreža od cigala, rotirajućih tenkovskih kupola, preleta borbenih aviona i štitova od čestica pri 60 do 120 FPS na Windows PC-u i Android uređajima. Fizički motor opće namjene nije dolazio u obzir - trebala nam je namjenski izgrađena višeslojna arhitektura sudara.
U ovom sveobuhvatnom vodiču vođenom kodom, krenut ćemo od apsolutnih osnova MonoGame kolizije ('Rectangle.Intersects' sa stvarnim Bullet i Enemy spriteovima) i prijeći na napredne provjere krugova, miješano stezanje, anti-tuneliranje raycasta, Pixel-Perfect sudara proizvodne kvalitete i Zero-Allocation Spatial Rešetke optimizirane za preživljavanje mobilnog GC-a.
1. Temelj: jednostavna hijerarhija MonoGame Sprite
Prije otkrivanja sudara potrebni su nam čisti entiteti igre. U MonoGameu, entitet u osnovi posjeduje položaj, teksturu i granični pravokutnik.
Ovo je osnovna arhitektura entiteta koja se koristi u našim igrama:
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);
}
}
Kreirajmo sada konkretne klase Igrač, Neprijatelj i Metak:
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;
}
}
S tim entitetima na mjestu, istražimo kako otkriti kolizije između njih, počevši od najjednostavnije metode.
2. Razina 1: Najjednostavniji sudar – Rectangle.Intersects (AABB)
Najosnovnija 2D provjera sudara u MonoGameu je Axis-Aligned Bounding Box (AABB) test. Izraz "poravnano s osi" jednostavno znači da su rubovi pravokutnika potpuno paralelni s \(X\) i \(Y\) osi zaslona (bez rotacije).
MonoGame pruža brzu, ugrađenu metodu: Rectangle.Intersects(Rectangle value).
Kako Rectangle.Intersects radi ispod poklopca
Ispod površine, MonoGame izvršava četiri cjelobrojne usporedbe:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
Ako su ispunjena sva četiri uvjeta, pravokutnici se preklapaju. Ako čak i jedan uvjet ne ispuni, prazna os ih razdvaja i nije moguć sudar.
Pravi kod igre: Bullet protiv Enemy u Game1.Update
Evo kako provjeravate kolizije između popisa aktivnih metaka i popisa aktivnih neprijatelja unutar vaše glavne MonoGame Update petlje:
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);
}
}
Savjet za performanse: Primijetite da ponavljamo unatrag (
for (int i = list.Count - 1; i >= 0; i--))! Ako koristiteforeachi pokušate pozvati_bullets.Remove(bullet), C# izbacujeInvalidOperationException: Collection was modified. Ponavljanje unatrag omogućuje sigurno uklanjanje elementa bez problema s ponovnim indeksiranjem memorije.
The Arcade Secret: "Fair Hitboxes" putem Inflate
U retro igrama kao što je Blocked: Pixel Panzer teksture spritea često uključuju prozirne margine ili antenske šiljke. Ako igračev spremnik eksplodira jer je metak dotaknuo prazan prozirni kut njegove teksture, igrač će se osjećati prevarenim.
Kako bi sudar bio brz i pošten, igre koriste manji Hitbox unutar spritea koristeći 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. Razina 2: Sudar od kruga do kruga (otpornost na rotaciju)
Pravokutnici su izvrsni za statične blokove i rešetkaste pločice, ali ne uspijevaju kada se spriteovi okreću. Kada se nekvadratni svemirski brod rotira u Paint Treku, granični okvir poravnat s osi mora se proširiti kako bi obuhvatio kutove koji se okreću, što rezultira frustrirajućim "fantomskim sudarima" u praznom zraku.
Za kružne asteroide, energetske kugle za samonavođenje i rotirajuće svemirske letjelice, Ograničujući krugovi idealno su rješenje.
Zamka kvadratnog korijena
Dvije se kružnice sudaraju kada je udaljenost između njihovih središta manja ili jednaka zbroju njihovih polumjera:
\(\text{Udaljenost}(C_A, C_B) \le r_A + r_B\)
U kodu, izračunavanje euklidske udaljenosti koristi Math.Sqrt (ili Vector2.Distance). Međutim, izračunavanje kvadratnih korijena u petlji s 200 projektila troši stotine nepotrebnih CPU ciklusa!
Uspoređujući kvadrat udaljenosti s kvadratom zbroja radijusa, potpuno eliminiramo kvadratni korijen:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
Implementacija MonoGame: Krug protiv Kruga
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);
}
}
Sada integrirajte ovo izravno u entitet:
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);
}
}
Bez kvadratnih korijena, otporan na rotaciju spritea i brz kao munja.
4. Razina 3: Mješoviti oblici – Krug protiv kutije (MathHelper.Clamp)
Što se događa kada kružni svemirski brod u Paint Treku prolazi kroz uski labirint pravokutnih obrambenih barijera ili kada okrugli metak pogodi kvadratni blok u Blocked: Pixel Panzer?
Treba nam Krug protiv pravokutnika sudar.
Algoritam stezanja
Strategija je pronaći točku na pravokutniku koja je najbliža središtu kruga, a zatim testirati je li udaljenost od te najbliže točke do središta manja od polumjera kruga.
MonoGameov MathHelper.Clamp čini ovo trivijalnim:
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);
}
}
Sada možete testirati štitove igrača protiv pravokutnih cigli s nula dodjela i visokom preciznošću!
5. Razina 4: Kontinuirana detekcija sudara (CCD) & Swept Rays
Jeste li ikada u svojoj igrici ispalili snajperski metak visoke brzine ili railgun laser, samo da biste gledali kako metak magično prolazi ravno kroz tanki neprijateljski brod bez nanošenja štete?
Ovaj bug je poznat kao tuneliranje.
Budući da se diskretne igre ažuriraju u vremenskim koracima (\(\Delta t = 16,6\text{ms}\) pri 60 FPS), objekt koji se kreće brzinom od 1800 piksela u sekundi putuje 30 piksela u jednom okviru. Ako je trup neprijatelja debeo samo 15 piksela, metak je bio ispred neprijatelja na kadru 1, a potpuno iza neprijatelja u kadru 2.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
Rješenje: precrtani segment u odnosu na okvir (metoda ploča)
Umjesto testiranja jedne točke, testiramo cijeli segment linije koji povezuje poziciju grafičke oznake na okviru 1 (previousPosition) s okvirom 2 (currentPosition).
Ovo je metoda presjeka ploča raycastinga iz našeg popratnog 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;
}
}
U Blocked: Pixel Panzer, igračeva kontinuirana vještina laserske zrake koristi točan raycast za rezanje nizova padajućih blokova bez propuštanja ijednog sudara.
6. Razina 5: Pixel-Perfect Collision Detection proizvodne razine
Sada smo dosegli vrhunsku razinu 2D točnosti: Pixel-Perfect Collision.
U retro tenkovskoj pucačini ili zračnoj borbi svemirskih brodova, nepravilni oblici (cijev tenka, krila, kokpit kokpita) okruženi su prozirnim pikselima u teksturi spritea. Kada neprijateljski projektil pogodi taj prozirni prostor, igrači to odmah primjećuju.
Pixel-perfect collision provjerava stvarne alfa (prozirne) kanale tekstura koje se preklapaju. Ako se dva netransparentna piksela preklapaju na istoj svjetskoj koordinati, dogodio se pravi fizički pogodak.
Fatalna pogreška: GetData unutar Update()
Mnogi vodiči upućuju početnike da to učine:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
Pozivanje Texture2D.GetData() tijekom vremena izvođenja prisiljava CPU da čeka da se GPU cjevovod ispere, povlačeći memoriju teksture natrag preko sabirnice. Ako to učinite za desetke spriteova u svakom okviru, vaša igra će se smanjiti sa 60 FPS na 5 FPS i odmah pokrenuti Android ANR-ove!
Rješenje testirano u bitkama iz Blokirano: Pixel Panzer
U Blocked: Pixel Panzer, naš Sprite.cs implementira ultra-optimiziranu IntersectsPixel rutinu izgrađenu oko 5 ključnih pravila:
- Static Color Array Cache:
GetDatase poziva točno jednom kada se tekstura učitava i pohranjuje uRječnik<Texture2D, Color[]>. - AABB Early Exit Guard: Ako je
Bounds.Intersects(other.Bounds)netočan, odmah izlazimo. 99% provjera eliminira se prije provjere jednog piksela. - Texture Atlas &
SourceRectanglepodrška: Rukuje spriteovima upakiranima u atlase tekstura koristeći pomake izvornog pravokutnika. - Prozor izračunatog preklapanja: Prelazimo samo preko točnog pravokutnika presjeka između oba spritea (
Math.Max(a.Top, b.Top), itd.). - Alfa kratki spoj: Ako je piksel Sprite A proziran (
A <= 20), Sprite B je potpuno preskočen. - Podizanje svojstava: Pristup virtualnim svojstvima (
Granice) u ugniježđenim petljama stvara tisuće kopija strukture. Spremamo ih u lokalne varijable stoga prije ulaska u petlju.
Ovdje je potpuni kod spreman za proizvodnju:
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;
}
}
Ova se rutina izvršava za manje od 0,1 milisekunde na modernom Android hardveru i stolnim sustavima.
7. Razina 6: prostorna mreža široke faze i mobilni GC s nultom dodjelom
Zamislite da vaša igra ima 100 blokova, 60 metaka i 20 neprijatelja. Ako svaki entitet testirate u odnosu na svaki drugi entitet pomoću ugniježđenih petlji `za', izvodite:
\(\frac{180 \times 179}{2} = 16,110 \text{ provjera po okviru!}\)
Pri 60 FPS, to je gotovo 1.000.000 provjera sudara svake sekunde.
Da biste skalirali svoju igru, morate implementirati Broad-Phase Spatial Partitioning.
Jedinstvena prostorna hash mreža
Zaslon dijelimo na 2D mrežu jednakih ćelija (u Blokirano: Pixel Panzer, svaka ćelija ima 150$ \puta 150$ piksela). Entitet samo testira kolizije s drugim entitetima koji se nalaze u istim ćelijama mreže.
Problem mobilne memorije: GC Thrashing
Ako vaš SpatialGrid stvara novi List<Point>() ili novi List<Block>() svaki okvir, vi svake minute dodjeljujete megabajte smeća na upravljanoj hrpi.
U Androidovom Mono runtimeu, ovo pokreće česte Gen-0 Garbage Collections, uzrokujući:
mono runtime: nadmetanje nad izvornim zaključavanjem (mono_class_is_subclass_)
Igra zastajkuje i Google Play označava vašu igru upozorenjima o ANR-u!
Rješenje: Scratch pufferi za višekratnu upotrebu
Ovdje je Zero-Allocation Spatial Hash Grid iz CollisionManager.cs u 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);
}
}
}
}
}
}
}
}
Ponovnom upotrebom _scratchCells i _scratchCheckedEntities, cijeli korak sudara izvodi se s 0 bajtova raspodjele hrpe po okviru.
8. Sažeta tablica usporedbe
| Tehnika | Matematički trošak | Rotacijska podrška | Je li tuneliranje sigurno? | Najbolja upotreba u MonoGame |
|---|---|---|---|---|
AABB (Pravokutnik.Sjecišta) |
Ultrabrzo (~4 usporedbe cijelih brojeva) | Ne | Ne | Mrežni blokovi, široka faza metka, UI |
Krug (Udaljenost na kvadrat) |
Vrlo brzo (3 muls, 0 sqrt) | Da (nepromjenjivo) | Ne | Okrugli brodovi, vatrene kugle, energetske kugle |
Krug protiv kutije (Stega) |
Brzo (Local MathHelper.Clamp) | Da | Ne | Kružni igrač koji se kreće kroz uske zidove |
| Swept Ray (Slab CCD) | Umjereno (Parametric Raycast) | Da | Da | Brzi snajperski meci, railguns, laseri |
Pixel-Perfect (IntersectsPixel) |
Selektivno (ograničeno podpravo) | Da | Ne | Nepravilne konture spritea, pošteni hitboxovi |
| Prostorna mreža (Zero-GC) | Široka faza (\(O(N^2) \do O(N)\)) | N/A | N/A | Gusti valovi, pakao od metaka, Android 60 FPS |
Prikazi produkcije u stvarnom svijetu: Arar igre
Ove kolizijske arhitekture nisu teoretski eksperimenti - one su pravi inženjerski temelj koji pokreće naše komercijalno objavljene naslove:
- Blokirano: Pixel Panzer: Naša retro arkadna igra za preživljavanje tenkova na Google Playu i Microsoft Storeu. Sadrži naš potpuni dvoslojni sustav sudara: prostornu raspršenu rešetku s nultom raspodjelom koja filtrira padajuće blokove i savršenu provjeru kontakta za tenkove, borbene zrakoplove, kupole i elementarno streljivo ColorWheel.
- Paint Trek: Naša brza svemirska pucačina koja sadrži sudar u rotacijskom krugu, kontinuirano emitiranje zraka i obrambene sustave od raketnih projektila s precrtanim volumenom.
Zaključak i sljedeći koraci
MonoGame vam daje moć dizajniranja detekcije sudara koja savršeno odgovara potrebama vaše igre. Usklađivanjem skupih Pixel-Perfect provjera iza brzih AABB testova, korištenjem Kvadratne udaljenosti za krugove i uklanjanjem skupljanja smeća s Reusable Scratch Buffers, možete isporučiti glatku izvedbu od 60/120 FPS konzole na stolnim i mobilnim platformama.
Provjerite naše igre u trgovinama aplikacija kako biste vidjeli ove sustave sudara na djelu i počnite implementirati ove obrasce u svoje MonoGame projekte već danas!
Veze i resursi trgovine
- Blokirano: Pixel Panzer na Google Playu: Preuzimanje za Android
- Blokirano: Pixel Panzer u Microsoft Storeu: Preuzimanje za Windows
- Okvir MonoGame: monogame.net
SEO ključne riječi i hashtagovi
Ključne riječi: MonoGame 2D otkrivanje sudara, C# razvoj igre, Rectangle.Intersects MonoGame, pixel perfect collision C#, prostorna hash grid MonoGame, swept ray kontinuirana kolizija, optimizacija performansi indie igre, zero allocation game loop, Android MonoGame optimizacija, 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