2D откриване на сблъсък в MonoGame: от основни правоъгълници до идеални за пиксели системи с нулево разпределение
Когато създавате 2D игри във визуални машини като Unity или Godot, откриването на сблъсък често изглежда като поредица от квадратчета за отметка на инспектор: пускате „BoxCollider2D“ или „CircleCollider2D“, прикачвате „Rigidbody“ и се надявате стъпката на вътрешната физика да не заеква на мобилни устройства.
В MonoGame и C# обаче вие имате пълен контрол. Няма скрита физика, няма нежелана инерция на въртене и мистериозни шипове за събиране на отпадъци (GC), които крадат бюджета ви за рамка.
В Arar Games, когато създадохме Blocked: Pixel Panzer и Paint Trek, циклите на нашите аркадни игри трябваше да обработват стотици високоскоростни вражески куршуми, експлодиращи тухлени решетки, въртящи се кули на танкове, прелитания на изтребители и щитове от частици при 60 до 120 FPS както на компютър с 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;
}
Ако и четирите условия са изпълнени, правоъгълниците се припокриват. Ако дори едно условие е неуспешно, празна ос ги разделя и не е възможен сблъсък.
Истински код за игра: 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);
}
}
Съвет за производителност: Забележете, че итерираме назад (
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).
Ето производствения метод за пресичане на плочи за raycasting от нашето придружаващо заглавие 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 точност: Pixel-Perfect Collision.
В ретро стрелба с танкове или ръкопашен бой с космически кораби, неправилни форми (цилиндри на танкове, крила, пилотски кабини) са заобиколени от прозрачни пиксели в текстурата на спрайта. Когато вражеска ракета удари това прозрачно пространство, играчите забелязват веднага.
Идеалният сблъсък на пиксели проверява действителните алфа (прозрачност) канали на припокриващите се текстури. Ако два непрозрачни пиксела се припокриват в една и съща световна координата, е настъпило истинско физическо попадение.
Фаталната грешка: 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 важни правила:
- Static Color Array Cache:
GetDataсе извиква точно веднъж, когато текстурата се зарежда, и се съхранява вDictionary<Texture2D, Color[]>. - AABB Early Exit Guard: Ако
Bounds.Intersects(other.Bounds)е невярно, спасяваме незабавно. 99% от проверките се елиминират преди проверка на един пиксел. - Текстурен атлас и поддръжка на
SourceRectangle: Обработва спрайтове, опаковани в атласи на текстури, като използва отместванията на правоъгълника на източника. - Прозорец с изчислено припокриване: Преминаваме само през точния пресечен правоъгълник между двата спрайта (
Math.Max(a.Top, b.Top)и т.н.). - Alpha Short-Circuiting: Ако пикселът на 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 проверки за сблъсък всяка секунда.
За да мащабирате играта си, трябва да внедрите широкофазово пространствено разделяне.
Единната пространствена хеш мрежа
Разделяме екрана на 2D мрежа от еднакви клетки (в Blocked: Pixel Panzer всяка клетка е \(150 \times 150\) пиксела). Един обект тества сблъсъци само срещу други обекти, които се намират в същите клетки на мрежата.
Проблемът с мобилната памет: GC Thrashing
Ако вашият SpatialGrid създава new List<Point>() или new List<Block>() всеки кадър, вие разпределяте мегабайти боклук всяка минута в управляваната купчина.
В Mono runtime на Android това задейства често Gen-0 Garbage Collections, причинявайки:
моно изпълнение: Конкурс за собствено заключване (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 сравнения на цели числа) | Не | Не | Мрежови блокове, широка фаза на куршума, потребителски интерфейс |
Кръг (DistanceSquared) |
Много бързо (3 muls, 0 sqrt) | Да (непроменлива) | Не | Кръгли кораби, огнени топки, енергийни кълба |
Кръг срещу кутия (Скоба) |
Бързо (Local MathHelper.Clamp) | Да | Не | Кръгъл играч, навигиращ през тесни стени |
| Swept Ray (Slab CCD) | Умерен (Параметричен Raycast) | Да | Да | Бързи снайперски куршуми, релсови оръдия, лазери |
Pixel-Perfect (IntersectsPixel) |
Селективен (ограничен под-рект) | Да | Не | Неправилни контури на спрайтове, справедливи хитбоксове |
| Пространствена мрежа (Zero-GC) | Широка фаза (\(O(N^2) \до O(N)\)) | N/A | N/A | Плътни вълни, ад от куршуми, 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