在unity中,如何把敌人和玩家之间的碰撞检测的性能优化一下

Unity性能优化:用对象注册模式取代高频碰撞检测,性能飙升!

一个经典思路转变,带来性能的显著提升。

写在前面

大家好,我是一名热爱游戏开发的开发者。在开发过程中,我遇到了一个经典问题:大量游戏单位每帧进行碰撞检测导致CPU开销巨大。在苦苦思索后,我找到了一个优化方案,效果显著,特此记录分享。本人尚在成长中,如有不足之处,恳请各位大佬指正,共同进步!

问题背景:昂贵的碰撞检测

相信大家都遇到过类似场景:

  • 敌人需要追踪玩家
  • 玩家进入范围触发事件
  • 子弹检测是否命中目标

我们第一反应往往是使用Unity自带的OnTriggerEnterOnTriggerStay等碰撞检测方法。这很直观,但当一个场景中存在数十上百个对象每帧都在进行这种检测时,性能瓶颈就出现了

Unity的物理系统非常强大,但也很重量级。每帧处理碰撞体的形状计算、层级过滤、回调触发等,其开销远大于我们想象中的"简单判断"。

核心思路:从物理检测到数学计算

我的优化思路其实很简单:

既然碰撞检测的本质是判断"距离",那我们为什么不直接计算距离,而要绕道物理系统呢?

直接计算 Vector3.Distance 的消耗,可比完整的碰撞检测流程要小得多的多!

解决方案:对象注册模式 + 管理器查询

基于这个思路,我设计了一套 "对象注册模式" 的解决方案。

1. 核心架构

首先,我们需要一个管理中心(GameManager 单例)来登记场景中所有需要被查询的单位。

csharp 复制代码
public class GameManager : MonoBehaviour
{
    public static GameManager Instance;
    
    private List<HumanoidUnit> allUnits = new List<HumanoidUnit>();

    private void Awake()
    {
        if (Instance == null) Instance = this;
        else Destroy(gameObject);
    }

    // 注册单位
    public void RegisterUnit(HumanoidUnit unit)
    {
        if (!allUnits.Contains(unit))
        {
            allUnits.Add(unit);
        }
    }

    // 注销单位
    public void UnRegisterUnit(HumanoidUnit unit)
    {
        allUnits.Remove(unit);
    }
}

2. 单位基类设计

所有需要被追踪的单位都继承自同一个基类:

csharp 复制代码
public class HumanoidUnit : MonoBehaviour
{
    private void Start()
    {
        // 出生时自动注册
        GameManager.Instance?.RegisterUnit(this);
    }

    private void OnDestroy()
    {
        // 销毁时自动清理
        GameManager.Instance?.UnRegisterUnit(this);
    }
}

3. 具体单位实现

Player玩家类:

csharp 复制代码
public class Player : HumanoidUnit
{
    // 玩家相关逻辑
}

Enemy敌人类:

csharp 复制代码
public class Enemy : HumanoidUnit
{
    public float searchRadius = 10f;
    private Player currentTarget;

    private void Update()
    {
        FindClosestPlayer();
        
        if (currentTarget != null)
        {
            // 追踪逻辑
        }
    }

    private void FindClosestPlayer()
    {
        float nearestDistance = float.MaxValue;
        Player nearestPlayer = null;

        foreach (var unit in GameManager.Instance.AllUnits)
        {
            if (unit is Player player)
            {
                float distance = Vector3.Distance(transform.position, player.transform.position);
                if (distance < searchRadius && distance < nearestDistance)
                {
                    nearestDistance = distance;
                    nearestPlayer = player;
                }
            }
        }
        
        currentTarget = nearestPlayer;
    }
}

性能优化进阶技巧

技巧1:使用平方距离替代实际距离

这是一个经典优化点:避免使用耗时的开平方计算

csharp 复制代码
// 优化前
float distance = Vector3.Distance(positionA, positionB);
if (distance < searchRadius)

// 优化后
float sqrDistance = (positionA - positionB).sqrMagnitude;
float sqrSearchRadius = searchRadius * searchRadius;
if (sqrDistance < sqrSearchRadius)

技巧2:控制查询频率

不是每个敌人都需要每帧查询目标:

csharp 复制代码
private float searchTimer;
private const float SEARCH_INTERVAL = 0.3f; // 每0.3秒查询一次

private void Update()
{
    searchTimer += Time.deltaTime;
    if (searchTimer >= SEARCH_INTERVAL)
    {
        searchTimer = 0;
        FindClosestPlayer();
    }
}

技巧3:分帧处理(应对大量单位)

当敌人数量极多时,可以将查询任务分摊到多帧中完成:

csharp 复制代码
// 在GameManager中实现分帧查询
public class GameManager : MonoBehaviour
{
    private int currentFrameIndex = 0;
    
    private void Update()
    {
        // 每帧只更新一部分敌人的逻辑
        int unitsPerFrame = Mathf.CeilToInt(allUnits.Count / 5f); // 分5帧处理完
        
        for (int i = 0; i < unitsPerFrame; i++)
        {
            if (currentFrameIndex >= allUnits.Count) currentFrameIndex = 0;
            
            var unit = allUnits[currentFrameIndex];
            // 处理该单位的逻辑...
            
            currentFrameIndex++;
        }
    }
}

方案优势总结

  1. 性能大幅提升:从物理系统的重量级计算转变为轻量级的距离计算
  2. 代码可控性强:可以灵活控制查询频率、条件过滤等
  3. 架构清晰可扩展:易于添加新的单位类型和查询条件
  4. 内存友好:避免了碰撞回调产生的临时内存分配

适用场景

  • 大量NPC的索敌系统
  • 玩家感知系统(如潜行游戏)
  • 技能范围检测
  • 事件触发区域管理

结语

这个方案让我深刻体会到:有时候,最好的优化不是让现有方案跑得更快,而是换一条更近的路

从依赖引擎的通用系统,转向为自己游戏量身定制的专用系统,这种思路转变带来的性能提升往往是质的飞跃。

希望这个方案对大家有所启发!如果你有更好的想法或建议,欢迎在评论区交流讨论~


后续优化方向

如果项目规模继续扩大,还可以考虑:

  • 空间分区:使用四叉树/八叉树进一步优化大规模单位的查询
  • Job System + Burst Compiler:利用Unity的多线程和编译优化处理超大规模单位
相关推荐
陈尕六1 天前
从零开始的 Godot 之旅 — EP10:有限状态机(二)
godot·游戏开发
祭璃妖2 天前
关于2D人物冒险游戏闪现逻辑的实现方法
游戏开发
专注VB编程开发20年6 天前
游戏开发入门,简单小游戏原理-关于2D渲染的一些小想法
小游戏·游戏开发
陈尕六8 天前
从零开始的 Godot 之旅 — EP9:有限状态机(一)
godot·游戏开发
陈尕六15 天前
从零开始的 Godot 之旅 — EP8:角色移动和动画切换
godot·游戏开发
Setsuna_F_Seiei16 天前
CocosCreator 游戏开发 - 利用 AssetsBundle 技术对小游戏包体积进行优化
前端·cocos creator·游戏开发