Выяўленне 2D-сутыкненняў у MonoGame: ад простых прастакутнікаў да сістэм з ідэальным пікселем з нулявым размеркаваннем
Пры стварэнні 2D-гульняў у такіх візуальных рухавічках, як Unity або Godot, выяўленне сутыкненняў часта выглядае як серыя сцяжкоў інспектара: вы націскаеце на BoxCollider2D або CircleCollider2D, далучаеце Rigidbody і спадзяецеся, што ўнутраны крок фізікі не заікаецца на мабільных прыладах.
Аднак у MonoGame і C# вы цалкам кантралюеце. Няма ніякіх схаваных накладных выдаткаў на фізіку, непажаданай інэрцыі кручэння і таямнічых скокаў збору смецця (GC), якія крадуць ваш бюджэт кадраў.
У Arar Games, калі мы стваралі Blocked: Pixel Panzer і Paint Trek, нашы цыклы аркаднай гульні павінны былі апрацоўваць сотні высакахуткасных варожых куль, выбухаючыя цагляныя сеткі, якія верцяцца вежы танкаў, пралёты знішчальнікаў і шчыты ад часціц з хуткасцю ад 60 да 120 кадраў у секунду як на ПК з Windows, так і на прыладах Android. Пра фізічны механізм агульнага прызначэння не магло быць і гаворкі — нам патрэбна была спецыяльна створаная шматузроўневая архітэктура сутыкненняў.
У гэтым поўным кіраўніцтве, якое кіруецца кодам, мы пачнем з абсалютных асноў сутыкнення MonoGame ("Rectangle.Intersects" з рэальнымі спрайтамі "Bullet" і "Enemy") і пяройдзем да прасунутых праверак круга, змешанага заціску, анты-тунэлявання прамянёў, Pixel-Perfect сутыкнення і Zero-Allocation Spatial Сеткі аптымізаваныя для выжывання мабільнага GC.
1. Аснова: простая іерархія монагульні Sprite
Перш чым выяўляць сутыкненні, нам патрэбны чыстыя гульнявыя сутнасці. У MonoGame сутнасць прынцыпова мае пазіцыю, тэкстуру і абмежавальны прамавугольнік.
Вось базавая архітэктура сутнасці, якая выкарыстоўваецца ў нашых гульнях:
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);
}
}
Зараз давайце створым канкрэтныя класы Player, Enemy і 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;
}
}
Маючы гэтыя аб'екты на месцы, давайце вывучым, як выяўляць сутыкненні паміж імі, пачынаючы з самага простага метаду.
2. Узровень 1: Самае простае сутыкненне - "Прамавугольнік. Скрыжаванні" (AABB)
Самая фундаментальная праверка 2D-сутыкненняў у MonoGame - гэта тэст Axis-Aligned Bounding Box (AABB). Тэрмін "выраўноўванне па восі" проста азначае, што краю прамавугольніка цалкам паралельныя восям \(X\) і \(Y\) экрана (без павароту).
MonoGame забяспечвае хуткі, убудаваны метад: Rectangle.Intersects(Rectangle value).
Як Rectangle.Intersects працуе пад капотам
Пад паверхняй MonoGame выконвае чатыры цэлалікавых параўнання:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
Калі ўсе чатыры ўмовы выкананы, прамавугольнікі перакрываюцца. Калі хаця б адна ўмова не выконваецца, іх раздзяляе пустая вось, і сутыкненне немагчыма.
Код рэальнага геймплэя: Куля супраць Ворага ў Game1.Update
Вось як вы правяраеце сутыкненні паміж спісам актыўных куль і спісам актыўных ворагаў у вашым галоўным цыкле абнаўлення 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);
}
}
Парада па прадукцыйнасці: Звярніце ўвагу, што мы выконваем ітэрацыі ў зваротным кірунку (
for (int i = list.Count - 1; i >= 0; i--))! Калі вы выкарыстоўваецеforeachі спрабуеце выклікаць_bullets.Remove(bullet), C# выдаеInvalidOperationException: Collection was modified. Ітэрацыя назад дазваляе бяспечна выдаляць элементы без праблем з паўторнай індэксацыяй памяці.
The Arcade Secret: "Fair Hitboxes" праз Inflate
У такіх рэтра-гульнях, як Blocked: Pixel Panzer, тэкстуры спрайтаў часта ўключаюць празрыстыя палі або шыпы антэн. Калі танк гульца выбухне з-за таго, што куля закранула пусты празрысты кут яго тэкстуры, гулец адчуе сябе падманутым.
Каб зрабіць сутыкненне адчувальным і справядлівым, у гульнях выкарыстоўваецца меншы Hitbox унутры спрайта з дапамогай 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. Узровень 2: Сутыкненне ад круга да круга (Імунітэт да кручэння)
Прастакутнікі выдатна працуюць для статычных блокаў і плітак сеткі, але яны не працуюць, калі спрайты круцяцца. Калі неквадратны касмічны карабель круціцца ў Paint Trek, абмежавальная рамка, выраўнаваная па восі, павінна пашырацца, каб ахапіць верцяцца куты, што прыводзіць да расчаравальных «фантомных сутыкненняў» у пустым паветры.
Для круглых астэроідаў, энергетычных шароў з саманавядзеннем і касмічных караблёў, якія верцяцца, Абмежавальныя кругі з'яўляюцца ідэальным рашэннем.
Пастка квадратнага кораня
Дзве акружнасці сутыкаюцца, калі адлегласць паміж іх цэнтрамі меншая або роўная суме іх радыусаў:
\(\тэкст{Адлегласць}(C_A, C_B) \le r_A + r_B\)
У кодзе для вылічэння эўклідавай адлегласці выкарыстоўваецца Math.Sqrt (або Vector2.Distance). Аднак вылічэнне квадратных каранёў у цыкле з 200 снарадамі спальвае сотні непатрэбных цыклаў працэсара!
Параўноўваючы квадрат адлегласці з квадратам сумы радыуса, мы цалкам выключаем квадратны корань:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
Рэалізацыя MonoGame: Circle супраць Circle
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);
}
}
Цяпер інтэгруйце гэта непасрэдна ў сутнасць:
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);
}
}
Нуль квадратных каранёў, неўспрымальны да кручэння спрайтаў і вокамгненны.
4. Узровень 3: Змешаныя фігуры – круг супраць скрынкі (MathHelper.Clamp)
Што адбываецца, калі круглы касмічны карабель у Paint Trek праходзіць праз вузкі лабірынт прастакутных абарончых бар'ераў або калі круглая куля трапляе ў квадратны блок у Blocked: Pixel Panzer?
Нам патрэбна сутыкненне Круг супраць Прастакутніка.
Алгарытм заціску
Стратэгія складаецца ў тым, каб знайсці кропку на прамавугольніку, якая знаходзіцца бліжэй за ўсё да цэнтра акружнасці, а затым праверыць, ці меншая адлегласць ад гэтай бліжэйшай кропкі да цэнтра менш за радыус акружнасці.
MathHelper.Clamp MonoGame робіць гэта трывіяльным:
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);
}
}
Цяпер вы можаце праверыць шчыты гульцоў супраць прастакутных цаглін з нулявым размеркаваннем і высокай дакладнасцю!
5. Узровень 4: бесперапыннае выяўленне сутыкненняў (CCD) і качаюцца прамяні
Ці стралялі вы калі-небудзь са звышхуткаснага снайперскага снарада або з лазернай рэйкі ў сваёй гульні толькі для таго, каб назіраць, як куля магічным чынам праходзіць праз тонкі варожы карабель, не наносячы пашкоджанняў?
Гэтая памылка вядомая як тунэляванне.
Паколькі дыскрэтныя гульні абнаўляюцца з крокамі ў часе (\(\Delta t = 16,6\text{ms}\) пры 60 FPS), аб'ект, які рухаецца з хуткасцю 1800 пікселяў у секунду, рухаецца 30 пікселяў у адным кадры. Калі таўшчыня корпуса праціўніка складае ўсяго 15 пікселяў, на кадры 1 куля знаходзілася наперадзе праціўніка, а на кадры 2 - цалкам ззаду.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
Рашэнне: разгорнуты сегмент супраць каробкі (метад пліты)
Замест таго, каб правяраць адну кропку, мы правяраем увесь адрэзак лініі, які злучае пазіцыю маркера ў кадры 1 (previousPosition) і кадры 2 (currentPosition).
Вось метад перакрыжавання пліт прамянёвай кастынгу з нашай спадарожнай назвы 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 бесперапынны Навык лазернага прамяня гульца выкарыстоўвае менавіта гэта прамянёвае перадача, каб разразаць шэрагі блокаў, якія ідуць уніз, не прапускаючы ніводнага сутыкнення.
6. Узровень 5: Ідэальнае піксельнае выяўленне сутыкненняў вытворчага класа
Цяпер мы дасягнулі найвышэйшага ўзроўню дакладнасці 2D: Ідэальнае сутыкненне пікселяў.
У рэтра танкавым шутэры або паветраным баі касмічных караблёў няправільныя формы (ствалы танкаў, крылы, кабіны) акружаны празрыстымі пікселямі ў тэкстуры спрайтаў. Калі варожая ракета трапляе ў гэтую празрыстую прастору, гульцы адразу гэта заўважаюць.
Ідэальнае сутыкненне пікселяў правярае фактычныя альфа-каналы (празрыстасць) перакрываючыхся тэкстур. Калі два непразрыстыя пікселі перакрываюцца ў адной сусветнай каардынаце, адбылося сапраўднае фізічнае трапленне.
Фатальная памылка: GetData ўнутры Update()
Многія падручнікі інструктуюць пачаткоўцаў рабіць гэта:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
Выклік Texture2D.GetData() падчас выканання прымушае працэсар чакаць ачысткі канвеера графічнага працэсара, вяртаючы тэкстурную памяць па шыне. Робячы гэта для дзясяткаў спрайтаў у кожным кадры, ваша гульня будзе зніжацца з 60 FPS да 5 FPS і неадкладна запускаць ANR Android!
Выпрабаванае ў баях рашэнне ад Заблакіравана: Pixel Panzer
У Blocked: Pixel Panzer наш Sprite.cs рэалізуе звышаптымізаваную працэдуру IntersectsPixel, пабудаваную вакол 5 найважнейшых правіл:
- Кэш масіва статычных колераў:
GetDataвыклікаецца роўна адзін раз пры загрузцы тэкстуры і захоўваецца ўDictionary<Texture2D, Color[]>. - AABB Early Exit Guard: Калі
Bounds.Intersects(other.Bounds)несапраўднае, мы неадкладна выручаемся. 99% праверак адмяняюцца перад праверкай аднаго пікселя. - Падтрымка тэкстурнага атласа і
SourceRectangle: Апрацоўвае спрайты, спакаваныя ў тэкстурныя атласы з выкарыстаннем зрушэнняў зыходнага прамавугольніка. - Разлічанае акно перакрыцця: мы перабіраем толькі дакладны прамавугольнік перасячэння паміж абодвума спрайтамі (
Math.Max(a.Top, b.Top)і г.д.). - Альфа-кароткае замыканне: Калі піксель спрайта A празрысты (
A <= 20), спрайт B цалкам прапускаецца. - Пад'ём уласцівасцей: Доступ да віртуальных уласцівасцей (
Межы) ва ўкладзеных цыклах стварае тысячы копій структур. Мы захоўваем іх у лакальных зменных стэка перад уваходам у цыкл.
Вось поўны, гатовы да вытворчасці код:
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;
}
}
Гэтая працэдура выконваецца менш чым за 0,1 мілісекунды на сучасным абсталяванні Android і настольных сістэмах.
7. Узровень 6: шырокафазавая прасторавая сетка і мабільны GC з нулявым размеркаваннем
Уявіце, што ў вашай гульні 100 блокаў, 60 куль і 20 ворагаў. Калі вы правяраеце кожны аб'ект супраць любога іншага аб'екта з дапамогай укладзеных цыклаў for, вы выконваеце:
\(\frac{180 \times 179}{2} = 16 110 \text{ чэкаў на кадр!}\)
Пры 60 кадрах у секунду гэта складае амаль 1 000 000 праверак на сутыкненне кожную секунду.
Каб маштабаваць сваю гульню, вы павінны рэалізаваць Шырокафазавае прасторавае раздзяленне.
Аднастайная прасторавая хэш-сетка
Мы дзелім экран на 2D-сетку аднастайных вочак (у Blocked: Pixel Panzer кожная ячэйка складае \(150 \times 150\) пікселяў). Аб'ект правярае сутыкненні толькі з іншымі аб'ектамі, якія знаходзяцца ў тых жа ячэйках сеткі.
Праблема мабільнай памяці: GC Thrashing
Калі ваша SpatialGrid стварае новы List<Point>() або new List<Block>() кожны кадр, вы размяркоўваеце мегабайты смецця кожную хвіліну ў кіраванай кучы.
У асяроддзі выканання Android Mono гэта выклікае частыя зборкі смецця Gen-0, выклікаючы:
mono runtime: Канфлікт уласнай блакіроўкі (mono_class_is_subclass_)
Гульня заікаецца, а Google Play пазначае вашу гульню папярэджаннямі пра ANR!
Рашэнне: шматразовыя скрэтч-буферы
Вось прасторавая хэш-сетка з нулявым размеркаваннем з CollisionManager.cs у 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);
}
}
}
}
}
}
}
}
Пры паўторным выкарыстанні _scratchCells і _scratchCheckedEntities увесь этап сутыкнення выконваецца з 0 байтамі размеркавання кучы на кадр.
8. Зводная параўнальная табліца
| Тэхніка | Матэматычны кошт | Паваротная падтрымка | Тунэляванне бяспечна? | Лепшы варыянт выкарыстання ў MonoGame |
|---|---|---|---|---|
AABB (Прамавугольнік. Перасякае) |
Звышхуткі (~4 цэлых параўнання) | Няма | Няма | Блокі сеткі, шырокафазны маркер, карыстацкі інтэрфейс |
Круг (DistanceSquared) |
Вельмі хуткі (3 мульты, 0 sqrt) | Так (нязменная) | Няма | Круглыя караблі, агністыя шары, энергетычныя шары |
Круг супраць скрынкі (Заціск) |
Хуткі (лакальны MathHelper.Clamp) | Так | Няма | Кругавы гулец, які рухаецца па цесных сценах |
| Swept Ray (Slab CCD) | Умераны (параметрычны Raycast) | Так | Так | Хуткія снайперскія кулі, рэйсавыя гарматы, лазеры |
Pixel-Perfect (IntersectsPixel) |
Selective (Restricted Sub-Rect) | Так | Няма | Няправільныя контуры спрайтаў, дакладныя хітбоксы |
| Прасторавая сетка (нуль-GC) | Шырокая фаза (\(O(N^2) \да O(N)\)) | Н/Д | Н/Д | Шчыльныя хвалі, кулявыя пекла, Android 60 FPS |
Вытворчыя вітрыны ў рэальным свеце: Arar Games
Гэтыя архітэктуры сутыкненняў не з'яўляюцца тэарэтычнымі эксперыментамі - яны з'яўляюцца сапраўднай інжынернай асновай, на якой працуюць нашы камерцыйна выпушчаныя назвы:
- Заблакіравана: Pixel Panzer: Наша рэтра аркадная гульня на выжыванне ў танках у Google Play і Microsoft Store. У яго ўваходзіць наша поўная двухузроўневая сістэма сутыкненняў: прасторавая хэш-сетка з нулявым размеркаваннем, якая фільтруе падаючыя блокі, і праверка дакладнага кантакту для танкаў, знішчальнікаў, турэльных снарадаў і элементарных боепрыпасаў ColorWheel.
- Paint Trek: Наш імклівы касмічны шутэр з сутыкненнем па крузе, бесперапыннай трансляцыяй прамянёў і сістэмамі супрацьракетнай абароны з паваротным аб'ёмам.
Выснова і наступныя крокі
MonoGame дае вам магчымасць распрацаваць сістэму выяўлення сутыкненняў, якая цалкам адпавядае патрэбам вашай гульні. Правяраючы дарагія праверкі Pixel-Perfect за хуткімі тэстамі AABB, выкарыстоўваючы Квадратныя адлегласці для кругоў і выключаючы збор смецця з дапамогай Шматразовых буфераў драпін, вы можаце забяспечыць плыўную кансольную прадукцыйнасць 60/120 FPS як на настольных, так і на мабільных платформах.
Праверце нашы гульні ў крамах прыкладанняў, каб убачыць гэтыя сістэмы сутыкненняў у дзеянні, і пачніце ўкараняць гэтыя шаблоны ў сваіх уласных праектах MonoGame сёння!
Спасылкі і рэсурсы крамы
- Заблакіравана: Pixel Panzer у Google Play: Спампаваць для Android
- Заблакіравана: Pixel Panzer у Microsoft Store: Спампаваць для Windows
- MonoGame Framework: monogame.net
Ключавыя словы і хэштэгі SEO
Ключавыя словы: MonoGame 2D выяўленне сутыкненняў, распрацоўка гульняў на C#, Rectangle.Intersects MonoGame, ідэальнае сутыкненне пікселяў C#, прасторавая хэш-сетка MonoGame, бесперапыннае сутыкненне з разгорнутымі прамянямі, аптымізацыя прадукцыйнасці індзі-гульні, гульнявы цыкл з нулявым размеркаваннем, аптымізацыя Android MonoGame, 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