关于 Unity Performance
Unity Performance 提供 Unity 游戏性能优化指导,面向 Unity 开发者,通过分析瓶颈并采用针对性方法处理 CPU、内存、渲染和物理开销。
主要功能
- 01缓存组件与对象引用
- 02通过对象池复用对象
- 03减少 Update 循环工作
- 04降低垃圾回收分配
- 05优化物理检测
- 06分析瓶颈并比较结果
适用场景
- 01减少游戏运行时的帧耗时峰值
- 02降低高频代码中的内存分配
- 03排查渲染或物理性能瓶颈
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cryptorabea/ claude_unity_dev_plugin --skill unity-performance安装后在 Claude Code 中输入/unity-performance确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/memory-optimization.mdrendering-optimization.mdprofiling-guide.mdphysics-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 分配量
- 纹理内存
- 网格内存
性能分析工作流
- 识别瓶颈:运行 Profiler,找到开销较大的帧
- 深入分析:点击峰值,查看调用层级
- 测量基准:记录当前性能
- 应用优化:进行有针对性的更改
- 测量改进:比较优化前后
- 重复操作:查找下一个瓶颈
深度分析
启用 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- 完整的性能分析工作流、工具和分析方法
快速参考
缓存优先级:
- Transform 引用
- GetComponent 结果
- Find 结果
- Material 实例
- 协程中的 WaitForSeconds
避免在 Update 中执行:
- GetComponent
- Find 方法
- 字符串拼接
- 新建分配
- 物理射线检测(谨慎使用)
优化前务必进行性能分析:
- 测量基准
- 识别瓶颈
- 应用有针对性的修复
- 测量改进
持续应用这些性能优化实践,确保 Unity 游戏在所有平台上都能流畅、响应迅速。