Phát hiện va chạm 2D trong MonoGame: Từ hình chữ nhật cơ bản đến hệ thống hoàn hảo về điểm ảnh không phân bổ
Khi xây dựng trò chơi 2D bằng các công cụ trực quan như Unity hoặc Godot, việc phát hiện va chạm thường giống như một loạt hộp kiểm tra: bạn nhấn vào BoxCollider2D hoặc CircleCollider2D, đính kèm Rigidbody và hy vọng bước vật lý bên trong không bị vấp trên thiết bị di động.
Tuy nhiên, trong MonoGame và C#, bạn có toàn quyền kiểm soát. Không có chi phí vật lý ẩn, không có quán tính quay không mong muốn và không có đột biến thu gom rác (GC) bí ẩn nào ăn cắp ngân sách khung hình của bạn.
Tại Arar Games, khi chúng tôi xây dựng Blocked: Pixel Panzer và Paint Trek, vòng lặp trò chơi arcade của chúng tôi cần xử lý hàng trăm viên đạn tốc độ cao của kẻ thù, lưới gạch nổ, tháp xe tăng xoay, máy bay chiến đấu bay ngang qua và lá chắn hạt ở tốc độ 60 đến 120 FPS trên cả thiết bị Windows PC và Android. Một công cụ vật lý có mục đích chung là không thể - chúng tôi cần một kiến trúc va chạm theo cấp độ, được xây dựng có mục đích.
Trong hướng dẫn toàn diện, dựa trên mã này, chúng ta sẽ bắt đầu từ những điều cơ bản tuyệt đối về va chạm MonoGame (Rectangle.Intersects với các họa tiết Bullet và Enemy thực) và xây dựng các bước kiểm tra vòng tròn nâng cao, kẹp hỗn hợp, raycast chống đào hầm, va chạm hoàn hảo theo pixel cấp sản xuất và Lưới không gian phân bổ bằng 0 được tối ưu hóa để tồn tại GC trên thiết bị di động.
1. Nền tảng: Hệ thống phân cấp Sprite MonoGame đơn giản
Trước khi phát hiện xung đột, chúng ta cần các thực thể trò chơi sạch. Trong MonoGame, một thực thể về cơ bản sở hữu một vị trí, kết cấu và hình chữ nhật bao quanh.
Đây là kiến trúc thực thể cơ bản được sử dụng trong các trò chơi của chúng tôi:
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);
}
}
Bây giờ, hãy tạo các lớp Player, Enemy và Bullet cụ thể:
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;
}
}
Với những thực thể này, hãy khám phá cách phát hiện xung đột giữa chúng, bắt đầu từ phương pháp đơn giản nhất.
2. Cấp độ 1: Va chạm đơn giản nhất – Rectangle.Intersects (AABB)
Kiểm tra va chạm 2D cơ bản nhất trong MonoGame là thử nghiệm Hộp giới hạn căn chỉnh theo trục (AABB). Thuật ngữ "căn chỉnh theo trục" chỉ có nghĩa là các cạnh của hình chữ nhật hoàn toàn song song với trục \(X\) và \(Y\) của màn hình (không xoay).
MonoGame cung cấp một phương thức tích hợp nhanh: Rectangle.Intersects(Rectangle value).
Cách thức hoạt động của Rectangle.Intersects
Bên dưới bề mặt, MonoGame thực hiện bốn phép so sánh số nguyên:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
Nếu tất cả bốn điều kiện được đáp ứng, các hình chữ nhật sẽ chồng lên nhau. Nếu thậm chí một điều kiện không thành công, một trục trống sẽ tách chúng ra và không thể xảy ra va chạm.
Mã trò chơi thực tế: Bullet vs. Enemy trong Game1.Update
Đây là cách bạn kiểm tra va chạm giữa danh sách các viên đạn đang hoạt động và danh sách kẻ thù đang hoạt động bên trong vòng lặp MonoGame Update chính của bạn:
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);
}
}
Mẹo về hiệu suất: Lưu ý rằng chúng tôi lặp ngược lại (
for (int i = list.Count - 1; i >= 0; i--))! Nếu bạn sử dụngforeachvà cố gắng gọi_bullets.Remove(bullet), C# sẽ ném ra mộtInvalidOperationException: Collection đã được sửa đổi. Việc lặp lại cho phép loại bỏ phần tử an toàn mà không gặp vấn đề về lập chỉ mục lại bộ nhớ.
Bí mật trò chơi điện tử: "Hitboxes công bằng" thông qua Inflate
Trong các trò chơi cổ điển như Blocked: Pixel Panzer, họa tiết sprite thường bao gồm các lề trong suốt hoặc các gai ăng-ten. Nếu xe tăng của người chơi phát nổ do một viên đạn chạm vào một góc trong suốt trống rỗng trên kết cấu của nó, người chơi sẽ cảm thấy bị lừa.
Để tạo cảm giác va chạm nhanh và công bằng, trò chơi sử dụng Hitbox nhỏ hơn bên trong sprite bằng cách sử dụng 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. Cấp độ 2: Va chạm vòng tròn (Miễn dịch quay)
Hình chữ nhật hoạt động tốt cho các khối tĩnh và ô lưới, nhưng chúng không thành công khi các hình vẽ xoay. Khi một con tàu vũ trụ không vuông quay trong Paint Trek, hộp giới hạn theo trục phải mở rộng để bao quanh các góc quay, dẫn đến "va chạm ảo" khó chịu trong không khí trống rỗng.
Đối với các tiểu hành tinh hình tròn, quả cầu năng lượng dẫn đường và tàu vũ trụ quay, Vòng tròn giới hạn là giải pháp lý tưởng.
Bẫy căn bậc hai
Hai đường tròn va chạm nhau khi khoảng cách giữa tâm của chúng nhỏ hơn hoặc bằng tổng bán kính của chúng:
\(\text{Khoảng cách}(C_A, C_B) \le r_A + r_B\)
Trong mã, tính toán khoảng cách Euclide sử dụng Math.Sqrt (hoặc Vector2.Distance). Tuy nhiên, việc tính căn bậc hai trong một vòng lặp với 200 đường đạn sẽ đốt cháy hàng trăm chu kỳ CPU không cần thiết!
Bằng cách so sánh khoảng cách bình phương với tổng bán kính bình phương, chúng ta loại bỏ hoàn toàn căn bậc hai:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
Triển khai MonoGame: Circle so với 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);
}
}
Bây giờ tích hợp điều này trực tiếp vào một thực thể:
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);
}
}
Không có căn bậc hai, không bị xoay sprite và nhanh như chớp.
4. Cấp độ 3: Hình dạng hỗn hợp – Hình tròn so với Hình hộp (MathHelper.Clamp)
Điều gì sẽ xảy ra khi một con tàu vũ trụ hình tròn trong Paint Trek di chuyển qua một mê cung chật hẹp gồm các hàng rào phòng thủ hình chữ nhật hoặc khi một viên đạn tròn bắn trúng một khối vuông trong Blocked: Pixel Panzer?
Chúng ta cần va chạm Hình tròn và Hình chữ nhật.
Thuật toán kẹp
Chiến lược là tìm điểm trên hình chữ nhật gần tâm hình tròn nhất, sau đó kiểm tra xem khoảng cách từ điểm gần nhất đó đến tâm có nhỏ hơn bán kính hình tròn hay không.
MathHelper.Clamp của MonoGame khiến điều này trở nên tầm thường:
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);
}
}
Bây giờ bạn có thể kiểm tra lá chắn của người chơi chống lại các viên gạch hình chữ nhật với mức phân bổ bằng 0 và độ chính xác cao!
5. Cấp độ 4: Phát hiện va chạm liên tục (CCD) & Tia quét
Bạn đã bao giờ bắn một phát đạn bắn tỉa siêu tốc hoặc một khẩu súng laser trong trò chơi của mình chỉ để xem viên đạn xuyên thẳng qua một con tàu mỏng của kẻ thù một cách kỳ diệu mà không gây sát thương chưa?
Lỗi này được gọi là đào hầm.
Vì các trò chơi riêng biệt cập nhật theo các bước thời gian (\(\Delta t = 16,6\text{ms}\) ở tốc độ 60 FPS), một vật thể chuyển động với tốc độ 1.800 pixel mỗi giây sẽ di chuyển 30 pixel trong một khung hình. Nếu thân tàu địch chỉ dày 15 pixel thì viên đạn nằm ở phía trước kẻ địch trên Khung 1 và hoàn toàn ở phía sau kẻ thù trên Khung 2.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
Giải pháp: Phân đoạn quét so với Hộp (Phương pháp phiến)
Thay vì kiểm tra một điểm duy nhất, chúng tôi kiểm tra toàn bộ đoạn đường nối vị trí của dấu đầu dòng trên Khung 1 (previousPosition) đến Khung 2 (currentPosition).
Đây là phương pháp sản xuất raycasting Slab-intersection từ tiêu đề đồng hành của chúng tôi 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;
}
}
Trong Blocked: Pixel Panzer, Kỹ năng Tia Laser liên tục của người chơi sử dụng phương pháp truyền tia chính xác này để cắt xuyên qua các hàng khối giảm dần mà không bỏ sót một va chạm nào.
6. Cấp độ 5: Phát hiện va chạm pixel hoàn hảo cấp độ sản xuất
Giờ đây, chúng tôi đã đạt đến mức độ chính xác 2D cao nhất: Va chạm hoàn hảo đến từng pixel.
Trong game bắn súng xe tăng cổ điển hoặc trận không chiến tàu vũ trụ, các hình dạng bất thường (thùng xe tăng, cánh, buồng lái buồng lái) được bao quanh bởi các pixel trong suốt trong kết cấu sprite. Khi tên lửa của địch bắn trúng khoảng không trong suốt đó, người chơi sẽ nhận ra ngay lập tức.
Va chạm hoàn hảo về điểm ảnh sẽ kiểm tra các kênh alpha (độ trong suốt) thực tế của các họa tiết chồng chéo. Nếu hai pixel không trong suốt chồng lên nhau ở cùng một tọa độ thế giới, thì một cú đánh vật lý thực sự đã xảy ra.
Sai lầm tai hại: GetData bên trong Update()
Nhiều hướng dẫn hướng dẫn người mới bắt đầu làm điều này:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
Việc gọi Texture2D.GetData() trong thời gian chạy buộc CPU phải đợi đường dẫn GPU xử lý, kéo bộ nhớ kết cấu trở lại bus. Thực hiện việc này cho hàng chục hình ảnh trong mỗi khung hình sẽ giúp trò chơi của bạn tăng tốc từ 60 FPS lên 5 FPS và kích hoạt ANR của Android ngay lập tức!
Giải pháp đã được thử nghiệm trong trận chiến từ Bị chặn: Pixel Panzer
Trong Đã chặn: Pixel Panzer, Sprite.cs của chúng tôi triển khai quy trình IntersectsPixel cực kỳ tối ưu hóa được xây dựng dựa trên 5 quy tắc quan trọng:
- Bộ nhớ đệm mảng màu tĩnh:
GetDatađược gọi chính xác một lần khi kết cấu tải và được lưu trữ trongDictionary<Texture2D, Color[]>. - AABB Bảo vệ thoát sớm: Nếu
Bounds.Intersects(other.Bounds)là sai, chúng tôi sẽ bảo lãnh ngay lập tức. 99% lượt kiểm tra được loại bỏ trước khi kiểm tra từng pixel. - Hỗ trợ Bản đồ kết cấu &
SourceRectangle: Xử lý các họa tiết được đóng gói thành các tập bản đồ kết cấu bằng cách sử dụng các offset hình chữ nhật nguồn. - Cửa sổ chồng chéo được tính toán: Chúng tôi chỉ lặp qua hình chữ nhật giao nhau chính xác giữa cả hai họa tiết (
Math.Max(a.Top, b.Top), v.v.). - Ngắn mạch Alpha: Nếu pixel của Sprite A trong suốt (
A <= 20), Sprite B hoàn toàn bị bỏ qua. - Kéo thuộc tính: Truy cập các thuộc tính ảo (
Giới hạn) trong các vòng lặp lồng nhau sẽ tạo ra hàng nghìn bản sao cấu trúc. Chúng tôi lưu trữ chúng trong các biến ngăn xếp cục bộ trước khi vào vòng lặp.
Đây là mã hoàn chỉnh, sẵn sàng sản xuất:
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;
}
}
Quy trình này thực hiện trong vòng chưa đầy 0,1 mili giây trên hệ thống máy tính để bàn và phần cứng Android hiện đại.
7. Cấp độ 6: Lưới không gian pha rộng & GC di động phân bổ không
Hãy tưởng tượng trò chơi của bạn có 100 khối, 60 viên đạn và 20 kẻ thù. Nếu bạn kiểm tra mọi thực thể với mọi thực thể khác bằng cách sử dụng các vòng lặp for lồng nhau, thì bạn thực hiện:
\(\frac{180 \times 179}{2} = 16.110 \text{ kiểm tra trên mỗi khung!}\)
Ở tốc độ 60 FPS, tức là gần 1.000.000 lượt kiểm tra va chạm mỗi giây.
Để mở rộng quy mô trò chơi của mình, bạn phải triển khai Phân vùng không gian theo pha rộng.
Lưới băm không gian thống nhất
Chúng tôi chia màn hình thành một lưới 2D gồm các ô đồng nhất (trong Blocked: Pixel Panzer, mỗi ô có giá trị \(150 \times 150\) pixel). Một thực thể chỉ kiểm tra xung đột với các thực thể khác nằm trong cùng một ô lưới.
Vấn đề về bộ nhớ di động: GC bị đập
Nếu SpatialGrid của bạn tạo Danh sách mới<Point>() hoặc Danh sách mới<Block>() mỗi khung hình, thì bạn phân bổ megabyte rác mỗi phút trên vùng nhớ được quản lý.
Trên thời gian chạy Mono của Android, điều này kích hoạt Bộ sưu tập rác Gen-0 thường xuyên, gây ra:
thời gian chạy đơn âm: Tranh chấp khóa gốc (mono_class_is_subclass_)
Trò chơi bị giật và Google Play gắn cờ trò chơi của bạn kèm theo cảnh báo ANR!
Giải pháp: Bộ đệm chống xước có thể tái sử dụng
Đây là Lưới băm không gian phân bổ bằng 0 từ CollisionManager.cs trong Đã chặn: 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);
}
}
}
}
}
}
}
}
Bằng cách sử dụng lại _scratchCells và _scratchCheckedEntities, toàn bộ bước xung đột sẽ chạy với 0 byte phân bổ vùng nhớ heap cho mỗi khung.
##8. Bảng so sánh tóm tắt
| Kỹ thuật | Chi phí toán học | Hỗ trợ luân phiên | Đào hầm an toàn? | Trường hợp sử dụng tốt nhất trong MonoGame |
|---|---|---|---|---|
AABB (Hình chữ nhật.Giao điểm) |
Cực nhanh (~4 so sánh số nguyên) | Không | Không | Khối lưới, pha rộng, giao diện người dùng |
Vòng tròn (Khoảng cách bình phương) |
Rất nhanh (3 mul, 0 sqrt) | Có (Bất biến) | Không | Tàu tròn, quả cầu lửa, quả cầu năng lượng |
Vòng tròn vs Hộp (Kẹp) |
Nhanh (MathHelper.Clamp cục bộ) | Có | Không | Máy nghe nhạc tròn điều hướng các bức tường chật hẹp |
| Tia quét (CCD phiến) | Trung bình (Raycast tham số) | Có | Có | Đạn bắn tỉa nhanh, súng trường, tia laser |
Pixel-Perfect (IntersectsPixel) |
Chọn lọc (Phụ lục bị hạn chế) | Có | Không | Đường viền sprite không đều, hitbox công bằng |
| Lưới không gian (Zero-GC) | Pha rộng (\(O(N^2) \to O(N)\)) | Không áp dụng | Không áp dụng | Sóng dày đặc, địa ngục đạn, Android 60 FPS |
Buổi giới thiệu sản xuất trong thế giới thực: Arar Games
Những kiến trúc va chạm này không phải là những thử nghiệm lý thuyết—chúng là nền tảng kỹ thuật thực sự hỗ trợ các tựa game được phát hành thương mại của chúng tôi:
- Đã chặn: Pixel Panzer: Trò chơi arcade sinh tồn xe tăng cổ điển của chúng tôi trên Google Play và Microsoft Store. Nó có hệ thống va chạm hai tầng hoàn chỉnh của chúng tôi: lưới băm không gian phân bổ bằng 0 lọc các khối rơi và kiểm tra tiếp xúc hoàn hảo đến từng pixel đối với xe tăng, máy bay chiến đấu, đạn pháo tháp và đạn nguyên tố ColorWheel.
- Paint Trek: Game bắn súng không gian nhịp độ nhanh của chúng tôi có hệ thống va chạm vòng tròn quay, truyền tia liên tục và hệ thống phòng thủ tên lửa quét theo khối lượng.
Kết luận & Các bước tiếp theo
MonoGame cung cấp cho bạn khả năng thiết kế tính năng phát hiện va chạm hoàn toàn phù hợp với nhu cầu trò chơi của bạn. Bằng cách kiểm tra Pixel-Perfect đắt tiền đằng sau các thử nghiệm AABB nhanh, sử dụng Khoảng cách bình phương cho vòng tròn và loại bỏ việc thu gom rác bằng Bộ đệm trầy xước có thể tái sử dụng, bạn có thể mang lại hiệu suất 60/120 FPS mượt mà như bảng điều khiển trên cả nền tảng máy tính để bàn và thiết bị di động.
Hãy xem các trò chơi của chúng tôi trên các cửa hàng ứng dụng để xem hoạt động của các hệ thống va chạm này và bắt đầu triển khai các mô hình này trong các dự án MonoGame của riêng bạn ngay hôm nay!
Lưu trữ liên kết & tài nguyên
- Đã chặn: Pixel Panzer trên Google Play: Tải xuống cho Android
- Đã chặn: Pixel Panzer trên Microsoft Store: Tải xuống cho Windows
- Khung MonoGame: monogame.net
Từ khóa SEO & Hashtags
Từ khóa: Phát hiện va chạm MonoGame 2D, phát triển trò chơi C#, Rectangle.Intersects MonoGame, va chạm pixel hoàn hảo C#, lưới băm không gian MonoGame, va chạm tia quét liên tục, tối ưu hóa hiệu suất trò chơi indie, vòng lặp trò chơi phân bổ bằng 0, tối ưu hóa Android MonoGame, Trò chơi Arar, Pixel Panzer bị chặn, Paint Trek.
#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixelPanzer #PaintTrek #AarGames #GameProgramming
Hiç yorum yok:
Yorum Gönder