2D ütközésészlelés a MonoGame játékban: az alap téglalapoktól a nulla kiosztású pixel tökéletes rendszerekig
Amikor 2D-s játékokat készítenek olyan vizuális motorokban, mint a Unity vagy a Godot, az ütközésészlelés gyakran úgy tűnik, mintha egy sor ellenőrző jelölőnégyzet lenne: rácsapsz egy "BoxCollider2D" vagy "CircleCollider2D"-re, csatolsz egy "Rigidbody"-t, és reméled, hogy a belső fizika lépései nem akadoznak a mobileszközökön.
A MonoGame és C# játékban azonban teljes mértékben te irányítod. Nincs rejtett fizika, nincs nem kívánt forgási tehetetlenség, és nincsenek rejtélyes szemétgyűjtési (GC) tüskék, amelyek ellopnák a keret költségvetését.
Az Arar Games-en, amikor megépítettük a Blocked: Pixel Panzert és a Paint Treket, arcade játékhurkjainknak több száz nagysebességű ellenséges lövedék, felrobbanó téglarács, forgó harckocsi tornyok, vadászrepülőgépek átrepülése és részecskepajzsok feldolgozásához volt szükség Windows 20PS-en és 60 FPS-en egyaránt. Az általános célú fizikai motor szóba sem jöhetett – szükségünk volt egy erre a célra épített, többszintű ütközési architektúrára.
Ebben az átfogó, kódvezérelt útmutatóban a MonoGame ütközés abszolút alapjaiból indulunk ki ("Rectangle.Intersects" valódi "Bullet" és "Enemy" sprite-ekkel), és továbbhaladunk a fejlett körellenőrzésekig, vegyes rögzítéshez, alagút-gátló sugárküldéshez, éles szintű Pixel-Perfectation-összeállításhoz**, és kiosztáshoz**. mobil GC túlélésre optimalizálva.
1. Az alapítvány: Egy egyszerű MonoGame Sprite hierarchia
Az ütközések észlelése előtt tiszta játék entitásokra van szükségünk. A MonoGame-ben egy entitás alapvetően egy pozícióval, egy textúrával és egy határoló téglalappal rendelkezik.
Íme a játékainkban használt alap entitás architektúra:
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);
}
}
Most hozzunk létre konkrét „Player”, „Enemy” és „Bullet” osztályokat:
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;
}
}
Ha ezek az entitások a helyükön vannak, akkor a legegyszerűbb módszertől kezdve vizsgáljuk meg, hogyan lehet észlelni a köztük lévő ütközéseket.
2. 1. szint: A legegyszerűbb ütközés – „Téglalap.Metszik” (AABB)
A MonoGame legalapvetőbb 2D ütközési ellenőrzése az Axis-Aligned Bounding Box (AABB) teszt. A "tengelyhez igazított" kifejezés egyszerűen azt jelenti, hogy a téglalap élei teljesen párhuzamosak a képernyő \(X\) és \(Y\) tengelyével (nincs elforgatás).
A MonoGame egy gyors, beépített módszert kínál: Rectangle.Intersects(Rectangle value).
Hogyan működik a Rectangle.Intersects a motorháztető alatt
A felület alatt a MonoGame négy egész szám összehasonlítást hajt végre:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
Ha mind a négy feltétel teljesül, a téglalapok átfedik egymást. Ha akár egy feltétel is meghiúsul, egy üres tengely választja el őket, és nem lehetséges ütközés.
Valódi játékkód: "Bullet" vs. "Enemy" a "Game1.Update"-ben
Így ellenőrizheti az ütközéseket az aktív golyók listája és az aktív ellenségek listája között a fő MonoGame „Frissítés” hurkon belül:
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);
}
}
Tipp a teljesítményhez: Figyeljük meg, hogy visszafelé iterálunk (
for (int i = list.Count - 1; i >= 0; i--))! Ha a "foreach" parancsot használja, és megpróbálja meghívni a "_bullets.Remove(bullet)" parancsot, a C# egy "InvalidOperationException: Collection was modified" (InvalidOperationException: Collection was modified) üzenetet dob. A visszafelé történő iteráció lehetővé teszi az elemek biztonságos eltávolítását memória-újraindexelési problémák nélkül.
The Arcade Secret: "Fair Hitboxes" a "Felfújás" segítségével
Az olyan retro játékokban, mint a Blocked: Pixel Panzer, a sprite textúrák gyakran tartalmaznak átlátszó margókat vagy antennatüskéket. Ha a játékos tankja felrobban, mert egy golyó hozzáért a textúrájának egy üres átlátszó sarkához, a játékos becsapva érzi magát.
Annak érdekében, hogy az ütközés érzékeny és méltányos legyen, a játékok egy kisebb Hitboxot használnak a sprite belsejében a "Rectangle.Inflate" funkcióval:
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. szint: Kör-kör ütközés (Forgástűrés)
A téglalapok kiválóan használhatók statikus blokkokhoz és rácscsempékhez, de nem működnek, amikor a sprite-ok forognak. Amikor egy nem négyzet alakú űrhajó forog a Paint Trek-ben, egy tengelyhez igazított határolókeretnek ki kell tágulnia, hogy körülzárja a forgó sarkokat, ami frusztráló "fantomütközéseket" eredményez az üres levegőben.
A körkörös aszteroidák, az egymásra irányító energiagömbök és a forgó űrhajók esetében a Határkörök az ideális megoldás.
A négyzetgyökér csapda
Két kör ütközik, ha a középpontjuk távolsága kisebb vagy egyenlő, mint sugaruk összege:
\(\text{Távolság}(C_A, C_B) \le r_A + r_B\)
A kódban az euklideszi távolság kiszámítása a "Math.Sqrt" (vagy "Vector2.Distance") függvényt használja. Azonban a négyzetgyökök kiszámítása egy hurokban 200 lövedékkel több száz felesleges CPU ciklust éget el!
A négyzetes távolság és a négyzetes sugárösszeg összehasonlításával teljesen kiküszöböljük a négyzetgyököt:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
MonoGame megvalósítás: "Kör" vs. "Kör".
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);
}
}
Most integrálja ezt közvetlenül egy entitásba:
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);
}
}
Nulla négyzetgyök, immunis a sprite forgására, és villámgyors.
4. 3. szint: Vegyes alakzatok – Kör kontra doboz (MathHelper.Clamp)
Mi történik, ha a Paint Trek kör alakú űrhajója áthalad a téglalap alakú védelmi akadályok szűk labirintusán, vagy ha egy kerek golyó eltalálja a négyzet alakú blokkot a Blocked: Pixel Panzer-ban?
Szükségünk van a Kör vs. Téglalap ütközésre.
A rögzítési algoritmus
A stratégia az, hogy megkeressük a téglalapon azt a pontot, amely a legközelebb van a kör középpontjához, majd megvizsgáljuk, hogy a legközelebbi pont és a középpont távolsága kisebb-e, mint a kör sugara.
A MonoGame MathHelper.Clamp-ja ezt triviálissá teszi:
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);
}
}
Mostantól tesztelheti a játékospajzsokat téglalap alakú téglákkal szemben nulla kiosztással és nagy pontossággal!
5. 4. szint: Folyamatos ütközésészlelés (CCD) és söpört sugarak
Lőttél már valaha egy nagysebességű mesterlövész kört vagy egy sínpisztolylézert a játékban, csak azért, hogy megnézd, ahogy a golyó varázslatosan áthalad egy vékony ellenséges hajón anélkül, hogy sebzést okozna?
Ezt a hibát tunneling néven ismerik.
Mivel a diszkrét játékok időbeli lépésekben frissülnek (\(\Delta t = 16,6\text{ms}\) 60 FPS-nél), egy 1800 képpont/másodperc sebességgel mozgó objektum 30 képpontot tesz meg egyetlen képkockán belül. Ha az ellenséges hajótest csak 15 pixel vastag, akkor a golyó az 1. képkockán az ellenség előtt volt, a 2. képkockán pedig teljesen az ellenség mögött.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
A megoldás: Seprett szegmens kontra doboz (födém módszer)
Egyetlen pont tesztelése helyett teszteljük a teljes vonalszakaszt, amely összeköti a golyó pozícióját az 1. képkockán ("previousPosition") a 2. képkockával ("currentPosition").
Íme a gyártási raycasting lemez-metszés módszer a SpiralWar kísérőcímünkből:
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;
}
}
A Blocked: Pixel Panzer játékban a játékos folyamatos Lézersugár-készsége pontosan ezt a raycast-ot használja a leszálló blokkok sorain való átmetszéshez anélkül, hogy egyetlen ütközést is kihagyna.
6. 5. szint: gyártási fokozatú pixel-tökéletes ütközésészlelés
Most elértük a 2D pontosság legmagasabb szintjét: Pixel-Perfect Collision.
A retró tanklövőben vagy űrhajó kutyaharcban a szabálytalan formákat (tankcsövek, szárnyak, pilótafülke pilótafülke) átlátszó pixelek veszik körül a sprite textúrában. Amikor egy ellenséges rakéta eléri ezt az átlátszó helyet, a játékosok azonnal észreveszik.
A pixel-tökéletes ütközés megvizsgálja az átfedő textúrák tényleges alfa (átlátszósági) csatornáit. Ha két nem átlátszó képpont ugyanazon a világkoordinátán fedi egymást, akkor valódi fizikai találat történt.
A végzetes hiba: "GetData" az "Update()"-ben
Számos oktatóanyag erre utasítja a kezdőket:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
A "Texture2D.GetData()" futás közbeni meghívása arra kényszeríti a CPU-t, hogy megvárja, amíg a GPU-folyamat kiürül, és visszahúzza a textúra memóriáját a buszon. Ha ezt több tucatnyi sprite-nál csinálod, minden képkocka 60 FPS-ről 5 FPS-re gyorsítja a játékodat, és azonnal aktiválja az Android ANR-eket!
A harcban tesztelt megoldás a Blocked: Pixel Panzertől
A Blocked: Pixel Panzer-ben a Sprite.cs egy ultraoptimalizált IntersectsPixel rutint valósít meg, amely 5 kulcsfontosságú szabály köré épül fel:
- Statikus színtömb gyorsítótár: A „GetData” pontosan egyszer kerül meghívásra a textúra betöltésekor, és a „Szótárban<Texture2D, Color[]>” tárolódik.
- AABB Early Exit Guard: Ha a
Bounds.Intersects(other.Bounds)hamis, azonnal kimentjük. Az ellenőrzések 99%-a megszűnik egyetlen képpont vizsgálata előtt. - Texture Atlas &
SourceRectangletámogatás: Kezeli a textúra atlaszokba csomagolt sprite-eket a forrástéglalap-eltolások segítségével. - Kiszámított átfedési ablak: Csak a két sprite közötti pontos metszésszögű téglalap fölé hurkolunk (
Math.Max(a.Top, b.Top)stb.). - Alfa rövidzárlat: Ha a Sprite A képpontja átlátszó (
A <= 20), a Sprite B teljesen kimarad. - Tulajdonság kiemelés: A beágyazott hurkokban lévő virtuális tulajdonságok (
határok) elérése több ezer struktúra-másolatot hoz létre. Ezeket lokális veremváltozókban tároljuk, mielőtt belépnénk a ciklusba.
Íme a teljes, gyártásra kész kód:
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;
}
}
Ez a rutin 0,1 ezredmásodperc alatt fut le modern Android hardvereken és asztali rendszereken.
7. 6. szint: Széles fázisú térháló és nulla kiosztású mobil GC
Képzeld el, hogy a játékodban 100 blokk, 60 golyó és 20 ellenség található. Ha minden entitást minden más entitáshoz képest tesztel beágyazott "for" ciklusok segítségével, a következőket kell végrehajtania:
\(\frac{180 \times 179}{2} = 16 110 \text{ csekk keretenként!}\)
60 FPS-nél ez közel 1 000 000 ütközési ellenőrzés másodpercenként.
A játék méretezéséhez meg kell valósítania a Széles fázisú térbeli particionálást.
Az egységes térbeli hash-rács
A képernyőt egységes cellákból álló 2D rácsra osztjuk (a Blocked: Pixel Panzer-ben minden cella 150 \(\x 150\) pixel). Egy entitás csak az ütközéseket teszteli más entitásokkal szemben, amelyek ugyanabban a rácscellában találhatók.
A mobil memória probléma: GC Thrashing
Ha a SpatialGrid minden keretben létrehoz egy új List<Point>()' vagy new List
Az Android Mono futtatókörnyezetében ez gyakori Gen-0 szemétgyűjtést vált ki, ami:
mono futásidejű: Natív zárolási verseny (mono_class_is_subclass_)
A játék akadozik, és a Google Play ANR-figyelmeztetésekkel jelzi a játékodat!
A megoldás: újrafelhasználható kaparós pufferek
Íme a Zero-Allocation Spatial Hash Grid a `CollisionManager.cs-ből a Blocked: Pixel Panzer-ban:
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);
}
}
}
}
}
}
}
}
A "_scratchCells" és a "_scratchCheckedEntities" újrafelhasználásával a teljes ütközési lépés 0 bájt halomkiosztás keretenként fut le.
8. Összefoglaló összehasonlító táblázat
| Technika | Matematikai költség | Rotációs támogatás | Biztonságos alagútépítés? | Legjobb használati eset a MonoGame |
|---|---|---|---|---|
AABB (Téglalap.Metszik) |
Ultra-gyors (~4 egész szám összehasonlítása) | Nem | Nem | Rács blokkok, bullet széles fázisú, UI |
Kör (DistanceSquared) |
Nagyon gyors (3 muls, 0 négyzetméter) | Igen (változatlan) | Nem | Kerek hajók, tűzgolyók, energiagömbök |
| Kör kontra doboz ("bilincs") | Gyors (Local MathHelper.Clamp) | Igen | Nem | A szűk falakon navigáló kör alakú játékos |
| Swept Ray (Slab CCD) | Mérsékelt (Paraméteres Raycast) | Igen | Igen | Gyors mesterlövész golyók, sínfegyverek, lézerek |
| Pixel-Perfect ("IntersectsPixel") | Szelektív (Korlátozott aljogosultság) | Igen | Nem | Szabálytalan sprite-kontúrok, tisztességes hitboxok |
| Térháló (Zero-GC) | Széles fázisú (\(O(N^2) \to O(N)\)) | N/A | N/A | Sűrű hullámok, golyó poklok, Android 60 FPS |
Valós produkciós bemutatók: Arar Games
Ezek az ütközési architektúrák nem elméleti kísérletek – ők jelentik a valódi mérnöki alapot a kereskedelmi forgalomba kerülő címeinkhez:
- Letiltva: Pixel Panzer: Retro tank túlélő arcade játékunk a Google Playen és a Microsoft Store-on. Tartalmazza a teljes, kétszintű ütközési rendszerünket: nulla kiosztású térbeli hash rács, amely kiszűri a leeső blokkokat, és pixel-tökéletes érintkezés-ellenőrzés tankokhoz, vadászrepülőgépekhez, toronyhéjakhoz és ColorWheel elemi lőszerekhez.
- Paint Trek: Gyors tempójú űrlövöldözőnk rotációs körütközéssel, folyamatos sugársugárzással és nagy térfogatú rakétavédelmi rendszerekkel.
Következtetés és következő lépések
A MonoGame lehetővé teszi, hogy olyan ütközésérzékelést tervezzen, amely tökéletesen megfelel a játék igényeinek. A drága Pixel-Perfect ellenőrzések gyors AABB-tesztek mögé zárva, a Négyzetes távolságok használatával a köröknél, és kiküszöbölve a szemétgyűjtést az Újrafelhasználható Scratch Bufferekkel, konzolon egyenletes, 60/120 FPS-es teljesítményt biztosíthat asztali és mobil platformon egyaránt.
Tekintse meg játékainkat az alkalmazásboltokban, hogy megtekinthesse ezeket az ütközési rendszereket működés közben, és még ma kezdje el ezeket a mintákat implementálni saját MonoGame projektjeibe!
Üzleti linkek és források
- Letiltva: Pixel Panzer a Google Playen: Letöltés Androidra
- Letiltva: Pixel Panzer a Microsoft Store-ban: Letöltés Windowshoz
- MonoGame Framework: monogame.net
SEO kulcsszavak és hashtagek
Kulcsszavak: MonoGame 2D ütközésészlelés, C# játékfejlesztés, Rectangle.Intersects MonoGame, pixel perfect collision C#, térbeli hash grid MonoGame, swept ray folyamatos ütközés, független játék teljesítményoptimalizálása, nulla kiosztású játékhurok, Android Arlock B játék optimalizálása, Panzer, Pixel Trek.
#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #Teljesítményoptimalizálás #CleanCode #PanzertTreixel #ArarGames #Játékprogramozás`
Hiç yorum yok:
Yorum Gönder