2D-törmäystentunnistus MonoGamessa: perussuorakulmioista nolla-allokaatioon pikselitäydellisiin järjestelmiin
Kun rakennetaan 2D-pelejä visuaalisissa moottoreissa, kuten Unity tai Godot, törmäysten havaitseminen tuntuu usein sarjalta tarkastajien valintaruutuja: lyötät "BoxCollider2D" tai "CircleCollider2D", kiinnität "Rigidbody" ja toivot, että sisäisen fysiikan vaihe ei pätki mobiililaitteissa.
MonoGamessa ja C#:ssa olet kuitenkin täysin hallinnassa. Yläpuolella ei ole piilotettua fysiikkaa, ei-toivottua pyörimishitautta eikä salaperäisiä roskakeräyspiikkejä, jotka varastavat kehysbudjettisi.
Arar Gamesissa, kun rakensimme Blocked: Pixel Panzerin ja Paint Trekin, arcade-pelisilmukamme tarvitsivat satojen nopeiden vihollisen luotien, räjähtävien tiiliverkkojen, pyörivien tankkitornien, hävittäjälentokoneiden ohituksia ja hiukkassuojaimia Windows- ja 60 FPS-laitteissa. Yleiskäyttöinen fysiikkamoottori ei tullut kysymykseen – tarvitsimme tarkoitukseen rakennetun, porrastetun törmäysarkkitehtuurin.
Tässä kattavassa koodiohjatussa oppaassa aloitamme MonoGame-törmäyksen ehdottomista perusasioista ("Rectangle.Intersects" oikeilla "Bullet"- ja "Enemy" spriteillä) ja jatkamme edistyneitä ympyrätarkistuksia, sekakiinnitystä, tunnelointia estäviä sädelähetyksiä, tuotantotason Pixel-AllocationZero-Perfectation- ja -pisteiden yhteensovittamista. optimoitu mobiili GC:n selviytymiseen.
1. Säätiö: Yksinkertainen MonoGame "Sprite" -hierarkia
Ennen törmäysten havaitsemista tarvitsemme puhtaita pelikokonaisuuksia. MonoGamessa entiteetillä on pohjimmiltaan sijainti, pintakuvio ja rajaava suorakulmio.
Tässä on peleissämme käytetty peruskokonaisuusarkkitehtuuri:
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);
}
}
Luodaan nyt konkreettiset "Player"-, "Enemy"- ja "Bulet"-luokat:
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;
}
}
Kun nämä entiteetit ovat paikoillaan, tutkitaan kuinka havaita niiden väliset törmäykset yksinkertaisimmasta menetelmästä alkaen.
2. Taso 1: Yksinkertaisin törmäys – "Rectangle.Intersects" (AABB)
MonoGamen perustavanlaatuisin 2D-törmäystarkistus on Axis-Aligned Bounding Box (AABB) -testi. Termi "akselin suuntainen" tarkoittaa yksinkertaisesti, että suorakulmion reunat ovat täysin yhdensuuntaiset näytön \(X\)- ja \(Y\)-akseleiden kanssa (ei kiertoa).
MonoGame tarjoaa nopean, sisäänrakennetun menetelmän: "Rectangle.Intersects(Rectangle value)".
Miten "Rectangle.Intersects" toimii konepellin alla
MonoGame suorittaa pinnan alla neljä kokonaislukuvertailua:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
Jos kaikki neljä ehtoa täyttyvät, suorakulmiot menevät päällekkäin. Jos yksikin ehto epäonnistuu, tyhjä akseli erottaa ne, eikä törmäys ole mahdollinen.
Todellinen pelikoodi: "Bullet" vs. "Enemy" pelissä Game1.Update
Näin voit tarkistaa aktiivisten luotien luettelon ja aktiivisten vihollisten luettelon väliset törmäykset MonoGame-päivityksen pääsilmukassa:
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);
}
}
Suorituskykyvinkki: Huomaa, että iteroitamme taaksepäin (
for (int i = list.Count - 1; i >= 0; i--))! Jos käytät "foreach" ja yrität kutsua "_bullets.Remove(bullet)", C# antaa "InvalidOperationException: Collection was modified" -ilmoituksen. Taaksepäin iterointi mahdollistaa elementtien turvallisen poistamisen ilman muistin uudelleenindeksointiongelmia.
The Arcade Secret: "Fair Hitboxes" 'Inflaten' kautta
Retropeleissä, kuten Blocked: Pixel Panzer, sprite-tekstuurit sisältävät usein läpinäkyviä marginaaleja tai antennipiikkijä. Jos pelaajan tankki räjähtää, koska luoti kosketti tyhjää läpinäkyvää kulmaansa, pelaaja tuntee itsensä huijatuksi.
Jotta törmäys tuntuisi reagoivalta ja oikeudenmukaiselta, pelit käyttävät pienempää Hitboxia spriten sisällä käyttämällä "Rectangle.Inflate" -toimintoa:
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. Taso 2: Ympyrästä ympyrään törmäys (kiertohäiriönsieto)
Suorakulmiot toimivat erinomaisesti staattisissa lohkoissa ja ruudukkolaatoissa, mutta ne epäonnistuvat spritien pyöriessä. When a non-square spaceship rotates in Paint Trek, an axis-aligned bounding box must expand to enclose the spinning corners, resulting in frustrating "phantom collisions" in empty air.
Bounding Circles on ihanteellinen ratkaisu pyöreälle asteroidille, suuntautuville energiapalloille ja pyöriville avaruusaluksille.
Neliöjuuren ansa
Kaksi ympyrää törmäävät toisiinsa, kun niiden keskipisteiden välinen etäisyys on pienempi tai yhtä suuri kuin niiden säteiden summa:
\(\teksti{etäisyys}(C_A, C_B) \le r_A + r_B\)
Koodissa euklidisen etäisyyden laskeminen käyttää arvoa "Math.Sqrt" (tai "Vektori2.Etäisyys"). Kuitenkin neliöjuurien laskeminen silmukassa 200 ammuksen kanssa polttaa satoja tarpeettomia prosessorijaksoja!
Vertaamalla neliöetäisyyttä säteen neliön summaan eliminoimme neliöjuuren kokonaan:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
MonoGame-toteutus: "Ympyrä" vs. "Ympyrä".
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);
}
}
Integroi tämä nyt suoraan kokonaisuuteen:
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);
}
}
Nolla neliöjuurta, immuuni spriten pyörimiselle ja salamannopea.
4. Taso 3: Sekalaiset muodot – ympyrä vs. laatikko (MathHelper.Clamp)
Mitä tapahtuu, kun Paint Trekin pyöreä avaruusalus navigoi suorakaiteen muotoisten puolustusesteiden tiukan labyrintin läpi tai kun pyöreä luoti osuu neliömäiseen lohkoon Blocked: Pixel Panzerissa?
Tarvitsemme Ympyrä vs. suorakulmio -törmäyksen.
Kiinnitysalgoritmi
Tavoitteena on löytää suorakulmion piste, joka on lähinnä ympyrän keskustaa, ja sitten testata, onko etäisyys lähimmästä pisteestä keskipisteeseen pienempi kuin ympyrän säde.
MonoGamen "MathHelper.Clamp" tekee tästä triviaalin:
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);
}
}
Voit nyt testata pelaajakilpiä suorakaiteen muotoisia tiiliä vastaan nollavarauksilla ja suurella tarkkuudella!
5. Taso 4: Jatkuva törmäyksen tunnistus (CCD) ja pyyhkäisysäteily
Oletko koskaan ampunut pelissäsi hypernopeatar-ampujalaukkua tai kiskoase laseria vain nähdäksesi luodin kulkevan taianomaisesti suoraan ohuen vihollisaluksen läpi aiheuttamatta vahinkoa?
Tämä vika tunnetaan nimellä tunnelointi.
Koska erilliset pelit päivittyvät aikaaskelin (\(\Delta t = 16,6\text{ms}\) 60 FPS:llä), 1 800 pikseliä sekunnissa liikkuva kohde kulkee 30 pikseliä yhdessä kehyksessä. Jos vihollisen runko on vain 15 pikseliä paksu, luoti oli vihollisen edessä kehyksessä 1 ja kokonaan vihollisen takana kehyksessä 2.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
Ratkaisu: Pyyhkäisy segmentti vs. laatikko (laattamenetelmä)
Yhden pisteen testaamisen sijaan testaamme koko viivaosuuden, joka yhdistää luodin sijainnin kehyksessä 1 ("previousPosition") kehykseen 2 ("currentPosition").
Tässä on tuotantosädevalulaatan leikkausmenetelmä kumppaninimestämme 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;
}
}
Blocked: Pixel Panzer -pelissä pelaajan jatkuva Laser Beam -taito käyttää tätä tarkkaa sädelähetystä laskevien lohkojen rivien läpi leikkaamiseen ilman yhtäkään törmäystä.
6. Taso 5: Tuotantotason pikselin täydellinen törmäystunnistus
Nyt saavutamme 2D-tarkkuuden äärimmäisen tason: Pixel-Perfect Collision.
Retro-panssariampuja- tai avaruusaluksen koirataistelussa epäsäännölliset muodot (tankin tynnyrit, siivet, ohjaamon ohjaamot) ympäröivät läpinäkyviä pikseleitä sprite-tekstuurissa. Kun vihollisen ohjus osuu tähän läpinäkyvään tilaan, pelaajat huomaavat sen välittömästi.
Pikselitäydellinen törmäys tarkastaa päällekkäisten pintakuvioiden todelliset alfa-kanavat (läpinäkyvyys). Jos kaksi läpinäkymätöntä pikseliä menevät päällekkäin samassa maailmankoordinaatissa, on tapahtunut todellinen fyysinen osuma.
Kohtalokas virhe: "GetData" "Update()" -kohdassa
Monet opetusohjelmat neuvovat aloittelijoita tekemään tämän:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
"Texture2D.GetData()":n kutsuminen ajon aikana pakottaa suorittimen odottamaan, että GPU-liukuhihna tyhjenee, mikä vetää tekstuurimuistin takaisin väylän yli. Kun teet tämän kymmenille spriteille jokaisessa ruudussa, pelisi tankkaa 60 FPS:stä 5 FPS:ään ja laukaisee Androidin ANR:t välittömästi!
Taistelutestattu ratkaisu Blocked: Pixel Panzerilta
Blocked: Pixel Panzerissa Sprite.cs toteuttaa erittäin optimoidun IntersectsPixel-rutiinin, joka on rakennettu viiden tärkeän säännön ympärille:
- Staattisen väritaulukon välimuisti: "GetData" kutsutaan tasan kerran, kun pintakuviointi latautuu, ja se tallennetaan sanakirjaan<Texture2D, Color[]>".
- AABB Early Exit Guard: Jos
Bounds.Intersects(other.Bounds)on väärä, pelastamme välittömästi. 99 % tarkistuksista poistetaan ennen yhden pikselin tarkastamista. - Tekstuurikartasto ja 'SourceRectangle' -tuki: Käsittelee tekstuurikartasteihin pakattuja spritejä käyttämällä lähdesuorakulmion siirtymiä.
- Laskettu limitysikkuna: Teemme silmukan vain tarkan molempien spritien välisen leikkaussuorakulmion yli (
Math.Max(a.Top, b.Top)jne.). - Alfa-oikosulku: Jos Sprite A:n pikseli on läpinäkyvä (
A <= 20), Sprite B ohitetaan kokonaan. - Property Hoisting: Virtuaalisten ominaisuuksien (
Bounds) käyttäminen sisäkkäisissä silmukoissa luo tuhansia rakennekopioita. Tallennamme ne paikallisiin pinomuuttujiin ennen silmukkaan siirtymistä.
Tässä on täydellinen, tuotantovalmis koodi:
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;
}
}
Tämä rutiini suoritetaan alle 0,1 millisekunnissa nykyaikaisissa Android-laitteistoissa ja työpöytäjärjestelmissä.
7. Taso 6: Laajavaiheinen tilaverkko ja nollavaraus mobiili GC
Kuvittele, että pelissäsi on 100 lohkoa, 60 luotia ja 20 vihollista. Jos testaat jokaista entiteettiä kaikkia muita entiteettejä vastaan käyttämällä sisäkkäisiä for-silmukoita, suoritat:
\(\frac{180 \times 179}{2} = 16 110 \text{ shekkiä kehystä kohden!}\)
60 FPS:llä eli lähes 1 000 000 törmäystarkistusta sekunnissa.
Skaalataksesi peliäsi sinun on otettava käyttöön Laajavaiheinen tilaosio.
Uniform Spatial Hash Grid
Jaamme näytön yhtenäisten solujen 2D-ruudukoksi (Blocked: Pixel Panzerissa jokainen solu on 150 \(\ kertaa 150\) pikseliä). Entiteetti testaa vain törmäyksiä muihin samoissa ruudukon soluissa oleviin entiteeteihin.
Mobiilimuistiongelma: GC Thrashing
Jos "SpatialGrid" luo "uuden List
Androidin Mono-ajotilassa tämä käynnistää usein Gen-0-roskakeräyksiä, mikä aiheuttaa:
mono runtime: Alkuperäinen lukkokilpailu (mono_class_is_subclass_)
Peli pätkii ja Google Play merkitsee pelisi ANR-varoituksella!
Ratkaisu: uudelleen käytettävät raaputuspuskurit
Tässä on Zero-Allocation Spatial Hash Grid `CollisionManager.cs-sivustolta Estetty: 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);
}
}
}
}
}
}
}
}
Käyttämällä _scratchCells- ja _scratchCheckedEntities-tiedostoja uudelleen, koko törmäysvaihe suoritetaan 0 tavulla kehystä kohden.
8. Yhteenveto vertailutaulukko
| Tekniikka | Matemaattiset kustannukset | Rotaatiotuki | Turvallinen tunnelointi? | Paras käyttökotelo MonoGamessa |
|---|---|---|---|---|
AABB (Suorakamio.Leikkaukset) |
Erittäin nopea (~4 kokonaislukuvertailua) | Ei | Ei | Ristikkolohkot, luotilaajavaiheinen, käyttöliittymä |
Ympyrä (DistanceSquared) |
Erittäin nopea (3 multaa, 0 neliömetriä) | Kyllä (Invariantti) | Ei | Pyöreät laivat, tulipallot, energiapallot |
| Ympyrä vs. laatikko ("kiinnike") | Nopea (paikallinen MathHelper.Clamp) | Kyllä | Ei | Pyöreä pelaaja navigoi ahtaissa seinissä |
| Swept Ray (Slab CCD) | Keskitaso (parametrinen säteilylähetys) | Kyllä | Kyllä | Nopeat ampujan luodit, kiskoaseet, laserit |
Pixel-Perfect (IntersectsPixel) |
Valikoiva (rajoitettu alisuora) | Kyllä | Ei | Epäsäännölliset sprite-muodot, reilut hitboxit |
| Spatial Grid (Zero-GC) | Laajavaiheinen (\(O(N^2) \to O(N)\)) | N/A | N/A | Tiheät aallot, luotihelvetit, Android 60 FPS |
Tosimaailman tuotantoesityksiä: Arar Games
Nämä törmäysarkkitehtuurit eivät ole teoreettisia kokeita – ne ovat todellinen tekninen perusta, joka toimii kaupallisesti julkaistuille nimikkeillemme:
- Estetty: Pixel Panzer: Retro tankkien selviytymispelimme Google Playssa ja Microsoft Storessa. Siinä on täydellinen kaksitasoinen törmäysjärjestelmämme: nolla-allokoitu spatiaalinen hash-verkko, joka suodattaa putoavia lohkoja, ja pikselitäydellinen kontaktien tarkistus tankeille, hävittäjille, tornin kuorille ja ColorWheel-elementaaliammuksille.
- Paint Trek: Nopeatempoinen avaruusampujamme, jossa on pyörivä ympyrätörmäys, jatkuva sädelähetys ja suuren tilavuuden ohjuspuolustusjärjestelmät.
Johtopäätös ja seuraavat vaiheet
MonoGame antaa sinulle mahdollisuuden suunnitella törmäystunnistuksen, joka vastaa täydellisesti pelisi tarpeita. Aidattamalla kalliit Pixel-Perfect -tarkistukset nopeiden AABB-testien taakse, käyttämällä neliöetäisyyksiä ympyröille ja eliminoimalla roskien keräämisen Uudelleenkäytettävillä Scratch Buffers -puskurilla, voit tarjota konsolitasoisen 60/120 FPS:n suorituskyvyn sekä pöytäkoneilla että mobiilialustoilla.
Tutustu peleihimme sovelluskaupoissa nähdäksesi nämä törmäysjärjestelmät toiminnassa ja aloita näiden mallien toteuttaminen omissa MonoGame-projekteissasi jo tänään!
Kaupan linkit ja resurssit
- Estetty: Pixel Panzer Google Playssa: Lataa Androidille
- Estetty: Pixel Panzer Microsoft Storessa: Lataa Windowsille
- MonoGame Framework: monogame.net
SEO-avainsanat ja hashtagit
Avainsanat: MonoGame 2D -törmäysten tunnistus, C#-pelikehitys, Rectangle.Intersects MonoGame, pixel perfect collision C#, spatial hash grid MonoGame, pyyhkäisysäteilyn jatkuva törmäys, indie-pelin suorituskyvyn optimointi, nollaallokaatiopelisilmukka, Android Arlock B -optimointi, Panzer-peli, PanoGame Trek.
#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #PanintTre``xel #ArarGames #Peliohjelmointi
Hiç yorum yok:
Yorum Gönder