You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows Forms.NET(C#) 2D俯视角射击游戏300-500敌人碰撞检测与性能优化求助

Windows Forms.NET(C#) 2D俯视角射击游戏300-500敌人碰撞检测与性能优化求助

嘿,我很理解你现在的困扰——几百个敌人的嵌套循环碰撞检测确实会让Windows Forms的性能直接跳水,毕竟每帧要做O(n²)次检测,300个敌人就是4万多次判断,500个更是12万次,不卡才怪。下面我给你分享几个从易到难的优化方案,先从不用大改代码的小技巧开始,再到中等复杂度的空间分区,最后是整体性能的调优建议:

一、快速见效的小优化(不用改核心结构)

这些方法能立刻降低计算量,而且代码改动极小:

  1. 降低碰撞检测频率
    敌人的移动速度不会快到几帧就互相穿模,完全不用每帧都检测碰撞。比如加个计数器,每2-3帧才执行一次calculateEnemyCollision():

    private int collisionCheckCounter = 0;
    private void tmrMovement_Tick(object sender, EventArgs e) {
        // ... 其他逻辑 ...
        collisionCheckCounter++;
        if (collisionCheckCounter % 2 == 0) { // 每2帧检测一次
            calculateEnemyCollision();
        }
        // ... 其他逻辑 ...
    }
    

    直接把碰撞计算量砍半,效果立竿见影。

  2. 提前过滤不可能碰撞的敌人
    在调用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()调用,毕竟大部分敌人之间距离都很远。

  3. 优化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)更新网格位置。

三、整体性能调优(进一步提升帧率)

除了碰撞检测,这些细节也能帮你提升整体流畅度:

  1. 对象池复用子弹/敌人
    避免频繁创建销毁对象导致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;
            }
            // 子弹命中逻辑...
        }
    }
    
  2. 优化绘制性能

    • 把Image转成Bitmap再绘制,Bitmap的绘制速度更快;
    • 只绘制屏幕内的敌人/子弹,超出边界的直接跳过;
    • 关闭不必要的图形效果,比如抗锯齿:
      private void Form1_Paint(object sender, PaintEventArgs e) {
          e.Graphics.SmoothingMode = SmoothingMode.None;
          e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
          // 绘制逻辑...
      }
      
  3. 后台线程处理碰撞检测
    如果碰撞检测还是太耗时,可以把它放到后台线程执行,计算出碰撞结果后再回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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 11:33:00