2Д откривање судир во моноигра: од основни правоаголници до системи со совршени пиксели со нулта распределба
Кога се градат 2D игри во визуелни мотори како Unity или Godot, откривањето судир често се чувствува како серија полиња за избор на инспекторите: удрите „BoxCollider2D“ или „CircleCollider2D“, прикачувате „Rigidbody“ и се надевате дека чекорот за внатрешна физика нема да пелтечи на мобилните уреди.
Во MonoGame и C#, сепак, вие имате целосна контрола. Нема скриена физика над глава, нема несакана ротациона инерција и нема мистериозни шила за собирање ѓубре (GC) што го крадат вашиот буџет за рамка.
Во Arar Games, кога ги изградивме Blocked: Pixel Panzer и Paint Trek, нашите јамки за аркадни игри беа потребни за обработка на стотици непријателски куршуми со голема брзина, решетки од тули кои експлодираа, ротирачки одбранбени тенкови, прелетување на борбени авиони и штитови од честички со брзина од 60 до 120 FPS и на уредите со Windows PC и 120 FPS. Физичкиот мотор за општа намена не доаѓаше во предвид - ни требаше наменска архитектура на судир со нивоа.
Во овој сеопфатен водич управуван од код, ќе започнеме од апсолутните основи на судирот MonoGame („Rectangle. Intersects“ со вистински „Bullet“ и „Enemy“ sprites) и ќе изградиме напредни проверки на кругот, мешано стегање, емитувања против тунелирање, продукциски степен Pixellision-A, collision-A Решетки** оптимизирани за опстанок на мобилен 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 е тестот за граничи порамнети со оската (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;
}
Ако се исполнети сите четири услови, правоаголниците се преклопуваат. Ако дури и еден услов не успее, празна оска ги дели и не е можен судир.
Вистински код за игра: „Bullet“ наспроти „Enemy“ во „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);
}
}
Совет за изведба: Забележете дека повторуваме наназад (
за (int i = листа. Број - 1; i >= 0; i--))! Ако користите „foreach“ и се обидете да повикате „_bullets.Remove(bullet)“, C# дава „InvalidOperationException: Колекцијата беше изменета“. Повторувањето наназад овозможува безбедно отстранување на елементите без проблеми со повторното индексирање на меморијата.
Тајната на аркадата: „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: Судир од круг до круг (ротациски имунитет)
Правоаголниците работат одлично за статични блокови и мрежни плочки, но тие не успеваат кога се ротираат sprites. Кога вселенски брод кој не е квадрат се ротира во Paint Trek, рамката за ограничување усогласена со оската мора да се прошири за да ги затвори аглите што се вртат, што резултира со фрустрирачки „фантомски судири“ во празен воздух.
За кружни астероиди, енергетски топчиња за враќање и вселенски летала што се вртат, Круговите за граница се идеалното решение.
Стапица со квадратни корени
Два круга се судираат кога растојанието помеѓу нивните центри е помало или еднакво на збирот на нивните радиуси:
\(\text{Растојание}(C_A, C_B) \le r_A + r_B\)
Во кодот, пресметувањето на Евклидовото растојание користи „Math.Sqrt“ (или „Vector2.Distance“). Сепак, пресметувањето на квадратните корени во јамка со 200 проектили согорува стотици непотребни циклуси на процесорот!
Со споредување на квадратното растојание со збирот на квадратниот радиус, целосно го елиминираме квадратниот корен:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
Имплементација на моноигра: „Круг“ наспроти „Круг“.
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), објектот што се движи со 1.800 пиксели во секунда патува 30 пиксели во една рамка. Ако трупот на непријателот е дебел само 15 пиксели, куршумот бил пред непријателот на Рамката 1 и целосно зад непријателот на Рамката 2.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
Решението: Поместен сегмент наспроти кутија (метод на плоча)
Наместо да тестираме една точка, го тестираме целиот сегмент на линијата што ја поврзува позицијата на куршумот на рамката 1 („претходна положба“) со рамката 2 („тековната положба“).
Еве го методот на производство на пресек на плоча од нашиот придружник 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, континуираната вештина на играчот со ласерски зрак го користи овој точен raycast за да сече низ редовите на блокови во опаѓање без да пропушти ниту еден судир.
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() за време на траењето го принудува процесорот да чека да се испушти цевководот на графичкиот процесор, повлекувајќи ја меморијата за текстура назад преку магистралата. Ако го направите ова за десетици sprites секоја рамка, ќе ја зголемите вашата игра од 60 FPS до 5 FPS и веднаш ќе активира ANR на Android!
Решението тестирано во битка од Блокирано: Pixel Panzer
Во Blocked: Pixel Panzer, нашиот Sprite.cs имплементира ултра оптимизирана рутина IntersectsPixel изградена околу 5 клучни правила:
- Кеш за статична низа на бои:
GetDataсе повикува точно еднаш кога се вчитува текстурата и се складира во „Речник<Texture2D, Color[]>“. - AABB Early Exit Guard: Ако
Bounds.Intersects(other.Bounds)е лажен, ние веднаш спасуваме. 99% од проверките се елиминираат пред да се провери еден пиксел. - Текстурен атлас и „SourceRectangle“ Поддршка: Се справува со sprites спакувани во атласи со текстура користејќи поместувања на изворниот правоаголник.
- Прозорец со пресметување на преклопување: Ние го превртуваме само пресечниот правоаголник помеѓу двата спрајти (`Math.Max(a.Top, b.Top)“ итн.).
- Кратко спој на алфа: Ако пикселот на Sprite A е транспарентен (
A <= 20), Sprite 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 непријатели. Ако го тестирате секој ентитет против секој друг ентитет користејќи вгнездени јамки „за“, ќе извршите:
\(\frac{180 \times 179}{2} = 16.110 \text{ проверки по рамка!}\)
При 60 FPS, тоа е скоро 1.000.000 проверки на судир секоја секунда.
За да ја зголемите вашата игра, мора да спроведете Широфазна просторна партиција.
Единствена просторна хаш мрежа
Екранот го делиме на 2Д решетка од униформни ќелии (во Блокирано: Pixel Panzer, секоја ќелија е 150 \(\ пати 150\) пиксели). Ентитетот тестира само судири со други ентитети што живеат во истите ќелии на мрежата.
Проблем со мобилната меморија: GC Thrashing
Ако вашата SpatialGrid создава „нова листа или „нова листа<Block>() секоја рамка, вие доделувате мегабајти ѓубре секоја минута на управуваниот куп.
За време на траењето Mono на Android, ова предизвикува чести собирања на отпад Gen-0, што предизвикува:
mono runtime: Native lock contest (mono_class_is_subclass_)
Играта пелтечи и Google Play ја означува вашата игра со предупредувања за ANR!
Решението: Повторно употребливи бафери за гребење
Еве ја Просторната хаш-мрежа со нулта распределба од „CollisionManager.cs“ во Блокирано: 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 споредби со цели броеви) | Не | Не | Блокови на мрежа, куршуми со широка фаза, UI |
Круг (DistanceSquared) |
Многу брзо (3 muls, 0 sqrt) | Да (Непроменливо) | Не | Тркалезни бродови, огнени топки, енергетски топки |
Круг против кутија (Стегач) |
Брзо (локален MathHelper.Clamp) | Да | Не | Кружен плеер кој се движи по тесни ѕидови |
| ** Swept Ray (Плочка CCD)** | Умерен (параметарски Raycast) | Да | Да | Брзи снајперски куршуми, пиштоли, ласери |
Пиксел-Совршен (Пиксели се вкрстуваат) |
Селективен (Ограничен под-право) | Да | Не | Неправилни контури на спрајт, фер хитбокс |
| Просторна мрежа (Нулта-GC) | Широка фаза (\(O(N^2) \до O(N)\)) | N/A | N/A | Густи бранови, пеколни куршуми, Android 60 FPS |
Изложби за продукција во реалниот свет: Арар игри
Овие архитектури на судир не се теоретски експерименти - тие се вистинската инженерска основа што ги напојува нашите комерцијално објавени наслови:
- Блокирано: 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.net
Клучни зборови и хаштагови за оптимизација
Клучни зборови: MonoGame 2D откривање судир, развој на C# игра, Rectangle.Intersects MonoGame, совршен судир на пиксели C#, просторна хаш-мрежа MonoGame, непрекинат судир со пребришени зраци, оптимизација на перформансите на инди-играта, оптимизација на игра со нулта распределба, јамка за игри со нулта распределба, игри со оптимизација на Андроид Monoint, Paxela Трек.
#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixelPixel #Програмирање на игри`
Hiç yorum yok:
Yorum Gönder