Windows Forms.NET(C#) 2D俯视角射击游戏300-500敌人碰撞检测与性能优化求助
Windows Forms.NET(C#) 2D俯视角射击游戏300-500敌人碰撞检测与性能优化求助
嘿,我很理解你现在的困扰——几百个敌人的嵌套循环碰撞检测确实会让Windows Forms的性能直接跳水,毕竟每帧要做O(n²)次检测,300个敌人就是4万多次判断,500个更是12万次,不卡才怪。下面我给你分享几个从易到难的优化方案,先从不用大改代码的小技巧开始,再到中等复杂度的空间分区,最后是整体性能的调优建议:
一、快速见效的小优化(不用改核心结构)
这些方法能立刻降低计算量,而且代码改动极小:
降低碰撞检测频率
敌人的移动速度不会快到几帧就互相穿模,完全不用每帧都检测碰撞。比如加个计数器,每2-3帧才执行一次calculateEnemyCollision():private int collisionCheckCounter = 0; private void tmrMovement_Tick(object sender, EventArgs e) { // ... 其他逻辑 ... collisionCheckCounter++; if (collisionCheckCounter % 2 == 0) { // 每2帧检测一次 calculateEnemyCollision(); } // ... 其他逻辑 ... }直接把碰撞计算量砍半,效果立竿见影。
提前过滤不可能碰撞的敌人
在调用IntersectsWith()前,先通过距离平方(避免开根号的开销)过滤掉离得远的敌人:private void calculateEnemyCollision() { int enemyCount = enemies.Count; // 缓存Count,避免循环中反复访问属性 for (int i = 0; i < enemyCount; i++) { Enemy enemy1 = enemies[i]; for (int j = i + 1; j < enemyCount; j++) { Enemy enemy2 = enemies[j]; // 计算距离平方(比开根号快很多) float distanceSq = (enemy1.colliderPlr.X - enemy2.colliderPlr.X) * (enemy1.colliderPlr.X - enemy2.colliderPlr.X) + (enemy1.colliderPlr.Y - enemy2.colliderPlr.Y) * (enemy1.colliderPlr.Y - enemy2.colliderPlr.Y); // 计算两个敌人半径之和的平方 float combinedRadiusSq = (enemy1.colliderPlr.Width/2 + enemy2.colliderPlr.Width/2) * (enemy1.colliderPlr.Width/2 + enemy2.colliderPlr.Width/2); // 距离太远直接跳过 if (distanceSq > combinedRadiusSq) continue; // 再执行精确碰撞检测 if (enemy1.colliderPlr.IntersectsWith(enemy2.colliderPlr)) { // 处理碰撞逻辑 } } } }这能减少大量不必要的
IntersectsWith()调用,毕竟大部分敌人之间距离都很远。优化List遍历与初始化
- 初始化
enemies时直接指定容量:new List<Enemy>(500),避免List频繁扩容带来的内存拷贝开销; - 用索引式
for循环代替foreach遍历大集合,索引访问比枚举器更快。
- 初始化
二、中等复杂度优化(网格分区,比四叉树简单)
如果小优化不够,试试把屏幕分成网格,让每个敌人只和同网格/相邻网格的敌人检测碰撞,这能把O(n²)降到接近O(n):
private Dictionary<Tuple<int, int>, List<Enemy>> enemyGrid = new Dictionary<Tuple<int, int>, List<Enemy>>(); private int gridSize = 128; // 格子大小,根据敌人尺寸调整,比如128x128像素 // 敌人移动后更新它所在的网格 private void UpdateEnemyGrid(Enemy enemy) { var oldCell = Tuple.Create((int)(enemy.colliderPlr.X / gridSize), (int)(enemy.colliderPlr.Y / gridSize)); if (enemyGrid.ContainsKey(oldCell) && enemyGrid[oldCell].Contains(enemy)) { enemyGrid[oldCell].Remove(enemy); if (enemyGrid[oldCell].Count == 0) enemyGrid.Remove(oldCell); } var newCell = Tuple.Create((int)(enemy.colliderPlr.X / gridSize), (int)(enemy.colliderPlr.Y / gridSize)); if (!enemyGrid.ContainsKey(newCell)) enemyGrid[newCell] = new List<Enemy>(); enemyGrid[newCell].Add(enemy); } // 优化后的碰撞检测 private void calculateEnemyCollision() { foreach (var cell in enemyGrid.Keys.ToList()) { var currentEnemies = enemyGrid[cell]; // 检测当前格子内的敌人 for (int i = 0; i < currentEnemies.Count; i++) { for (int j = i + 1; j < currentEnemies.Count; j++) { CheckCollision(currentEnemies[i], currentEnemies[j]); } } // 检测相邻8个格子的敌人 var adjacentCells = new List<Tuple<int, int>> { Tuple.Create(cell.Item1+1, cell.Item2), Tuple.Create(cell.Item1-1, cell.Item2), Tuple.Create(cell.Item1, cell.Item2+1), Tuple.Create(cell.Item1, cell.Item2-1), Tuple.Create(cell.Item1+1, cell.Item2+1), Tuple.Create(cell.Item1-1, cell.Item2-1), Tuple.Create(cell.Item1+1, cell.Item2-1), Tuple.Create(cell.Item1-1, cell.Item2+1) }; foreach (var adjCell in adjacentCells) { if (enemyGrid.ContainsKey(adjCell)) { var adjEnemies = enemyGrid[adjCell]; foreach (var e1 in currentEnemies) { foreach (var e2 in adjEnemies) { // 避免重复检测(比如A在格子1,B在格子2,处理格子2时会再检测一次) if (enemies.IndexOf(e1) < enemies.IndexOf(e2)) { CheckCollision(e1, e2); } } } } } } } // 抽离碰撞逻辑复用 private void CheckCollision(Enemy e1, Enemy e2) { // 先距离过滤,再精确检测 float distanceSq = (e1.colliderPlr.X - e2.colliderPlr.X) * (e1.colliderPlr.X - e2.colliderPlr.X) + (e1.colliderPlr.Y - e2.colliderPlr.Y) * (e1.colliderPlr.Y - e2.colliderPlr.Y); float combinedRadiusSq = (e1.colliderPlr.Width/2 + e2.colliderPlr.Width/2) * (e1.colliderPlr.Width/2 + e2.colliderPlr.Width/2); if (distanceSq > combinedRadiusSq) return; if (e1.colliderPlr.IntersectsWith(e2.colliderPlr)) { // 简单的碰撞处理:推开敌人,避免重叠 Vector2 dir = new Vector2(e2.colliderPlr.X - e1.colliderPlr.X, e2.colliderPlr.Y - e1.colliderPlr.Y); dir = Vector2.Normalize(dir); e1.colliderPlr.Location = new Point((int)(e1.colliderPlr.X - dir.X * 2), (int)(e1.colliderPlr.Y - dir.Y * 2)); e2.colliderPlr.Location = new Point((int)(e2.colliderPlr.X + dir.X * 2), (int)(e2.colliderPlr.Y + dir.Y * 2)); } }
记得在敌人移动后调用UpdateEnemyGrid(enemy)更新网格位置。
三、整体性能调优(进一步提升帧率)
除了碰撞检测,这些细节也能帮你提升整体流畅度:
对象池复用子弹/敌人
避免频繁创建销毁对象导致GC卡顿,比如给子弹做个对象池:private Stack<Bullet> bulletPool = new Stack<Bullet>(); private void createBullet() { Bullet bullet; if (bulletPool.Count > 0) { bullet = bulletPool.Pop(); // 重置子弹属性 bullet.colliderBullet.Location = plr.colliderPlr.Location; bullet.velocity = plr.getBulletVelocity(); bullet.isActive = true; } else { bullet = new Bullet(new Size(5,5), plr.colliderPlr.Location); bullet.velocity = plr.getBulletVelocity(); bullets.Add(bullet); } } // 子弹失效后放回池 private void checkBulletCollision() { for (int i = bullets.Count - 1; i >= 0; i--) { Bullet bullet = bullets[i]; if (!bullet.isActive || bullet.colliderBullet.X < 0 || bullet.colliderBullet.X > boundX || bullet.colliderBullet.Y < 0 || bullet.colliderBullet.Y > boundY) { bullets.RemoveAt(i); bulletPool.Push(bullet); continue; } // 子弹命中逻辑... } }优化绘制性能
- 把
Image转成Bitmap再绘制,Bitmap的绘制速度更快; - 只绘制屏幕内的敌人/子弹,超出边界的直接跳过;
- 关闭不必要的图形效果,比如抗锯齿:
private void Form1_Paint(object sender, PaintEventArgs e) { e.Graphics.SmoothingMode = SmoothingMode.None; e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor; // 绘制逻辑... }
- 把
后台线程处理碰撞检测
如果碰撞检测还是太耗时,可以把它放到后台线程执行,计算出碰撞结果后再回UI线程应用:private void tmrMovement_Tick(object sender, EventArgs e) { // ... 玩家移动、敌人移动等UI操作 ... // 后台线程处理碰撞 Task.Run(() => { List<Tuple<Enemy, Enemy>> collisions = new List<Tuple<Enemy, Enemy>>(); // 这里执行碰撞检测逻辑,收集碰撞对 // ... // 回UI线程处理结果 this.Invoke((Action)(() => { foreach (var pair in collisions) { // 处理碰撞 } })); }); }
这些方案应该能帮你把几百个敌人的性能拉到可接受的水平,先从简单的小优化试起,不够再逐步升级到网格分区~
内容来源于stack exchange
相关产品推荐
相关产品推荐

