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

Unity中生成超过3个敌人后FPS掉帧问题排查与优化咨询

Unity 敌人数量超3个时卡顿掉帧问题修复

核心问题原因

卡顿是多个代码问题叠加导致的,按影响程度排序如下:

  • 会直接触发性能爆炸的逻辑错误
    1. 零向量归一化产生非法值,导致物理引擎计算异常
      追击逻辑中,当敌人和玩家坐标完全重合时,direction变量为零向量Vector2.zero,直接调用Normalize()会生成NaN(非数字)值,后续给刚体的angularVelocity、velocity赋值NaN后,2D物理引擎的迭代求解器无法处理非法值,会出现计算无法收敛的问题,直接导致掉帧卡顿。
    2. 协程递归写法存在溢出风险
      当前通过递归启动新协程实现循环生成的写法非常脆弱,一旦TimeStep参数被意外设为0或负数,WaitForSeconds会直接跳过等待,瞬间生成海量敌人,直接把帧率压到个位数。
  • 带来不必要开销的冗余逻辑
    1. 每个敌人生成时重复执行全局查找
      每个敌人在Start()里都调用FindObjectOfType<Player>()和FindGameObjectWithTag("Player")两个遍历全场景的查找API,敌人数量上来后会产生累积开销。
    2. 生命周期计时逻辑不规范
      销毁计时放在FixedUpdate()中执行,还使用Time.deltaTime累加,额外占用物理帧的执行时间,逻辑上也容易出现计时不准的问题。
    3. 未做碰撞层过滤
      没有配置碰撞矩阵的情况下,敌人和敌人之间、敌人和场景无关碰撞体之间会持续产生无意义的物理碰撞计算,白白消耗性能。
    4. 频繁创建销毁对象触发GC
      反复调用Instantiate/Destroy会产生垃圾回收开销,内存碎片积累到一定程度会触发帧时间突增。

修复方案

1. 修复核心逻辑错误

首先替换敌人生成脚本,把递归协程改成内部循环,同时缓存等待对象减少GC,生成敌人时直接传入玩家引用,避免敌人自己做全局查找:

using System.Collections;
using UnityEngine;

public class SpawnEnemy : MonoBehaviour
{
   public GameObject[] enemy;
   public Transform[] spawnPoint;
   public float spawnInterval = 3f;
   private WaitForSeconds _spawnWait;

   private void Start()
   {
      _spawnWait = new WaitForSeconds(spawnInterval);
      StartCoroutine(SpawnLoop());
   }

   IEnumerator SpawnLoop()
   {
      while (true)
      {
         yield return _spawnWait;
         int randEnemyId = Random.Range(0, enemy.Length);
         int randSpawnId = Random.Range(0, spawnPoint.Length);
         GameObject newEnemy = Instantiate(enemy[randEnemyId], spawnPoint[randSpawnId].position, Quaternion.identity);
         // 直接传玩家引用,省去敌人内部查找开销
         newEnemy.GetComponent<EnemyTypeOne>().Init(Player.Instance.transform);
      }
   }
}

再替换敌人行为脚本,修复零向量归一化问题,把计时逻辑移到Update中,去掉冗余查找:

using UnityEngine;
using UnityEngine.SceneManagement;

[RequireComponent(typeof(Rigidbody2D))]
public class EnemyTypeOne : MonoBehaviour
{
   public float speed = 5f;
   public float rotateSpeed = 200f;
   public float lifeTime = 10f;
   private Rigidbody2D rb;
   private Transform _target;
   private Player _player;
   private float _lifeTimer;

   // 初始化时直接接收玩家引用
   public void Init(Transform playerTarget)
   {
      _target = playerTarget;
      _player = _target.GetComponent<Player>();
      rb = GetComponent<Rigidbody2D>();
      // 强制关闭重力,避免无意义的物理计算
      rb.gravityScale = 0f;
      _lifeTimer = 0f;
   }

   private void Update()
   {
      // 生命周期计时放在Update中,不占用物理帧时间
      _lifeTimer += Time.deltaTime;
      if (_lifeTimer >= lifeTime)
      {
         Destroy(gameObject);
      }
   }

   private void FixedUpdate()
   {
      Vector2 direction = (Vector2)_target.position - rb.position;
      // 距离过近时跳过旋转计算,避免零向量归一化产生NaN
      if (direction.sqrMagnitude < 0.01f)
      {
         rb.velocity = Vector2.zero;
         rb.angularVelocity = 0f;
         return;
      }
      direction.Normalize();
      float rotateAmount = Vector3.Cross(direction, transform.right).z;
      rb.angularVelocity = -rotateAmount * rotateSpeed;
      rb.velocity = transform.right * speed;
   }

   private void OnTriggerEnter2D(Collider2D collision)
   {
      if (collision.CompareTag("Player"))
      { 
         Destroy(gameObject);
         if (_player.myHealth > 5)
         {
            _player.myHealth -= 5f;
         }
         else
         {
            SceneManager.LoadScene(0);                     
         }
      }
   }
}

最后给Player脚本加全局单例,方便直接获取引用,把下面代码加到Player类里即可,原有逻辑不用改:

public class Player : MonoBehaviour
{
   public static Player Instance;
   public float myHealth;

   private void Awake()
   {
      Instance = this;
   }
   // 原有Player逻辑保持不变
}

2. 编辑器配置优化

  • 打开Edit > Project Settings > Physics2D,在Layer Collision Matrix中取消Enemy层和Enemy层的碰撞勾选,让敌人之间不产生碰撞检测,记得把所有敌人预制体的Layer设为Enemy。
  • 把敌人的Rigidbody2D的Collision Detection模式设为Discrete,除非遇到高速穿透问题否则不要用Continuous模式,前者性能开销低很多。
  • 再次确认所有敌人预制体的Rigidbody2D重力缩放参数为0,避免无意义的重力计算。

3. 进阶优化

如果后续需要提升同屏敌人数量上限,将当前的Instantiate/Destroy逻辑替换为对象池实现:提前生成一批敌人对象缓存在池中,需要生成时从池里取可用对象,销毁时把对象放回池中复用,避免反复创建销毁对象带来的GC和内存碎片问题。

补充说明:Unity编辑器本身运行就带有调试刷新、Gizmos绘制等额外开销,修复上述问题后如果编辑器下帧率稳定,编译出包后的运行帧率会更高。

内容的提问来源于stack exchange,提问作者George Zyn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 09:18:19