mcpField
U
Unity Performance

Unity Performance

github.com/cryptorabea开发3156 次安装44 周前更新
代码仓库

关于 Unity Performance

Unity Performance 提供 Unity 游戏性能优化指导,面向 Unity 开发者,通过分析瓶颈并采用针对性方法处理 CPU、内存、渲染和物理开销。

主要功能

  1. 01缓存组件与对象引用
  2. 02通过对象池复用对象
  3. 03减少 Update 循环工作
  4. 04降低垃圾回收分配
  5. 05优化物理检测
  6. 06分析瓶颈并比较结果

适用场景

  1. 01减少游戏运行时的帧耗时峰值
  2. 02降低高频代码中的内存分配
  3. 03排查渲染或物理性能瓶颈

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/cryptorabea/claude_unity_dev_plugin --skill unity-performance

安装后在 Claude Code 中输入/unity-performance确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
memory-optimization.md
rendering-optimization.md
profiling-guide.md
physics-optimization.md

详细介绍

Unity 性能优化

涵盖 Unity 游戏的核心性能优化技巧,包括内存管理、CPU 优化、渲染和性能分析策略。

概述

性能对所有平台上的 Unity 游戏都至关重要。性能不佳会表现为帧率低、卡顿、加载时间长和崩溃。本技能介绍适用于所有 Unity 项目的成熟优化技巧。

核心优化领域:

  • CPU 优化(Update 循环、缓存、对象池)
  • 内存管理(减少 GC、分配模式)
  • 渲染优化(批处理、剔除、LOD)
  • 性能分析与测量(识别瓶颈)

引用缓存

最常见的 Unity 性能问题是重复执行开销较大的查找操作。缓存所有引用,避免冗余操作。

GetComponent 缓存

切勿重复调用 GetComponent — 应在 Awake 中缓存结果:

// ❌ BAD - GetComponent every frame
private void Update()
{
    GetComponent<Rigidbody>().velocity = Vector3.forward;  // SLOW!
}

// ✅ GOOD - Cache once
private Rigidbody rb;

private void Awake()
{
    rb = GetComponent<Rigidbody>();
}

private void Update()
{
    rb.velocity = Vector3.forward;  // FAST
}

性能影响:GetComponent 比缓存引用慢 10-100 倍。

Transform 缓存

缓存对 transform 的访问,尤其是频繁访问的 GameObject:

// ❌ BAD - Property access overhead
private void Update()
{
    transform.position += Vector3.forward * Time.deltaTime;
    transform.rotation = Quaternion.identity;
}

// ✅ GOOD - Cache transform reference
private Transform myTransform;

private void Awake()
{
    myTransform = transform;
}

private void Update()
{
    myTransform.position += Vector3.forward * Time.deltaTime;
    myTransform.rotation = Quaternion.identity;
}

原因:transform 属性存在开销。缓存引用可以避免重复查找。

Find 方法缓存

切勿在 Update 中使用 Find 方法 — 应缓存结果:

// ❌ BAD - Find every frame (EXTREMELY SLOW)
private void Update()
{
    GameObject player = GameObject.Find("Player");
    Transform target = GameObject.FindWithTag("Enemy").transform;
}

// ✅ GOOD - Cache in Start
private GameObject player;
private Transform target;

private void Start()
{
    player = GameObject.Find("Player");
    target = GameObject.FindWithTag("Enemy")?.transform;
}

private void Update()
{
    // Use cached references
}

性能影响:Find 方法会扫描整个场景层级。比缓存引用慢 100-1000 倍。

Material 缓存

访问 renderer.material 会创建新的 Material 实例 — 应进行缓存以避免泄漏:

// ❌ BAD - Creates new Material every frame (MEMORY LEAK)
private void Update()
{
    GetComponent<Renderer>().material.color = Color.red;  // Creates new Material!
}

// ✅ GOOD - Cache material reference
private Material material;

private void Awake()
{
    material = GetComponent<Renderer>().material;
}

private void Update()
{
    material.color = Color.red;  // Modifies cached Material
}

private void OnDestroy()
{
    // Clean up instantiated material
    if (material != null)
        Destroy(material);
}

重要:访问 .material 会创建新实例。只读访问时请使用 .sharedMaterial,以避免实例化。

对象池

Instantiate 和 Destroy 的开销很大。应复用对象,而不是反复创建和销毁。

基本对象池实现
public class ObjectPool : MonoBehaviour
{
    [SerializeField] private GameObject prefab;
    [SerializeField] private int initialSize = 10;

    private Queue<GameObject> pool = new Queue<GameObject>();

    private void Awake()
    {
        // Pre-instantiate objects
        for (int i = 0; i < initialSize; i++)
        {
            GameObject obj = Instantiate(prefab);
            obj.SetActive(false);
            pool.Enqueue(obj);
        }
    }

    public GameObject Get()
    {
        if (pool.Count > 0)
        {
            GameObject obj = pool.Dequeue();
            obj.SetActive(true);
            return obj;
        }

        // Pool exhausted - create new
        return Instantiate(prefab);
    }

    public void Return(GameObject obj)
    {
        obj.SetActive(false);
        pool.Enqueue(obj);
    }
}

适用对象:

  • 子弹、投射物
  • 粒子效果
  • UI 元素(工具提示、伤害数字)
  • 波次制游戏中的敌人
  • 音频源

性能提升:比 Instantiate/Destroy 快 10-50 倍,并可消除 GC 峰值。

对象池模式用法
public class BulletSpawner : MonoBehaviour
{
    [SerializeField] private ObjectPool bulletPool;
    [SerializeField] private Transform firePoint;

    private void Fire()
    {
        // Get from pool
        GameObject bullet = bulletPool.Get();
        bullet.transform.position = firePoint.position;
        bullet.transform.rotation = firePoint.rotation;

        // Return after 3 seconds
        StartCoroutine(ReturnToPoolAfterDelay(bullet, 3f));
    }

    private IEnumerator ReturnToPoolAfterDelay(GameObject obj, float delay)
    {
        yield return new WaitForSeconds(delay);
        bulletPool.Return(obj);
    }
}

Update 循环优化

Update、FixedUpdate 和 LateUpdate 会频繁调用 — 应尽量减少这些方法中的工作。

移除空的 Update 方法
// ❌ BAD - Empty methods still have overhead
private void Update() { }
private void FixedUpdate() { }

// ✅ GOOD - Remove unused methods
// (No Update/FixedUpdate if not needed)

性能影响:即使 Update 方法为空,Unity 也会调用它们。移除这些方法可减少开销。

降低 Update 频率

并非所有逻辑都需要每帧运行:

// ❌ BAD - Expensive check every frame
private void Update()
{
    CheckForNearbyEnemies();  // Expensive raycast/distance checks
}

// ✅ GOOD - Check every N frames
private int frameCounter = 0;
private const int checkInterval = 10;

private void Update()
{
    frameCounter++;
    if (frameCounter >= checkInterval)
    {
        frameCounter = 0;
        CheckForNearbyEnemies();
    }
}

// ✅ BETTER - Use InvokeRepeating or Coroutine
private void Start()
{
    InvokeRepeating(nameof(CheckForNearbyEnemies), 0f, 0.2f);  // Every 0.2 seconds
}

替代方案:协程

private void Start()
{
    StartCoroutine(CheckEnemiesRoutine());
}

private IEnumerator CheckEnemiesRoutine()
{
    while (true)
    {
        CheckForNearbyEnemies();
        yield return new WaitForSeconds(0.2f);
    }
}
事件驱动架构

用事件替代轮询:

// ❌ BAD - Poll for state change every frame
private bool wasGrounded;

private void Update()
{
    bool grounded = IsGrounded();
    if (grounded != wasGrounded)
    {
        OnGroundedChanged(grounded);
    }
    wasGrounded = grounded;
}

// ✅ GOOD - Event-driven
public event Action<bool> OnGroundedChanged;

private bool isGrounded;

private void SetGrounded(bool grounded)
{
    if (isGrounded != grounded)
    {
        isGrounded = grounded;
        OnGroundedChanged?.Invoke(grounded);
    }
}

减少垃圾回收

避免在频繁调用的方法中进行内存分配,以防止 GC 峰值。

字符串拼接
// ❌ BAD - Allocates strings every frame
private void Update()
{
    string message = "Health: " + health;  // String allocation
    scoreText.text = "Score: " + score;    // String allocation
}

// ✅ GOOD - Use StringBuilder or string interpolation
private StringBuilder sb = new StringBuilder();

private void UpdateUI()
{
    sb.Clear();
    sb.Append("Health: ").Append(health);
    healthText.text = sb.ToString();
}

// ✅ ALTERNATIVE - Cache formatted strings
private void UpdateHealth(int newHealth)
{
    health = newHealth;
    healthText.text = health.ToString();  // Less allocation than concatenation
}
集合分配
// ❌ BAD - Allocates new list every frame
private void Update()
{
    List<Enemy> nearbyEnemies = new List<Enemy>();  // GC allocation!
    FindNearbyEnemies(nearbyEnemies);
}

// ✅ GOOD - Reuse list
private List<Enemy> nearbyEnemies = new List<Enemy>();

private void Update()
{
    nearbyEnemies.Clear();  // Reuse existing list
    FindNearbyEnemies(nearbyEnemies);
}
数组/List 最佳实践
// ❌ BAD - ToArray allocates
private void Update()
{
    GameObject[] enemies = enemyList.ToArray();  // GC allocation!
}

// ✅ GOOD - Iterate list directly
private void Update()
{
    for (int i = 0; i < enemyList.Count; i++)
    {
        Enemy enemy = enemyList[i];
        // Process enemy
    }
}

// ✅ GOOD - Use foreach (no allocation for List)
private void Update()
{
    foreach (var enemy in enemyList)
    {
        // Process enemy
    }
}
协程分配
// ❌ BAD - Allocates WaitForSeconds every call
private IEnumerator DelayedAction()
{
    yield return new WaitForSeconds(1f);  // New allocation each time
}

// ✅ GOOD - Cache WaitForSeconds
private WaitForSeconds oneSecondWait = new WaitForSeconds(1f);

private IEnumerator DelayedAction()
{
    yield return oneSecondWait;  // Reuse cached wait
}

组件访问模式

尽量减少组件查询
// ❌ BAD - Multiple GetComponent calls
private void OnTriggerEnter(Collider other)
{
    if (other.GetComponent<Enemy>() != null)
    {
        other.GetComponent<Enemy>().TakeDamage(10);  // Called twice!
    }
}

// ✅ GOOD - Single GetComponent with pattern matching
private void OnTriggerEnter(Collider other)
{
    if (other.TryGetComponent<Enemy>(out var enemy))
    {
        enemy.TakeDamage(10);  // Called once
    }
}
碰撞时的组件缓存
// ❌ BAD - GetComponent on every collision
private void OnTriggerEnter(Collider other)
{
    var damageable = other.GetComponent<IDamageable>();
    if (damageable != null)
        damageable.TakeDamage(10);
}

// ✅ GOOD - Cache component on trigger enter
private Dictionary<Collider, IDamageable> damageableCache = new Dictionary<Collider, IDamageable>();

private void OnTriggerEnter(Collider other)
{
    if (!damageableCache.TryGetValue(other, out var damageable))
    {
        damageable = other.GetComponent<IDamageable>();
        damageableCache[other] = damageable;  // Cache for future collisions
    }

    damageable?.TakeDamage(10);
}

private void OnTriggerExit(Collider other)
{
    damageableCache.Remove(other);  // Clean up cache
}

物理优化

基于层的碰撞

配置 Physics Layer Collision Matrix,避免不必要的碰撞检测:

Edit > Project Settings > Physics > Layer Collision Matrix

// Setup layers
Layer 8: Player
Layer 9: Enemies
Layer 10: Projectiles
Layer 11: Environment

// Disable unnecessary collisions:
- Player vs Player (disabled)
- Enemies vs Enemies (disabled)
- Projectiles vs Projectiles (disabled)

性能提升:物理开销减少 30-50%。

Raycast 优化
// ❌ BAD - Raycast checks everything
bool hit = Physics.Raycast(origin, direction, out RaycastHit hitInfo);

// ✅ GOOD - Layer mask limits checks
int layerMask = 1 << LayerMask.NameToLayer("Enemy");
bool hit = Physics.Raycast(origin, direction, out RaycastHit hitInfo, maxDistance, layerMask);

// ✅ BETTER - Cache layer mask
private int enemyLayerMask;

private void Awake()
{
    enemyLayerMask = 1 << LayerMask.NameToLayer("Enemy");
}

private void Fire()
{
    bool hit = Physics.Raycast(origin, direction, out RaycastHit hitInfo, maxDistance, enemyLayerMask);
}
Rigidbody 休眠

Rigidbody 静止时应进入休眠状态:

// Rigidbody automatically sleeps when velocity < threshold
// Configure in Edit > Project Settings > Physics

// Wake up manually when needed
private void ApplyForce()
{
    if (rb.IsSleeping())
        rb.WakeUp();

    rb.AddForce(force);
}

性能分析

优化前先进行测量。使用 Unity Profiler 识别实际瓶颈。

打开 Profiler:Window > Analysis > Profiler

Profiler 关键指标

CPU 使用率:

  • 渲染(DrawCalls、SetPass 调用)
  • 脚本(Update、FixedUpdate、协程)
  • 物理(FixedUpdate.PhysicsFixedUpdate)
  • GC.Alloc(垃圾回收分配)

内存:

  • 总分配量
  • GC 分配量
  • 纹理内存
  • 网格内存
性能分析工作流
  1. 识别瓶颈:运行 Profiler,找到开销较大的帧
  2. 深入分析:点击峰值,查看调用层级
  3. 测量基准:记录当前性能
  4. 应用优化:进行有针对性的更改
  5. 测量改进:比较优化前后
  6. 重复操作:查找下一个瓶颈
深度分析

启用 Deep Profile 以查看详细调用堆栈(会影响性能):

警告:Deep Profile 会显著降低游戏速度。请用于小型场景或有针对性的性能分析。

自定义性能分析标记

测量特定代码段:

using Unity.Profiling;

public class AIController : MonoBehaviour
{
    private static readonly ProfilerMarker s_PathfindingMarker = new ProfilerMarker("AI.Pathfinding");
    private static readonly ProfilerMarker s_DecisionMarker = new ProfilerMarker("AI.DecisionMaking");

    private void Update()
    {
        s_PathfindingMarker.Begin();
        CalculatePath();
        s_PathfindingMarker.End();

        s_DecisionMarker.Begin();
        MakeDecision();
        s_DecisionMarker.End();
    }

    private void CalculatePath() { }
    private void MakeDecision() { }
}

在 Profiler 中显示自定义标记,以便精确测量。

性能预算

为每个系统设定性能目标:

目标:60 FPS(每帧 16.67 ms)

  • 渲染:6 ms
  • 脚本:4 ms
  • 物理:2 ms
  • UI:1 ms
  • 音频:0.5 ms
  • 其他:3 ms

使用 Profiler 进行监控,并优化超出预算的系统。

平台专项优化

移动设备优化

主要关注点:

  • CPU/GPU 性能较低
  • 内存受限
  • 电池续航
  • 触控输入开销

移动设备专项优化:

  • 减少绘制调用(移动设备上少于 100 次)
  • 降低纹理分辨率
  • 禁用阴影或使用简单阴影
  • 减少粒子数量
  • 使用遮挡剔除
  • 优化 UI(Canvas 批处理)
PC/主机优化

性能余量更大,但仍需优化:

  • 最低目标为 60 FPS
  • 允许使用更高画质设置
  • 监控 VRAM 使用量
  • 在最低配置硬件上进行性能分析

其他资源

参考文件

有关详细的性能优化技术,请参阅:

  • references/memory-optimization.md - 高级 GC 减少方法、分配模式
  • references/rendering-optimization.md - 绘制调用批处理、GPU 优化、着色器
  • references/physics-optimization.md - 碰撞优化、Rigidbody 最佳实践
  • references/profiling-guide.md - 完整的性能分析工作流、工具和分析方法

快速参考

缓存优先级:

  1. Transform 引用
  2. GetComponent 结果
  3. Find 结果
  4. Material 实例
  5. 协程中的 WaitForSeconds

避免在 Update 中执行:

  • GetComponent
  • Find 方法
  • 字符串拼接
  • 新建分配
  • 物理射线检测(谨慎使用)

优化前务必进行性能分析:

  • 测量基准
  • 识别瓶颈
  • 应用有针对性的修复
  • 测量改进

持续应用这些性能优化实践,确保 Unity 游戏在所有平台上都能流畅、响应迅速。

评论

支持 Markdown