跳过域重新加载:使用 MethodReload 功能实现更快的迭代

Oct 22, 2026
Programming and DevOps
方法重载

为方便起见,此网页已进行机器翻译。我们无法保证翻译内容的准确性或可靠性。如果您对翻译内容的准确性有疑问,请参阅此网页的官方英文版本。

我们都经历过那种情况:你修改了一些游戏玩法代码,然后等待 Editor 完成域重新加载。该过程会减慢您的迭代时间并真正耗尽您的创造力。更不用说ON设备上迭代所花费的额外时间,每次循环都需要完整生成。

我们的答案是MethodReload。我们在Unite Seoul的舞台上ON了演示,现在它作为一个实验性的性包可用。在POST中,我们将介绍如何使用 MethodReload,它的局限性是什么,以及它与您Project可能已有的热重新加载工具相比如何。

我们为什么卡在重新加载上

了解一些History背景会有帮助,因为域名重新加载存在的原因也是它难以根除的原因。

播放模式是基于 Mono 的 AppDomains 构建的,其中代码更改会触发一个熟悉的流程:销毁域,加载新的程序集,重新 JIT,然后在同一进程中恢复。多年来,那是一种真正的超能力,这也是在 Unity 中迭代感觉比构建 Player 要快的一个重要原因。

它也是一个粗暴的工具,意思是说在重新加载期间,所有东西都会消失,所有东西都会回来,无论你更改了一个方法还是一百个。此外,IL2CPP All支持持域重新加载,因此每次Change都需要重新构建整个Player。

你可能在问自己,“CoreCLR 会解决这个问题吗?”答案是:不是神奇的。虽然 CoreCLR 摒弃了 domain reloads,但它仍然需要重新加载程序集,MethodReload 在这方面也有帮助。

如何使用MethodReload

如上所述,MethodReload 现已可用,它包含在 com.unity.pipeline 中,当前版本为 0.7.0-exp.1。

使用 MethodReload 的过程相当Simple。只需用 [MethodReload] 属性标记一个或多个方法,启动监视器,进入播放模式或附加开发人员播放器,然后编辑方法的主体。正在running的实例ONNext一次调用时会拾取新建的主体。

但是 MethodReload 并不能完全替换重载。更大的改动仍然GO经过常规的编译。但对于make迭代Loop的大部分的小调整来说,它将漫长的等待变成几秒钟,无论是在Editor器中还是ON设备上。

试一试

了解 MethodReload 最快的方法是运行它,所以如果你想自己测试一下,这里有一些入门步骤。注意:由于这是一个实验性的性功能,该流程未来可能会发生Change,并且可能与此处描述的不同

1.安装该包(需要 Unity 6.0+)。你可以通过 Unity CLI 安装它,或者使用包管理器通过“按技术名称安装包”并使用此字符串:com.unity.pipeline@0.7.0-exp.1 手动添加该 package 管道。

2.标记你迭代的方法.该属性必须在您进入播放模式或生成Player之前存在,因为分发Check是在编译时间编织进去的。标记的方法必须是 public(实例或静态)并返回 void 或 IEnumerator;否则在生成时会发出警告并跳过。

using Unity.Pipeline.MethodReload;
using UnityEngine;

public class Spinner : MonoBehaviour
{
    public float rotationSpeed = 90f;

    [MethodReload]
    public void Update()
    {
        transform.Rotate(Vector3.up, rotationSpeed * Time.deltaTime);
    }
}

3.Start监视器。为了让您的 MethodReload 实时看到您的更改,您需要启动 MethodReload Watcher。通过进入“项目设置”→“管道”→“Editor”,然后在“方法重新加载解释器监视”部分点击“Start监视”来完成此操作。

项目设置

虽然监视器覆盖了整个 Assets 树,但一个保存的 .cs 只有在包含 MethodReload 属性时才会编译,因此不相关的保存对您没有任何成本。

4.如果在Editor器中测试:进入播放模式。一旦运行,任何标记的代码更改都将被编译并原地注入。一旦您离开播放模式,您的更改将编译并通过常规的重新加载

5.如果在设备上测试:插入一个设备.构建一个启用了 runtime Pipeline driver enabled 的开发 Player。使用Autoconnect Profiler连接设备,或通过性能分析器或控制台窗口附加。从那以后,每次保存都会在 Editor 中使用 player's 预处理器定义进行编译,并推送到所有连接的 Player。两个方向都不需要重启。

两个值得前面了解的工作流程流程细节:

  • 当监视器运行时,自动刷新是关闭的,因此保存会重新加载而不是触发域重新加载;其他 Assets 在您按下 Ctrl/Cmd+R 之前不会导入,而停止监视会将您的设置恢复。
  • 该手表在域重新加载时得以存活:它会重新应用播放模式重新加载清除的编辑,并重新推送到重新连接的 Player,因为重启的 Player 会启动原始编译代码。

关于使用命令行界面怎么样?

如果您更喜欢在 Terminal 中工作,想通过 Agent 工作,或者想推送所有标记的方法而不是仅推送自监视开始以来发生变化的方法,相同的功能也作为 Unity CLI 命令暴露出来:

# Reload in the Editor through the interpreter
unity command reload_file_editor_interpreter "Assets/Scripts/Spinner.cs"

# Compile in the Editor, push to connected players (folders work too)
unity command reload_file_player_interpreter "Assets/Scripts" --player 2

局限性

没有绕过域重新加载的方法而不会做出妥协,而 MethodReload 的限制在三个方面:它可以应用哪种类型的更改,它可以运行哪种 C# 代码,以及它运行的速度有多快。

只有标记的方法体发生变化。重新加载永远不会触及调用点、字段或类型Layout。没有新建字段,没有新建。那个约束使得将其应用于实时实例是安全的,这也是你计划的:决定你将迭代哪些方法并为它们打标签。重新加载 可以 声明一个新方法,并从重新加载的体中调用它,但该方法不能是一个重新加载的入口点,因为在您下一次实际编译之前,任何编译过的代码都不知道要调用它。

它运行一个 C# 子集。解释器是手工构建的,必须足够小才能存在于Player内部,所以它没有实现All C# 功能。在 [MethodReload] 体内您不能使用:

  • await 和 Task, try/catch/finally, lock, LINQ 查询语法, checked
  • 十进制, 动态, 反射类型, 线程类型
  • ref, out, 和 in 参数,ref 返回和局部变量,多维数组(锯齿状也可以),泛型方法

does 工作覆盖了大多数游戏玩法代码:原始类型、方法调用、循环、委托、字符串插值、coroutine yield、Collections。Roslyn 分析器随包一起提供,并且仅在标记的体中在您的 IDE 中显示违规情况,因此您在输入时而不是在推送后发现。

在设备上,解释的代码只能调用 Player 暴露的功能。IL2CPP 会剥离没有引用的代码,因此一个重新加载的、Calls了您的生成从未使用的 UnityEngine 或 BCL 成员的体,将没有东西可以调用。在Editor器中,缺失的成员ON需绑定。ON设备上,前面推送会检查并以警告形式列出缺失的成员;如果代码仍然到达其中一个,它就会抛出异常。经验法则:改变一个方法 所做 的事情,而不是它 依赖的 API 集。

解释型代码比编译型代码慢。这就是解释器的本质,无论是时间 IL2CPP 还是时间编译的 Mono 和 CoreCLR。它只影响Development构建中的标记方法。在发布构建中没有序言和解释器;一个标记的方法直接运行其编译后的主体。最终,由你决定迭代过程中的性能损失是否值得换取改进的迭代速度和便利性。

协程在创建时拾取更改。一个 [MethodReload] IEnumerator 方法在下一次 StartCoroutine 运行时会使用新的主体。正在运行的协程会保持它们开始时的状态。将其与 [OnMethodReloaded] 配对,这是一个无参数的实例方法,在每次重新加载到达其类型后的每个活动实例上触发,并且您有一个域重新加载自由的 OnEnable 用于重启长期运行的循环:

[OnMethodReloaded]
void OnMethodReloaded()
{
    StopAllCoroutines();
    StartCoroutine(MainLoop());
}

其内部工作原理

它是两个思想的结合:第一个是编译 C# 会产生 IL 字节码,而 IL 如果保持在一个适度的子集内,就可以被解释器执行而不是被编译。那个解释器本身就是普通的 C#,所以 IL2CPP 会将其与您游戏中的REST部分一起编译,这就是它能够ON设备上运行的原因。

第二个想法是深入其中。每个[MethodReload] 方法都有一个连接到解释器的路径。通过在编译时间在标记方法中编织一个前言来Done分支到解释器。反编译后,它看起来像这样:

[MethodReload]
public void TakeDamage(int amount)
{
    // weaved during compilation
    if (MethodReloadRegistry.HasOverride("Player.TakeDamage") &&
        MethodReloadRegistry.TryInvokeMethodReload(
            "Player.TakeDamage", this, amount))
    {
        return;
    }

    // initial code
    hp -= amount;
}

编织的 Check 会询问注册表是否存在此方法的覆盖。监视器和命令行界面用新编译的IL填充了该注册表。如果有入口,解释器会运行它,原始主体将被跳过。否则编译后的代码照常运行。

这如何比较

已经有现成的工具可以ON不重新加载的情况下迭代 C# 代码,我们宁愿精确说明每种工具的适用范围,也不愿假装它们不存在。

Asset Store(资源商店)中的包在运行中的 Mono 运行时中修补方法。这为他们提供了出色的 Editor 体验,在重新加载的体中拥有完整的 C#,没有标记步骤,在 Hot Reload 的情况下,为 Mono Player 构建提供了设备模式。All真正的优势。这也意味着它们不与使用 IL2CPP 的 Player 配合工作,因为没有运行时可以打补丁。

.NET Hot Reload 是 Visual Studio 和 Rider 中用于 .NET Applications 的功能,它通过运行时(runtime)的编辑和继续(edit-and-continue)路径应用编辑。它是为 CoreCLR 构建的,不Target Unity 的 Mono 或 IL2CPP 运行时。

MethodReload 在其解释器后端交易语言覆盖范围方面处于一个不同的聚光灯,它是一个 C# 的子集,但runningON与您通常需要重新部署到Phone或控制台开发套件上的相同的 IL2CPP 开Development Build,通过您已经插上的数据线,没有额外的服务Server在Player端,也没有需要Open的端口。

诚实的版本是:如果你的迭代痛点存在于Editor器中,现有的工具已经很成熟,你应该保持使用对你有效的东西。如果你的迭代痛点存在ON某个设备上,尤其是ON IL2CPP 上,这是第一个会出现在那里的选项。

最终提示

如果你现在正在为某个 Project 设置工作流程:

  • 标记你的热点。 [MethodReload] 在发布构建中不产生任何费用,所以请标记你在设备上知道会修改的 Update、调优和游戏手感方法。
  • 让分析器将你保留在子集中。如果一个方法需要 try/catch 或 await,请将该部分移到一个未标记的辅助函数中并重新加载周围的逻辑。分析器会告诉你你做错了什么。
  • 先在 Editor 中试一下。监视器,或来自 CLI 的 reload_file_editor_interpreter,通过与在设备上运行的相同的解释器运行您的编辑。如果它在那里有效,那么它ON硬件上也会有效,这样你就可以ON不等待生成的情况下捕获子集和缺失 API 的问题。

接下来该怎么做

命令参考、运行时设置 Page 和完整的约束列表都在 com.unity.pipeline docs 中。

我们鼓励您在您的 Projects 中试用此功能,并 告诉我们进展如何。非常感谢您的Feedback,当人们在上面进行推送时,实验性的软件包提升最快。

域名重新加载不会对需要它的更改生效。对于没有改变的,你可以停止等待。