זיהוי התנגשות דו-ממדי ב-MonoGame: ממלבנים בסיסיים למערכות פיקסלים מושלמות בהקצאת אפס
כאשר בונים משחקי דו-ממד במנועים חזותיים כמו Unity או Godot, זיהוי התנגשות מרגיש לעתים קרובות כמו סדרה של תיבות סימון של מפקחים: אתה מטיח על BoxCollider2D או CircleCollider2D, מצרף Rigidbody, ומקווה ששלב הפיזיקה הפנימי לא יגמגם במכשירים ניידים.
עם זאת, ב-MonoGame וב-C# אתם בשליטה מוחלטת. אין פיזיקה נסתרת, אין אינרציה סיבובית לא רצויה, ואין קוצים מסתוריים של איסוף אשפה (GC) שגונבים את תקציב המסגרת שלך.
ב-Arar Games, כשבנינו את Blocked: Pixel Panzer ו-Paint Trek, לולאות משחקי הארקייד שלנו היו דרושות לעיבוד מאות כדורי אויב מהירים, רשתות לבנים מתפוצצות, צריחי טנקים מסתובבים, טיסות מטוסי קרב ומגני חלקיקים במהירות 60 עד 120 FPS במכשירי Windows ו-Android. מנוע פיזיקה למטרות כלליות לא היה בא בחשבון - היינו צריכים ארכיטקטורת התנגשות מדורגת תכליתית.
במדריך המקיף הזה, מונחה הקוד, נתחיל מהיסודות המוחלטים של התנגשות MonoGame (מלבן. מצטלב עם ספרייטים של כדור ואויב אמיתיים) ונבנה לבדיקות מעגלים מתקדמות, הידוק מעורב, הקרנות נגד מנהור, Pixel-Per, All-Pixel-Zero ו-Pixel-Ffect Spa. רשתות** מותאמות להישרדות GC ניידים.
1. The Foundation: A Simple MonoGame 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);
}
}
עכשיו בואו ניצור מחלקות 'שחקן', 'אויב' ו-'כדור' קונקרטיות:
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)
בדיקת ההתנגשות הדו-ממדית הבסיסית ביותר ב-MonoGame היא מבחן Axis-Aligned Bounding Box (AABB). המונח "מיושר ציר" אומר בפשטות שהקצוות של המלבן מקבילים לחלוטין לצירים \(X\) ו-\(Y\) של המסך (ללא סיבוב).
MonoGame מספקת שיטה מהירה ומובנית: Rectangle.Intersects(Retangle value).
איך 'מלבן. מצטלב' עובד מתחת למכסה המנוע
מתחת לפני השטח, 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'. איטרציה לאחור מאפשרת הסרה בטוחה של אלמנטים ללא בעיות אינדקס מחדש של הזיכרון.
סוד הארקייד: "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, תיבת תוחמת מיושרת ציר חייבת להתרחב כדי לסגור את הפינות המסתובבות, וכתוצאה מכך "התנגשויות פנטום" מתסכלות באוויר ריק.
עבור אסטרואידים מעגליים, כדורי אנרגיה ביות וחלליות מסתובבות, מעגלים תוחמים הם הפתרון האידיאלי.
מלכודת השורש
שני מעגלים מתנגשים כאשר המרחק בין המרכזים שלהם קטן או שווה לסכום הרדיוסים שלהם:
\(\text{מרחק}(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), אובייקט שנע במהירות של 1,800 פיקסלים לשנייה עובר 30 פיקסלים בפריים בודד. אם עובי גוף האויב הוא רק 15 פיקסלים, הקליע היה לפני האויב על פריים 1, ומאחורי האויב על פריים 2.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
הפתרון: פלח סוחף לעומת תיבה (שיטת לוח)
במקום לבדוק נקודה בודדת, אנו בודקים את כל קטע הקו המחבר את מיקום הכדור על מסגרת 1 (PreviousPosition) למסגרת 2 (currentPosition).
הנה שיטת ייצור raycasting slab-intersection מהכותרת המלווה שלנו 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: זיהוי התנגשות בדרגת ייצור לפי פיקסל מושלם
כעת אנו מגיעים לרמה האולטימטיבית של דיוק דו-ממד: התנגשות פיקסל מושלמת.
בקרב טנקים רטרו או קרב חללית, צורות לא סדירות (חביות טנקים, כנפיים, תא הטייס) מוקפות בפיקסלים שקופים במרקם הספרייט. כאשר טיל אויב פוגע בחלל השקוף הזה, השחקנים מבחינים מיד.
התנגשות פיקסל מושלמת בודקת את ערוצי האלפא (שקיפות) בפועל של המרקמים החופפים. אם שני פיקסלים לא שקופים חופפים באותה קואורדינטת עולם, התרחשה פגיעה פיזית אמיתית.
הטעות הגורלית: 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()' במהלך זמן הריצה מאלצת את ה-CPU להמתין לשטיפה של צינור ה-GPU, ומושכת את זיכרון הטקסטורה חזרה מעל האוטובוס. אם תעשה זאת עבור עשרות ספרייטים, כל פריים יעביר את המשחק שלך מ-60 FPS ל-5 FPS ויפעיל ANR של אנדרואיד באופן מיידי!
הפתרון שנבדק בקרב מחסום: פיקסל פנצר
ב-Blocked: Pixel Panzer, Sprite.cs שלנו מיישם שגרת IntersectsPixel אופטימלית במיוחד הבנויה סביב 5 כללים מכריעים:
- מטמון מערך צבעים סטטי:
GetDataנקרא בדיוק פעם אחת כאשר המרקם נטען, ומאוחסן במילון<Texture2D, Color[]>. - משמר יציאה מוקדמת של AABB: אם 'Bounds.Intersects(other.Bounds)' הוא שקרי, נחלץ מיד. 99% מהבדיקות מבוטלות לפני בדיקת פיקסל בודד.
- תמיכה בטקסטורה אטלס ו-'SourceRectangle': מטפל ב-sprites ארוזים באטלס טקסטורה באמצעות קיזוז מלבן מקור.
- חלון חפיפה מחושב: אנחנו רק בלולאה על מלבן החיתוך המדויק בין שני הספרייטים (
Math.Max(a.Top, b.Top)וכו'). - Alpha Short-Circuiting: אם הפיקסל של Sprite A שקוף (
A <= 20), Sprite B ידלג לחלוטין. - הרמת נכסים: גישה למאפיינים וירטואליים (
גבולות) בלולאות מקוננות יוצרת אלפי עותקי struct. אנו מאחסנים אותם במשתני מחסנית מקומיים לפני הכניסה ללולאה.
הנה הקוד המלא, מוכן לייצור:
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 אלפיות השנייה בחומרה מודרנית של אנדרואיד ובמערכות שולחניות.
7. רמה 6: רשת מרחבית רחבת שלב ו-GC נייד עם הקצאת אפס
תאר לעצמך שיש למשחק שלך 100 בלוקים, 60 כדורים ו-20 אויבים. אם אתה בודק כל ישות מול כל ישות אחרת באמצעות לולאות 'for' מקוננות, אתה מבצע:
\(\frac{180 \times 179}{2} = 16,110 \text{ המחאות לכל מסגרת!}\)
ב-60 FPS, זה כמעט 1,000,000 בדיקות התנגשות בכל שנייה.
כדי להגדיל את המשחק שלך, עליך ליישם חלוקה מרחבית רחבה לשלבים.
רשת האח המרחבית האחידה
אנו מחלקים את המסך לרשת דו-ממדית של תאים אחידים (ב-חסומים: Pixel Panzer, כל תא הוא \(150 \x 150\) פיקסלים). ישות בודקת רק התנגשויות מול ישויות אחרות השוכנות באותם תאי רשת.
בעיית הזיכרון הנייד: GC Thrashing
אם SpatialGrid שלך יוצר רשימה חדשה<Point>() או רשימה חדשה<Block>() בכל פריים, אתה מקצה מגה-בייט של זבל בכל דקה בערימה המנוהלת.
בזמן ריצה מונו של אנדרואיד, זה מפעיל איסוף זבל תכוף מהדור 0, מה שגורם ל:
זמן ריצה מונו: התמודדות עם נעילת מקור (mono_class_is_subclass_)
המשחק מגמגם ו-Google Play מסמן את המשחק שלך עם אזהרות ANR!
הפתרון: מאגרי שריטות לשימוש חוזר
הנה רשת האפס מרחבית להקצאת אפס מ-'CollisionManager.cs' ב-** חסום: פיקסל פנצר**:
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) | כן (בלתי משתנה) | לא | ספינות עגולות, כדורי אש, כדורי אנרגיה |
Circle vs Box (Clamp) |
מהיר (מקומי MathHelper.Clamp) | כן | לא | שחקן עגול מנווט קירות הדוקים |
| Swept Ray (Slab CCD) | בינוני (Raycast פרמטרי) | כן | כן | כדורי צלפים מהירים, רובי רכבת, לייזרים |
Pixel-Perfect (IntersectsPixel) |
סלקטיבי (תת-רקט מוגבל) | כן | לא | קווי מתאר ספרייט לא סדירים, קופסאות הוגנים |
| רשת מרחבית (אפס-GC) | שלב רחב (\(O(N^2) \to O(N)\)) | לא רלוונטי | לא רלוונטי | גלים צפופים, גיהנום של כדורים, אנדרואיד 60 FPS |
מופעי הפקה בעולם האמיתי: משחקי Arar
ארכיטקטורות ההתנגשות הללו אינן ניסויים תיאורטיים - הן הבסיס ההנדסי האמיתי המניע את הכותרים שפורסמו באופן מסחרי:
- חסום: Pixel Panzer: משחק ארקייד ההישרדות של טנקים רטרו ב-Google Play וב-Microsoft Store. הוא כולל את מערכת ההתנגשות הדו-שכבתית המלאה שלנו: רשת hash מרחבית בהקצאה אפסית המסננת בלוקים נופלים, ובדיקת מגע מושלם לפיקסלים של טנקים, מטוסי קרב, פגזי צריח ותחמושת אלמנטרית של ColorWheel.
- Paint Trek: היורה החלל המהיר שלנו הכולל התנגשות מעגלים סיבוביים, הקרנת קרינה מתמשכת ומערכות הגנה מפני טילים בנפח סוחף.
מסקנה והשלבים הבאים
MonoGame נותן לך את הכוח לעצב זיהוי התנגשות התואם בצורה מושלמת את צורכי המשחק שלך. על ידי העברת בדיקות Pixel-Perfect יקרות מאחורי בדיקות AABB מהירות, ניצול מרחקים מרובעים עבור מעגלים, וביטול איסוף אשפה עם מאגרי שריטות לשימוש חוזר, אתה יכול לספק ביצועים חלקים של 60/120 FPS בפלטפורמות שולחניות ונייד.
בדוק את המשחקים שלנו בחנויות האפליקציות כדי לראות את מערכות ההתנגשות הללו בפעולה, והתחיל ליישם את הדפוסים האלה בפרויקטים של MonoGame משלך עוד היום!
קישורים ומשאבים לחנות
- חסום: Pixel Panzer ב-Google Play: הורדה לאנדרואיד
- חסום: Pixel Panzer ב-Microsoft Store: הורדה עבור Windows
- MonoGame Framework: monogame.net
מילות מפתח & Hashtag SEO
מילות מפתח: זיהוי התנגשות MonoGame 2D, פיתוח משחקי C#, Rectangle. מצטלב MonoGame, התנגשות פיקסל מושלמת C#, רשת hash מרחבית MonoGame, התנגשות רציפה של קרניים נסחפות, אופטימיזציה של ביצועי משחק אינדי, לולאת משחקי הקצאת אפס, אופטימיזציה של משחקי אנדרואיד, אופטימיזציה של פיקסל MonoGame, אופטימיזציה של אנדרואיד.
#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode `#BlockedPixelPanzer``##Paintrek##Paintrek
Hiç yorum yok:
Yorum Gönder