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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 08:42:14