MonoGame の 2D 衝突検出: 基本的な四角形からゼロ割り当てのピクセルパーフェクト システムまで
Unity や Godot などのビジュアル エンジンで 2D ゲームを構築する場合、衝突検出は一連のインスペクター チェックボックスのように感じられることがよくあります。「BoxCollider2D」または「CircleCollider2D」をタップし、「Rigidbody」をアタッチし、内部の物理ステップがモバイル デバイス上で途切れないことを祈ります。
ただし、MonoGame と C# では、完全に制御できます。隠れた物理的オーバーヘッド、不要な回転慣性、フレーム バジェットを盗む謎のガベージ コレクション (GC) スパイクはありません。
Arar Games では、Blocked: Pixel Panzer と Paint Trek を構築したとき、アーケード ゲーム ループは、Windows PC と Android デバイスの両方で、数百もの高速の敵の弾丸、爆発するレンガ グリッド、回転する戦車砲塔、戦闘機のフライバイ、パーティクル シールドを 60 ~ 120 FPS で処理する必要がありました。汎用の物理エンジンは問題外で、専用の階層型衝突アーキテクチャが必要でした。
この包括的なコードベースのガイドでは、MonoGame コリジョン (実際の「Bullet」 および「Enemy」 スプライトとの「Rectangle.Intersects」) の絶対的な基本から始めて、高度な円チェック、混合クランプ、アンチトンネリング レイキャスト、プロダクション グレードの ピクセルパーフェクトコリジョン、およびモバイル GC 存続のために最適化された ゼロ割り当て空間グリッドまでを構築していきます。
1. 基礎: 単純なモノゲームの「スプライト」階層
衝突を検出する前に、クリーンなゲーム エンティティが必要です。 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: 最も単純な衝突 – Rectangle.Intersects (AABB)
MonoGame の最も基本的な 2D 衝突チェックは、軸合わせバウンディング ボックス (AABB) テストです。 「軸が揃っている」という用語は、長方形の端が画面の \(X\) 軸と \(Y\) 軸に完全に平行である (回転していない) ことを単に意味します。
MonoGame は、高速な組み込みメソッド Rectangle.Intersects(Rectangle value) を提供します。
Rectangle.Intersects が内部的にどのように機能するか
MonoGame は表面下で 4 つの整数比較を実行します。
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
4 つの条件がすべて満たされる場合、長方形は重なり合います。 1 つの条件でも失敗すると、空の軸がそれらを分離し、衝突は起こりません。
実際のゲームプレイ コード: Game1.Update の Bullet と Enemy の比較
以下は、メインの MonoGame Update ループ内で、アクティブな弾丸のリストとアクティブな敵のリストの間の衝突をチェックする方法です。
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 updatedをスローします。逆方向に反復処理を行うと、メモリの再インデックスの問題を発生させることなく、安全に要素を削除できます。
アーケードの秘密: Inflate による「公平なヒットボックス」
Blocked: Pixel Panzer のようなレトロ ゲームでは、スプライト テクスチャに透明なマージンやアンテナ スパイクが含まれることがよくあります。弾丸がテクスチャの空いている透明な角に触れたためにプレイヤーの戦車が爆発した場合、プレイヤーはだまされたと感じるでしょう。
衝突の応答性と公平性を高めるために、ゲームでは 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 で非正方形の宇宙船が回転すると、回転する角を囲むように軸に合わせた境界ボックスを拡張する必要があり、空の空中でイライラする「ファントム衝突」が発生します。
円形の小惑星、ホーミング エネルギー球、回転する宇宙船の場合、バウンディング サークル が理想的なソリューションです。
平方根の罠
2 つの円は、中心間の距離が半径の合計以下の場合に衝突します。
\(\text{距離}(C_A, C_B) \le r_A + r_B\)
コードでは、ユークリッド距離の計算には Math.Sqrt (または Vector2.Distance) を使用します。ただし、200 個の発射体を使用したループで平方根を計算すると、何百もの不必要な CPU サイクルが消費されます。
距離の二乗と半径の二乗合計を比較することで、平方根を完全に排除します。
\(\text{距離の二乗} \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)
ペイント トレックで円形の宇宙船が長方形の防御障壁の狭い迷路を通過するとき、またはBlocked: Pixel Panzerで丸い弾丸が四角いブロックに当たったらどうなりますか?
円と四角形の衝突が必要です。
クランプアルゴリズム
この戦略は、円の中心に最も近い長方形上の点を見つけて、その最も近い点から中心までの距離が円の半径より小さいかどうかをテストすることです。
MonoGame の「MathHelper.Clamp」を使用すると、これが簡単になります。
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) と掃引光線
ゲーム内で超高速の狙撃弾やレールガン レーザーを発射したものの、ダメージを与えることなく、弾丸が魔法のように薄い敵艦を真っ直ぐ通過するのを見たことはありませんか?
このバグは トンネリング として知られています。
個別のゲームはタイム ステップ (60 FPS で \(\Delta t = 16.6\text{ms}\)) で更新されるため、1 秒あたり 1,800 ピクセルで移動するオブジェクトは 1 フレームで 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 精度の究極レベルである ピクセルパーフェクトコリジョン に到達しています。
レトロな戦車射手や宇宙船の空中戦では、不規則な形状 (戦車の砲身、翼、コックピットのコックピット) がスプライト テクスチャ内の透明なピクセルで囲まれます。敵のミサイルがその透明な空間に命中すると、プレイヤーはすぐに気づきます。
ピクセルパーフェクトコリジョンは、重なり合うテクスチャの実際のアルファ (透明度) チャネルを検査します。 2 つの不透明なピクセルが同じワールド座標で重なっている場合、真の物理的ヒットが発生しています。
致命的な間違い: Update() 内の GetData
多くのチュートリアルでは、初心者に次のことを行うように指示しています。
// 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 に低下し、すぐに Android ANR がトリガーされます。
Blocked: Pixel Panzer による実証済みのソリューション
Blocked: Pixel Panzer では、Sprite.cs は 5 つの重要なルールを中心に構築された超最適化された IntersectsPixel ルーチンを実装しています。
- 静的カラー配列キャッシュ:
GetDataはテクスチャのロード時に 1 回だけ呼び出され、Dictionary<Texture2D, Color[]>に保存されます。 - AABB Early Exit Guard:
Bounds.Intersects(other.Bounds)が false の場合、直ちにベイルアウトします。単一ピクセルを検査する前に、チェックの 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;
}
}
このルーチンは、最新の Android ハードウェアおよびデスクトップ システムでは 0.1 ミリ秒未満で実行されます。
7. レベル 6: ブロードフェーズ空間グリッドとゼロ割り当てモバイル GC
ゲームに 100 個のブロック、60 個の弾丸、20 人の敵があると想像してください。ネストされた for ループを使用して、すべてのエンティティを他のすべてのエンティティに対してテストする場合は、次を実行します。
\(\frac{180 \times 179}{2} = 16,110 \text{ フレームごとのチェック!}\)
60 FPS では、毎秒 1,000,000 回の衝突チェックに相当します。
ゲームをスケーリングするには、ブロードフェーズ空間パーティショニングを実装する必要があります。
均一空間ハッシュ グリッド
画面を均一なセルの 2D グリッドに分割します (ブロック: Pixel Panzer では、各セルは \(150 \x 150\) ピクセルです)。エンティティは、同じグリッド セル内に存在する他のエンティティとの衝突のみをテストします。
モバイル メモリの問題: GC スラッシング
SpatialGrid がフレームごとに new List<Point>() または new List<Block>() を作成する場合、マネージド ヒープ上に毎分メガバイトのガベージが割り当てられることになります。
Android の Mono ランタイムでは、これにより Gen-0 ガベージ コレクションが頻繁にトリガーされ、次のような問題が発生します。
mono ランタイム: ネイティブ ロック競合 (mono_class_is_subclass_)
ゲームが途切れ、Google Play がゲームに ANR 警告のフラグを立てます。
解決策: 再利用可能なスクラッチ バッファ
ブロックされた: Pixel Panzer の「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 を再利用することで、衝突ステップ全体が 1 フレームあたり 0 バイトのヒープ割り当てで実行されます。
8. 概要比較表
| テクニック | 数学的コスト | 回転サポート | トンネルは安全ですか? | MonoGame のベストユースケース |
|---|---|---|---|---|
AABB (Rectangle.Intersects) |
超高速 (最大 4 つの整数比較) | いいえ | いいえ | グリッド ブロック、ブレット ブロード フェーズ、UI |
円 (DistanceSquared) |
非常に高速 (3 マル、0 sqrt) | はい (不変) | いいえ | 丸い船、火の玉、エネルギー球 |
サークル vs ボックス (クランプ) |
高速 (ローカル MathHelper.Clamp) | はい | いいえ | 狭い壁を移動する円形プレーヤー |
| 掃引光線 (スラブ CCD) | 中程度 (パラメトリック レイキャスト) | はい | はい | 高速狙撃弾、レールガン、レーザー |
ピクセルパーフェクト (IntersectsPixel) |
選択的 (制限されたサブレクチャー) | はい | いいえ | 不規則なスプライト輪郭、公平なヒットボックス |
| 空間グリッド (ゼロ GC) | ブロードフェーズ (\(O(N^2) \to O(N)\)) | 該当なし | 該当なし | 密な波、弾幕、Android 60 FPS |
現実世界のプロダクション ショーケース: Arar Games
これらのコリジョン アーキテクチャは理論的な実験ではなく、商用リリースされたタイトルを強化する実際のエンジニアリング基盤です。
- ブロック済み: Pixel Panzer: Google Play および Microsoft Store でのレトロな戦車サバイバル アーケード ゲーム。これは、完全な 2 層衝突システムを備えています。落下ブロックをフィルタリングするゼロ割り当て空間ハッシュ グリッドと、戦車、戦闘機、砲塔砲弾、および ColorWheel 要素弾薬のピクセルパーフェクトな接触チェックです。
- Paint Trek: 回転円衝突、連続レイキャスティング、掃引体積ミサイル防衛システムを特徴とする、ペースの速いスペース シューティング ゲームです。
結論と次のステップ
MonoGame を使用すると、ゲームのニーズに完全に一致する衝突検出を設計できます。高速 AABB テストの背後で高価な Pixel-Perfect チェックをゲートし、円の 2 乗距離を利用し、再利用可能なスクラッチ バッファによるガベージ コレクションを排除することで、デスクトップとモバイル プラットフォームの両方でコンソール並みのスムーズな 60/120 FPS パフォーマンスを実現できます。
アプリ ストアでゲームをチェックして、これらの衝突システムが実際に動作していることを確認し、今すぐこれらのパターンを独自の MonoGame プロジェクトに実装してください。
ストアのリンクとリソース
- ブロックされました: Google Play の Pixel Panzer: Android 用ダウンロード
- ブロックされました: Microsoft Store の Pixel Panzer: Windows 用ダウンロード
- MonoGame フレームワーク: 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