Unity优化:如何在特定条件下避免调用Update()方法?
问题背景
首先定义全局枚举:
public enum UpdateMode{ EveryFrame, FixedUpdate }
然后在脚本中声明并使用:
UpdateMode updateMode; private void Update() { if (updateMode != UpdateMode.EveryFrame) { return; } MyFunction(); } private void FixedUpdate() { if (updateMode != UpdateMode.FixedUpdate) { return; } MyFunction(); }
认为原实现中Update()仍会每帧被调用(即使直接return)不够优化,考虑了两种方案:
- 编写两个独立脚本,代码重复,仅区分
Update()/FixedUpdate() - 编写两个分别包含
Update()和FixedUpdate()的脚本,通过引用调用MyFunction()
想了解:
- 是否有更优雅的实现方式
- 各方案在CPU开销、内存占用、复杂度上的性能对比
方案分析与优化建议
一、原方案的实际开销
先明确:Unity中每帧调用Update()里的简单枚举判断,CPU开销几乎可以忽略。枚举是值类型,判断只是一次整数比较,这种级别的开销在常规游戏项目中属于微乎其微的范畴——除非有成千上万个这类脚本同时运行,否则没必要为这点开销重构。但如果确实追求极致优化,再看其他方案。
二、你提出的两种方案对比
方案1:两个独立重复脚本
- CPU开销:最优,每个脚本只会执行对应的更新函数,无多余判断。但代码冗余,后续修改
MyFunction需要同步改两个脚本,维护成本高。 - 内存占用:略高,每个GameObject只挂其中一个脚本的话,内存差异极小,主要是代码冗余带来的编译后体积略增。
- 复杂度:实现简单,但维护复杂度高,重复代码容易出现不一致问题。
方案2:拆分调用脚本+逻辑脚本
- CPU开销:比原方案略高,多了一次跨组件的引用调用,但仍属于可忽略的级别。
- 内存占用:和原方案接近,只是每个GameObject需要同时挂逻辑脚本+其中一个更新调用脚本,内存增加极小。
- 复杂度:中等,需要维护三个脚本,但代码无冗余,修改
MyFunction只需改逻辑脚本,比方案1易维护。
三、更优雅的实现方式
推荐两种灵活且无冗余的方案:
1. 动态注册更新回调(适合频繁切换模式)
通过单例管理器统一管理更新回调,只在需要时注册对应逻辑,避免空跑更新函数。
核心代码示例:
public enum UpdateMode{ EveryFrame, FixedUpdate, None } public class MyLogic : MonoBehaviour { public UpdateMode updateMode; private Action _updateCallback; private void Awake() { _updateCallback = MyFunction; SwitchUpdateMode(updateMode); } private void SwitchUpdateMode(UpdateMode newMode) { // 注销旧回调 UpdateManager.Instance.UnregisterUpdateCallback(_updateCallback); UpdateManager.Instance.UnregisterFixedUpdateCallback(_updateCallback); // 注册新回调 switch(newMode) { case UpdateMode.EveryFrame: UpdateManager.Instance.RegisterUpdateCallback(_updateCallback); break; case UpdateMode.FixedUpdate: UpdateManager.Instance.RegisterFixedUpdateCallback(_updateCallback); break; } } public void SetUpdateMode(UpdateMode mode) { if(updateMode == mode) return; updateMode = mode; SwitchUpdateMode(mode); } private void MyFunction() { // 你的业务逻辑 } } // 全局更新管理器单例 public class UpdateManager : MonoBehaviour { public static UpdateManager Instance { get; private set; } private List<Action> _updateCallbacks = new(); private List<Action> _fixedUpdateCallbacks = new(); private void Awake() { if(Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); } private void Update() { foreach(var callback in _updateCallbacks) callback?.Invoke(); } private void FixedUpdate() { foreach(var callback in _fixedUpdateCallbacks) callback?.Invoke(); } public void RegisterUpdateCallback(Action callback) { if(!_updateCallbacks.Contains(callback)) _updateCallbacks.Add(callback); } public void UnregisterUpdateCallback(Action callback) { if(_updateCallbacks.Contains(callback)) _updateCallbacks.Remove(callback); } public void RegisterFixedUpdateCallback(Action callback) { if(!_fixedUpdateCallbacks.Contains(callback)) _fixedUpdateCallbacks.Add(callback); } public void UnregisterFixedUpdateCallback(Action callback) { if(_fixedUpdateCallbacks.Contains(callback)) _fixedUpdateCallbacks.Remove(callback); } }
- 优势:只有注册了回调的逻辑才会被执行,支持动态切换更新模式,扩展性强(可快速添加LateUpdate等模式),代码无冗余。
- 开销:比原方案略高(委托调用的微小开销),但远低于跨组件调用,适合大量需要动态控制更新的对象。
2. 接口+动态添加组件(追求极致性能)
定义接口规范逻辑方法,编写两个分别处理Update/FixedUpdate的组件,根据模式动态添加/移除。
核心代码示例:
public interface IUpdateHandler { void MyFunction(); } public class MyLogic : MonoBehaviour, IUpdateHandler { public UpdateMode updateMode; private void Awake() { SwitchUpdateMode(updateMode); } public void SetUpdateMode(UpdateMode mode) { if(updateMode == mode) return; updateMode = mode; SwitchUpdateMode(mode); } private void SwitchUpdateMode(UpdateMode newMode) { // 移除旧处理组件 Destroy(GetComponent<UpdateHandler>()); Destroy(GetComponent<FixedUpdateHandler>()); // 添加新处理组件 switch(newMode) { case UpdateMode.EveryFrame: gameObject.AddComponent<UpdateHandler>().SetHandler(this); break; case UpdateMode.FixedUpdate: gameObject.AddComponent<FixedUpdateHandler>().SetHandler(this); break; } } public void MyFunction() { // 你的业务逻辑 } } public class UpdateHandler : MonoBehaviour { private IUpdateHandler _handler; public void SetHandler(IUpdateHandler handler) { _handler = handler; } private void Update() { _handler?.MyFunction(); } } public class FixedUpdateHandler : MonoBehaviour { private IUpdateHandler _handler; public void SetHandler(IUpdateHandler handler) { _handler = handler; } private void FixedUpdate() { _handler?.MyFunction(); } }
- 优势:完全避免了不需要的更新函数调用,每个对象只有一个处理组件在运行,代码职责清晰,逻辑与更新处理分离,维护方便。
- 开销:和方案1的独立脚本性能一致,动态添加组件的开销仅在初始化或切换模式时发生,运行时无多余消耗。
四、总结对比表格
| 方案 | CPU开销 | 内存占用 | 复杂度 | 维护性 |
|---|---|---|---|---|
| 原方案(枚举判断) | 极低(微开销) | 低 | 低 | 高 |
| 方案1(独立重复脚本) | 最优 | 略高 | 低 | 极差 |
| 方案2(拆分调用脚本) | 略高 | 略高 | 中等 | 高 |
| 动态回调管理器 | 略高 | 中等 | 中等 | 极高 |
| 接口+动态组件 | 最优 | 低 | 中等 | 高 |
- 如果是少量对象,原方案完全够用,无需优化;
- 如果是大量对象或追求极致性能,推荐接口+动态组件方案;
- 如果需要频繁切换更新模式,动态回调方案更灵活。
内容的提问来源于stack exchange,提问作者Aliph Null
相关产品推荐
相关产品推荐

