Unity 大量怪物和大量子弹检测碰撞优化


image.png


📝 核心代码实现

1. 空间哈希网格系统

public class SpatialHashGrid : MonoBehaviour
{
    private Dictionary<Vector2Int, HashSet<Collider2D>> grid;
    private float cellSize = 5f; // 根据游戏对象大小调整
    private int gridWidth = 200;
    private int gridHeight = 200;

    void Awake()
    {
        grid = new Dictionary<Vector2Int, HashSet<Collider2D>>();
        InitializeGrid();
    }

    private void InitializeGrid()
    {
        for (int x = 0; x < gridWidth; x++)
        {
            for (int y = 0; y < gridHeight; y++)
            {
                grid[new Vector2Int(x, y)] = new HashSet<Collider2D>();
            }
        }
    }

    public Vector2Int WorldToGrid(Vector3 worldPos)
    {
        int x = Mathf.FloorToInt(worldPos.x / cellSize);
        int y = Mathf.FloorToInt(worldPos.y / cellSize);
        return new Vector2Int(
            Mathf.Clamp(x, 0, gridWidth - 1),
            Mathf.Clamp(y, 0, gridHeight - 1)
        );
    }

    public void AddToGrid(Collider2D collider)
    {
        Vector2Int gridPos = WorldToGrid(collider.transform.position);
        if (grid.ContainsKey(gridPos))
        {
            grid[gridPos].Add(collider);
        }
    }

    public void RemoveFromGrid(Collider2D collider, Vector2Int oldGridPos)
    {
        if (grid.ContainsKey(oldGridPos))
        {
            grid[oldGridPos].Remove(collider);
        }
    }

    public HashSet<Collider2D> GetNearbyObjects(Vector3 position, int range = 1)
    {
        HashSet<Collider2D> nearby = new HashSet<Collider2D>();
        Vector2Int centerGrid = WorldToGrid(position);

        for (int x = -range; x <= range; x++)
        {
            for (int y = -range; y <= range; y++)
            {
                Vector2Int checkGrid = new Vector2Int(centerGrid.x + x, centerGrid.y + y);
                if (grid.ContainsKey(checkGrid))
                {
                    foreach (var obj in grid[checkGrid])
                    {
                        nearby.Add(obj);
                    }
                }
            }
        }
        return nearby;
    }
}

2. 高效碰撞管理器

public class CollisionManager : MonoBehaviour
{
    [Header("性能设置")]
    public int maxCollisionChecksPerFrame = 100;
    public float maxCheckDistance = 20f;

    private SpatialHashGrid spatialGrid;
    private ObjectPoolManager poolManager;
    private Queue<CollisionPair> collisionQueue;
    private List<Bullet> activeBullets;
    private List<Monster> activeMonsters;

    // 性能统计
    public int CollisionChecksThisFrame { get; private set; }
    public int TotalCollisionsDetected { get; private set; }

    private struct CollisionPair
    {
        public Collider2D bullet;
        public Collider2D monster;
        public float priority; // 基于距离的优先级
    }

    void Awake()
    {
        spatialGrid = GetComponent<SpatialHashGrid>();
        poolManager = GetComponent<ObjectPoolManager>();
        collisionQueue = new Queue<CollisionPair>();
        activeBullets = new List<Bullet>();
        activeMonsters = new List<Monster>();

        // 设置物理层
        SetupPhysicsLayers();
    }

    void SetupPhysicsLayers()
    {
        // 子弹层 = 8, 怪物层 = 9
        Physics2D.IgnoreLayerCollision(8, 8); // 子弹间不碰撞
        Physics2D.IgnoreLayerCollision(9, 9); // 怪物间不碰撞
        // 只保留子弹-怪物碰撞
    }

    void FixedUpdate()
    {
        CollisionChecksThisFrame = 0;
        UpdateSpatialGrid();
        ProcessCollisionDetection();
    }

    private void UpdateSpatialGrid()
    {
        // 清空网格
        spatialGrid.ClearGrid();

        // 重新添加所有活跃对象
        foreach (var bullet in activeBullets)
        {
            if (bullet.gameObject.activeInHierarchy)
                spatialGrid.AddToGrid(bullet.GetComponent<Collider2D>());
        }

        foreach (var monster in activeMonsters)
        {
            if (monster.gameObject.activeInHierarchy)
                spatialGrid.AddToGrid(monster.GetComponent<Collider2D>());
        }
    }

    private void ProcessCollisionDetection()
    {
        // 构建碰撞候选队列
        BuildCollisionCandidates();

        // 分帧处理碰撞检测
        int checksThisFrame = 0;
        while (collisionQueue.Count > 0 && checksThisFrame < maxCollisionChecksPerFrame)
        {
            var pair = collisionQueue.Dequeue();
            if (CheckCollision(pair))
            {
                HandleCollision(pair.bullet, pair.monster);
                TotalCollisionsDetected++;
            }
            checksThisFrame++;
        }

        CollisionChecksThisFrame = checksThisFrame;
    }

    private void BuildCollisionCandidates()
    {
        collisionQueue.Clear();

        foreach (var bullet in activeBullets)
        {
            if (!bullet.gameObject.activeInHierarchy) continue;

            var nearbyObjects = spatialGrid.GetNearbyObjects(bullet.transform.position);

            foreach (var obj in nearbyObjects)
            {
                if (obj.gameObject.layer == 9) // 怪物层
                {
                    float distance = Vector3.Distance(bullet.transform.position, obj.transform.position);
                    if (distance <= maxCheckDistance)
                    {
                        collisionQueue.Enqueue(new CollisionPair
                        {
                            bullet = bullet.GetComponent<Collider2D>(),
                            monster = obj,
                            priority = 1f / (distance + 0.1f) // 距离越近优先级越高
                        });
                    }
                }
            }
        }

        // 按优先级排序(可选,性能消耗较大时可省略)
        // var sortedPairs = collisionQueue.OrderByDescending(p => p.priority);
        // collisionQueue = new Queue<CollisionPair>(sortedPairs);
    }

    private bool CheckCollision(CollisionPair pair)
    {
        if (pair.bullet == null || pair.monster == null) return false;

        // 快速距离检测
        float sqrDistance = (pair.bullet.transform.position - pair.monster.transform.position).sqrMagnitude;
        float combinedRadius = GetColliderRadius(pair.bullet) + GetColliderRadius(pair.monster);

        return sqrDistance <= (combinedRadius * combinedRadius);
    }

    private float GetColliderRadius(Collider2D collider)
    {
        if (collider is CircleCollider2D circle)
            return circle.radius * Mathf.Max(collider.transform.lossyScale.x, collider.transform.lossyScale.y);
        else if (collider is BoxCollider2D box)
            return Mathf.Max(box.size.x, box.size.y) * 0.5f * Mathf.Max(collider.transform.lossyScale.x, collider.transform.lossyScale.y);

        return 1f; // 默认半径
    }

    private void HandleCollision(Collider2D bullet, Collider2D monster)
    {
        // 处理碰撞逻辑
        var bulletComponent = bullet.GetComponent<Bullet>();
        var monsterComponent = monster.GetComponent<Monster>();

        if (bulletComponent != null && monsterComponent != null)
        {
            // 造成伤害
            monsterComponent.TakeDamage(bulletComponent.Damage);

            // 回收子弹到对象池
            poolManager.ReturnBullet(bulletComponent);

            // 播放特效
            var effect = poolManager.GetHitEffect();
            effect.transform.position = bullet.transform.position;
            effect.Play();
        }
    }

    // 注册/注销方法
    public void RegisterBullet(Bullet bullet)
    {
        activeBullets.Add(bullet);
    }

    public void UnregisterBullet(Bullet bullet)
    {
        activeBullets.Remove(bullet);
    }

    public void RegisterMonster(Monster monster)
    {
        activeMonsters.Add(monster);
    }

    public void UnregisterMonster(Monster monster)
    {
        activeMonsters.Remove(monster);
    }
}

3. 智能对象池系统

public class ObjectPoolManager : MonoBehaviour
{
    [System.Serializable]
    public class PoolSettings
    {
        public GameObject prefab;
        public int initialSize = 50;
        public int maxSize = 200;
        public bool allowGrowth = true;
    }

    [Header("对象池配置")]
    public PoolSettings bulletPool;
    public PoolSettings hitEffectPool;
    public PoolSettings damageTextPool;

    private Dictionary<string, Queue<GameObject>> pools;
    private Dictionary<string, PoolSettings> poolSettings;
    private Dictionary<string, int> poolCounts;

    void Awake()
    {
        InitializePools();
    }

    private void InitializePools()
    {
        pools = new Dictionary<string, Queue<GameObject>>();
        poolSettings = new Dictionary<string, PoolSettings>();
        poolCounts = new Dictionary<string, int>();

        // 初始化各个对象池
        CreatePool("Bullet", bulletPool);
        CreatePool("HitEffect", hitEffectPool);
        CreatePool("DamageText", damageTextPool);
    }

    private void CreatePool(string poolName, PoolSettings settings)
    {
        pools[poolName] = new Queue<GameObject>();
        poolSettings[poolName] = settings;
        poolCounts[poolName] = 0;

        // 预创建对象
        for (int i = 0; i < settings.initialSize; i++)
        {
            GameObject obj = Instantiate(settings.prefab, transform);
            obj.SetActive(false);
            pools[poolName].Enqueue(obj);
            poolCounts[poolName]++;
        }
    }

    public GameObject GetFromPool(string poolName)
    {
        if (!pools.ContainsKey(poolName))
        {
            Debug.LogError($"Pool {poolName} does not exist!");
            return null;
        }

        GameObject obj = null;
        var pool = pools[poolName];
        var settings = poolSettings[poolName];

        // 尝试从池中获取
        while (pool.Count > 0)
        {
            obj = pool.Dequeue();
            if (obj != null)
            {
                obj.SetActive(true);
                return obj;
            }
        }

        // 池为空,检查是否可以扩容
        if (settings.allowGrowth && poolCounts[poolName] < settings.maxSize)
        {
            obj = Instantiate(settings.prefab, transform);
            poolCounts[poolName]++;
            obj.SetActive(true);
            return obj;
        }

        Debug.LogWarning($"Pool {poolName} is exhausted!");
        return null;
    }

    public void ReturnToPool(string poolName, GameObject obj)
    {
        if (!pools.ContainsKey(poolName))
        {
            Destroy(obj);
            return;
        }

        obj.SetActive(false);
        obj.transform.SetParent(transform);
        pools[poolName].Enqueue(obj);
    }

    // 便捷方法
    public Bullet GetBullet()
    {
        var obj = GetFromPool("Bullet");
        return obj?.GetComponent<Bullet>();
    }

    public void ReturnBullet(Bullet bullet)
    {
        ReturnToPool("Bullet", bullet.gameObject);
    }

    public ParticleSystem GetHitEffect()
    {
        var obj = GetFromPool("HitEffect");
        return obj?.GetComponent<ParticleSystem>();
    }
}

4. 性能监控面板

public class PerformanceMonitor : MonoBehaviour
{
    private CollisionManager collisionManager;
    private ObjectPoolManager poolManager;

    [Header("显示设置")]
    public bool showDebugInfo = true;
    public KeyCode toggleKey = KeyCode.F1;

    // 性能数据
    private float deltaTime;
    private int lastFrameCollisionChecks;

    void Start()
    {
        collisionManager = FindObjectOfType<CollisionManager>();
        poolManager = FindObjectOfType<ObjectPoolManager>();
    }

    void Update()
    {
        deltaTime += (Time.unscaledDeltaTime - deltaTime) * 0.1f;

        if (Input.GetKeyDown(toggleKey))
        {
            showDebugInfo = !showDebugInfo;
        }

        lastFrameCollisionChecks = collisionManager.CollisionChecksThisFrame;
    }

    void OnGUI()
    {
        if (!showDebugInfo) return;

        GUILayout.BeginArea(new Rect(10, 10, 300, 200));
        GUILayout.Label($"FPS: {Mathf.Ceil(1.0f / deltaTime)}");
        GUILayout.Label($"碰撞检测/帧: {lastFrameCollisionChecks}");
        GUILayout.Label($"总碰撞数: {collisionManager.TotalCollisionsDetected}");
        GUILayout.Label($"活跃子弹: {FindObjectsOfType<Bullet>().Length}");
        GUILayout.Label($"活跃怪物: {FindObjectsOfType<Monster>().Length}");
        GUILayout.EndArea();
    }
}

⚙️ 使用配置

// 在CollisionManager中的推荐设置
[Header("性能调优")]
public int maxCollisionChecksPerFrame = 50;  // 根据目标帧率调整
public float maxCheckDistance = 15f;         // 根据游戏场景大小调整
public float cellSize = 3f;                  // 根据游戏对象平均大小调整



Unity 开源分享一个轻量路点编辑器插件 常用于对象寻路

Unity 好用的客户端框架推荐

评 论