Wykrywanie kolizji 2D w grze MonoGame: od podstawowych prostokątów po systemy z zerową alokacją i doskonałą jakością pikseli
Podczas tworzenia gier 2D w silnikach wizualnych, takich jak Unity lub Godot, wykrywanie kolizji często przypomina serię pól wyboru inspektora: uderzasz w BoxCollider2D lub CircleCollider2D, podłączasz Rigidbody i masz nadzieję, że wewnętrzny krok fizyki nie zacina się na urządzeniach mobilnych.
Jednak w MonoGame i C# masz całkowitą kontrolę. Nie ma żadnych ukrytych kosztów związanych z fizyką, niepożądanej bezwładności obrotowej ani tajemniczych skoków zbierania śmieci (GC), które kradną budżet na ramę.
Kiedy w Arar Games budowaliśmy Blocked: Pixel Panzer i Paint Trek, nasze pętle gier zręcznościowych wymagały przetwarzania setek szybkich pocisków wroga, eksplodujących ceglanych siatek, obracających się wieżyczek czołgów, przelotów myśliwców i tarcz cząsteczkowych przy 60 do 120 FPS na komputerach PC i urządzeniach z systemem Android. Nie było mowy o silniku fizyki ogólnego przeznaczenia – potrzebowaliśmy specjalnie zaprojektowanej, wielopoziomowej architektury kolizyjnej.
W tym obszernym, opartym na kodzie przewodniku zaczniemy od absolutnych podstaw kolizji w grze MonoGame („Rectangle.Intersects” z prawdziwymi ikonkami „Bullet” i „Enemy”) i przejdziemy do zaawansowanych kontroli okręgów, mieszanego zaciskania, promieni zapobiegających tunelowaniu, kolizji idealnej dla pikseli i siatek przestrzennych o zerowej alokacji zoptymalizowanych pod kątem przetrwania mobilnego GC.
1. Podstawa: prosta hierarchia „Sprite” w grze pojedynczej
Przed wykryciem kolizji potrzebujemy czystych jednostek gry. W MonoGame jednostka zasadniczo posiada pozycję, teksturę i prostokąt ograniczający.
Oto podstawowa architektura jednostek używana w naszych grach:
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);
}
}
Stwórzmy teraz konkretne klasy „Gracz”, „Wróg” i „Pocisk”:
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;
}
}
Mając już te elementy, przyjrzyjmy się, jak wykryć kolizje między nimi, zaczynając od najprostszej metody.
2. Poziom 1: Najprostsza kolizja – „Rectangle.Intersects” (AABB)
Najbardziej podstawową metodą kontroli kolizji 2D w grze MonoGame jest test wyrównanej ramki ograniczającej (AABB). Termin „wyrównany do osi” oznacza po prostu, że krawędzie prostokąta są całkowicie równoległe do osi \(X\) i \(Y\) ekranu (bez obrotu).
MonoGame udostępnia szybką, wbudowaną metodę: Rectangle.Intersects(Wartość prostokąta).
Jak działa „Rectangle.Intersects” pod maską
Pod powierzchnią MonoGame wykonuje cztery porównania liczb całkowitych:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
Jeśli wszystkie cztery warunki są spełnione, prostokąty nakładają się na siebie. Jeśli choć jeden warunek nie zostanie spełniony, oddziela je pusta oś i nie jest możliwa żadna kolizja.
Prawdziwy kod rozgrywki: „Pocisk” kontra „Wróg” w „Grze 1. Aktualizacja”
Oto jak sprawdzić kolizje pomiędzy listą aktywnych kul i listą aktywnych wrogów w głównej pętli „Aktualizacji” gry 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);
}
}
Wskazówka dotycząca wydajności: Zauważ, że wykonujemy iterację wstecz („for (int i = list.Count - 1; i >= 0; i--)
)! Jeśli użyjeszforeachi spróbujesz wywołać_bullets.Remove(bullet), C# zgłosiInvalidOperationException: Kolekcja została zmodyfikowana`. Iteracja wstecz umożliwia bezpieczne usuwanie elementów bez problemów z ponownym indeksowaniem pamięci.
Sekret salonu gier: „Dobre trafienia” poprzez „Nadmuchanie”.
W grach retro, takich jak Blocked: Pixel Panzer, tekstury ikonek często zawierają przezroczyste marginesy lub kolce antenowe. Jeśli czołg gracza eksploduje, ponieważ kula dotknęła pustego przezroczystego rogu jego tekstury, gracz poczuje się oszukany.
Aby kolizja wydawała się responsywna i sprawiedliwa, gry używają mniejszego Hitboxu wewnątrz ikonki, używając opcji 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. Poziom 2: Zderzenie koło-okrąg (odporność na obrót)
Prostokąty świetnie sprawdzają się w przypadku bloków statycznych i płytek siatki, ale zawodzą, gdy duszki się obracają. Kiedy statek kosmiczny o kształcie innym niż kwadratowy obraca się w Paint Trek, obwiednia wyrównana do osi musi się rozszerzyć, aby objąć obracające się rogi, co skutkuje frustrującymi „widmowymi zderzeniami” w pustym powietrzu.
W przypadku okrągłych asteroid, samonaprowadzających się kul energii i wirujących statków kosmicznych idealnym rozwiązaniem są Okręgi ograniczające.
Pułapka na pierwiastek kwadratowy
Dwa okręgi zderzają się, gdy odległość między ich środkami jest mniejsza lub równa sumie ich promieni:
\(\text{Odległość}(C_A, C_B) \le r_A + r_B\)
W kodzie do obliczania odległości euklidesowej używa się parametru „Math.Sqrt” (lub „Vector2.Distance”). Jednak obliczanie pierwiastków kwadratowych w pętli z 200 pociskami powoduje spalanie setek niepotrzebnych cykli procesora!
Porównując kwadrat odległości z kwadratową sumą promieni, całkowicie eliminujemy pierwiastek kwadratowy:
\(\text{OdległośćKwadrat} \le (r_A + r_B)^2\)
Implementacja gry pojedynczej: „Koło” kontra „Koło”.
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);
}
}
Teraz zintegruj to bezpośrednio z jednostką:
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 pierwiastków kwadratowych, odporność na rotację duszków i błyskawiczna szybkość.
4. Poziom 3: Kształty mieszane – okrąg kontra pudełko („MathHelper.Clamp”)
Co się stanie, gdy okrągły statek kosmiczny w Paint Trek przebije się przez ciasny labirynt prostokątnych barier obronnych lub gdy okrągła kula trafi w kwadratowy blok w Blocked: Pixel Panzer?
Potrzebujemy kolizji Koło kontra prostokąt.
Algorytm zaciskania
Strategia polega na znalezieniu punktu na prostokącie, który jest najbliżej środka okręgu, a następnie sprawdzeniu, czy odległość od tego najbliższego punktu do środka jest mniejsza niż promień okręgu.
MathHelper.Clamp firmy MonoGame sprawia, że jest to trywialne:
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);
}
}
Możesz teraz testować tarcze graczy w porównaniu z prostokątnymi cegłami z zerowym przydziałem i dużą dokładnością!
5. Poziom 4: Ciągłe wykrywanie kolizji (CCD) i promienie odchylone
Czy kiedykolwiek w grze wystrzeliłeś superszybki pocisk snajperski lub laser z karabinu szynowego, tylko po to, by zobaczyć, jak kula magicznie przechodzi prosto przez cienki wrogi statek, nie zadając uszkodzeń?
Ten błąd jest znany jako tunelowanie.
Ponieważ oddzielne gry aktualizują się w odstępach czasowych (\(\Delta t = 16,6\text{ms}\) przy 60 FPS), obiekt poruszający się z szybkością 1800 pikseli na sekundę przemieszcza się 30 pikseli w pojedynczej klatce. Jeśli kadłub wroga ma tylko 15 pikseli grubości, kula znajdowała się przed wrogiem w klatce 1 i całkowicie za wrogiem w klatce 2.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
Rozwiązanie: Segment ukosowany a pudełko (metoda płyty)
Zamiast testować pojedynczy punkt, testujemy cały segment linii łączący pozycję pocisku w klatce 1 („poprzednia pozycja”) z klatką 2 („bieżąca pozycja”).
Oto produkcyjna metoda przecięcia płyt metodą raycastingu z naszego towarzyszącego tytułu 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;
}
}
W Blocked: Pixel Panzer, ciągła umiejętność Laser Beam gracza wykorzystuje dokładnie ten raycast, aby przecinać rzędy opadających bloków, nie pomijając ani jednej kolizji.
6. Poziom 5: Doskonałe wykrywanie kolizji na poziomie produkcyjnym
Teraz osiągamy najwyższy poziom dokładności 2D: Kolizja idealna w pikselach.
W retro strzelance czołgowej lub walce powietrznej na statku kosmicznym nieregularne kształty (beczki czołgów, skrzydła, kokpity kokpitu) są otoczone przezroczystymi pikselami w teksturze sprite'a. Kiedy wrogi pocisk uderza w tę przezroczystą przestrzeń, gracze natychmiast to zauważają.
Doskonała kolizja pikseli sprawdza rzeczywiste kanały alfa (przezroczystość) nakładających się tekstur. Jeśli dwa nieprzezroczyste piksele nakładają się na tę samą współrzędną świata, następuje prawdziwe fizyczne trafienie.
Fatalny błąd: „GetData” wewnątrz „Update()”.
Wiele samouczków instruuje początkujących, jak to zrobić:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
Wywołanie Texture2D.GetData() w czasie wykonywania zmusza procesor do oczekiwania na opróżnienie potoku GPU, co powoduje przeciągnięcie pamięci tekstur z powrotem przez magistralę. Wykonanie tej czynności dla dziesiątek duszków w każdej klatce spowoduje spowolnienie gry z 60 klatek na sekundę do 5 klatek na sekundę i natychmiastowe wywołanie błędów ANR systemu Android!
Sprawdzone w boju rozwiązanie od Zablokowano: Pixel Panzer
W Blocked: Pixel Panzer nasz Sprite.cs implementuje ultra zoptymalizowaną procedurę IntersectsPixel zbudowaną wokół 5 kluczowych zasad:
- Statyczna pamięć podręczna tablicy kolorów:
GetDatajest wywoływana dokładnie raz podczas ładowania tekstury i zapisywana wDictionary<Texture2D, Color[]>. - AABB Strażnik wczesnego wyjścia: Jeśli
Bounds.Intersects(other.Bounds)jest fałszywe, natychmiast się ratujemy. 99% kontroli jest eliminowanych przed sprawdzeniem pojedynczego piksela. - Obsługa Atlasu Tekstur i
SourceRectangle: Obsługuje ikonki spakowane w atlasy tekstur przy użyciu przesunięć prostokątów źródłowych. - Obliczone okno nakładania się: Zapętlamy tylko dokładny prostokąt przecięcia obu duszków („Math.Max(a.Top, b.Top)` itp.).
- Zwarcie alfa: Jeśli piksel Sprite'a A jest przezroczysty (
A <= 20), Sprite B zostanie całkowicie pominięty. - Przenoszenie właściwości: Dostęp do właściwości wirtualnych („Granice”) w zagnieżdżonych pętlach tworzy tysiące kopii struktur. Przed wejściem do pętli przechowujemy je w lokalnych zmiennych stosu.
Oto kompletny, gotowy do produkcji kod:
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;
}
}
Ta procedura jest wykonywana w czasie krótszym niż 0,1 milisekundy na nowoczesnym sprzęcie i komputerach z systemem Android.
7. Poziom 6: Szerokofazowa sieć przestrzenna i mobilny GC z zerową alokacją
Wyobraź sobie, że Twoja gra składa się ze 100 bloków, 60 kul i 20 wrogów. Jeśli testujesz każdy element względem każdego innego elementu za pomocą zagnieżdżonych pętli „for”, wykonujesz:
\(\frac{180 \times 179}{2} = 16 110 \text{ kontroli na klatkę!}\)
Przy 60 FPS oznacza to prawie 1 000 000 kontroli kolizji na sekundę.
Aby skalować grę, musisz wdrożyć szerokofazowy podział przestrzenny.
Jednolita przestrzenna siatka skrótów
Dzielimy ekran na siatkę 2D jednolitych komórek (w Blocked: Pixel Panzer każda komórka to 150 \(\ razy 150\) pikseli). Obiekt testuje kolizje jedynie z innymi obiektami znajdującymi się w tych samych komórkach siatki.
Problem z pamięcią mobilną: szarpanie GC
Jeśli twoja SpatialGrid tworzy nową List<Point>() lub nową List<Block>() w każdej ramce, co minutę alokujesz megabajty śmieci na zarządzanej stercie.
W środowisku wykonawczym Mono Androida powoduje to częste zbieranie śmieci Gen-0, powodując:
mono środowisko wykonawcze: rywalizacja o blokadę natywną (mono_class_is_subclass_)
Gra się zacina, a Google Play oznacza ją ostrzeżeniami ANR!
Rozwiązanie: bufory zarysowania wielokrotnego użytku
Oto przestrzenna siatka mieszająca o zerowej alokacji z CollisionManager.cs w 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);
}
}
}
}
}
}
}
}
Ponowne użycie _scratchCells i _scratchCheckedEntities powoduje, że cały krok kolizji przebiega z 0 bajtami alokacji sterty na ramkę.
8. Podsumowanie tabeli porównawczej
| Technika | Koszt matematyczny | Wsparcie rotacyjne | Tunelowanie bezpieczne? | Najlepszy przypadek użycia w grze MonoGame |
|---|---|---|---|---|
| AABB („Prostokąt.Przecięcia”) | Ultraszybki (~4 porównania liczb całkowitych) | Nie | Nie | Bloki siatki, kula szerokofazowa, interfejs użytkownika |
| Koło („Odległość do kwadratu”) | Bardzo szybko (3 muls, 0 sqrt) | Tak (niezmienny) | Nie | Okrągłe statki, kule ognia, kule energii |
| Koło kontra pudełko („zacisk”) | Szybki (lokalny MathHelper.Clamp) | Tak | Nie | Okrągły gracz poruszający się po ciasnych ścianach |
| Promień skośny (CCD płyty) | Umiarkowany (Ramieniowanie parametryczne) | Tak | Tak | Szybkie kule snajperskie, działa szynowe, lasery |
| Doskonały piksel („Przecina piksel”) | Selektywny (podrekt ograniczony) | Tak | Nie | Nieregularne kontury duszków, uczciwe hitboxy |
| Siatka przestrzenna (Zero-GC) | Szerokofazowy (\(O(N^2) \do O(N)\)) | Nie dotyczy | Nie dotyczy | Gęste fale, piekło kul, Android 60 FPS |
Pokazy produkcji z prawdziwego świata: gry Arar
Te architektury kolizyjne nie są eksperymentami teoretycznymi — są prawdziwym fundamentem inżynieryjnym, na którym opierają się nasze komercyjnie wydane tytuły:
- Zablokowano: Pixel Panzer: Nasza gra zręcznościowa w stylu retro o przetrwaniu w czołgach w Google Play i Microsoft Store. Zawiera nasz kompletny dwupoziomowy system kolizyjny: przestrzenną siatkę mieszającą o zerowej alokacji, filtrującą spadające bloki oraz doskonałe w pikselach sprawdzanie kontaktu dla czołgów, myśliwców, pocisków wieży i amunicji elementarnej ColorWheel.
- Paint Trek: Nasza szybka strzelanka kosmiczna obejmująca zderzenia kół obrotowych, ciągłe rzucanie promieni i systemy obrony przeciwrakietowej o szerokim zasięgu.
Wnioski i kolejne kroki
MonoGame daje Ci możliwość zaprojektowania wykrywania kolizji, które idealnie pasuje do potrzeb Twojej gry. Łącząc drogie testy Pixel-Perfect z szybkimi testami AABB, wykorzystując Odległości do kwadratu dla okręgów i eliminując zbieranie śmieci za pomocą Buforów zarysowania wielokrotnego użytku, możesz zapewnić płynną na konsoli wydajność 60/120 FPS zarówno na komputerach stacjonarnych, jak i na platformach mobilnych.
Sprawdź nasze gry w sklepach z aplikacjami, aby zobaczyć te systemy kolizyjne w akcji i już dziś zacznij wdrażać te wzorce we własnych projektach MonoGame!
Linki i zasoby do sklepu
- Zablokowano: Pixel Panzer w Google Play: Pobierz na Androida
- Zablokowano: Pixel Panzer w sklepie Microsoft Store: Pobierz dla systemu Windows
- Framework MonoGame: monogame.net
Słowa kluczowe i hashtagi SEO
Słowa kluczowe: wykrywanie kolizji 2D MonoGame, tworzenie gier w C#, Rectangle.Intersects MonoGame, pikselowa kolizja C#, przestrzenna siatka mieszająca MonoGame, ciągłe kolizje Swept Ray, optymalizacja wydajności gier niezależnych, pętla gry z zerowym przydziałem, optymalizacja Android MonoGame, gry Arar, Blocked Pixel Panzer, Paint Trek.
#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixelPanzer #PaintTrek #ArarGames #Programowanie gier
Hiç yorum yok:
Yorum Gönder