关于Unity程序集的一些总结
在 Unity 中,如果你在运行时使用代码动态生成了一个程序集(Assembly),并希望在运行时重新加载它,需要了解以下几点:
🔧 一、Unity 的限制
Unity 的运行时(Mono 或 IL2CPP)不支持卸载已经加载的程序集。也就是说,一旦通过 Assembly.Load
加载了程序集,就无法卸载或替换它,除非整个 AppDomain
被卸载,而 Unity 不允许你创建/卸载自定义 AppDomain
。
🧪 二、可行方案
✅ 方法一:使用 Assembly.Load(byte[])
加载编译好的 DLL
你可以在运行时生成 DLL,然后用 Assembly.Load
加载它:
byte[] dllBytes = File.ReadAllBytes("path/to/your.dll");
Assembly assembly = Assembly.Load(dllBytes);
Type myType = assembly.GetType("YourNamespace.YourClass");
object instance = Activator.CreateInstance(myType);
这个只能加载一次,无法重复加载不同版本的 DLL(名字相同会冲突)。
✅ 方法二:用 AssemblyLoadContext
(仅限 .NET Core/5+/Unity Future)
Unity 当前默认不支持 AssemblyLoadContext
,但如果未来升级到 .NET Core 支持,它将允许卸载和重新加载程序集。
✅ 方法三:通过子进程实现热重载
一个变通方法是将你的热更新逻辑放到一个独立的子进程(例如使用 .NET CLI 程序),通过进程间通信(如 Socket、NamedPipe)与 Unity 主进程交互,实现真正意义的“热重载”。
🔁 Unity 编辑器下的变通方案
如果你是在编辑器中自动生成脚本代码,然后希望重新编译/加载这些代码,可以使用:
UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation();
这个会触发 Unity 的重新编译,自动将新生成的脚本编译到当前的 Assembly-CSharp.dll 中。
📦 如果你使用了 Assembly Definition 文件(.asmdef)
你也可以通过代码控制 .asmdef
的增删,然后调用 Unity 的编译流程强制重新生成并加载该程序集。
🔄 更推荐的运行时代码更新方案:使用热更新框架
如果你的目标是运行时代码更新,推荐使用以下热更框架:
框架名 | 优势 |
---|---|
ILRuntime | 支持 Unity,稳定,支持 AOT 与 JIT |
HybridCLR | 支持热更新、可替换函数、接近原生性能 |
XLua | 使用 Lua 作为热更语言,灵活轻便 |
✅ 总结
目标 | 推荐方案 |
---|---|
编辑器生成代码后加载 | UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation() |
运行时加载一次 | Assembly.Load(byte[]) |
多次加载/卸载 | 不可行(Mono 无法卸载程序集) |
需要动态替换逻辑 | 使用 ILRuntime、HybridCLR 等热更框架 |
CompilationPipeline.RequestScriptCompilation()
和 EditorUtility.RequestScriptReload()
核心区别:
✅ 一览对比表:
特性 | CompilationPipeline.RequestScriptCompilation() |
EditorUtility.RequestScriptReload() |
---|---|---|
所属命名空间 | UnityEditor.Compilation |
UnityEditor |
功能 | 请求重新编译脚本 | 请求重新加载脚本(通常用于强制刷新域) |
是否触发编译 | ✅ 是 | ❌ 不一定 |
是否触发 reload | 编译成功后自动 reload | 立即触发 reload(前提是脚本已编译) |
使用场景 | 你有改动脚本或生成代码后需要 Unity 重新编译 | 脚本没有变化,但你想强制 Unity 刷新一下脚本域 |
是否常用 | 常用于代码生成工具(如生成 .cs 后自动编译) |
常用于脚本域恢复(如反序列化、内存刷新) |
是否立即生效 | 编译是异步进行 | reload 是立即排队进行 |
🎯 具体解释
1️⃣ CompilationPipeline.RequestScriptCompilation()
-
作用:告诉 Unity “我改动了脚本,请你编译一下”。
-
典型使用场景:
- 在编辑器扩展中自动生成
.cs
脚本后调用它; - 修改了脚本文件后通过代码触发 Unity 自动编译;
- 通常用于配合
.asmdef
结构进行自定义编译处理。
- 在编辑器扩展中自动生成
📝 示例:
UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation();
Unity 编译完成后,会自动触发 Domain Reload(重载脚本域)。
2️⃣ EditorUtility.RequestScriptReload()
-
作用:告诉 Unity “请你重新加载一下脚本(或刷新 C# Domain)”,但不会强制编译。
-
典型使用场景:
- 没有代码变更但你想强制刷新脚本状态(比如刷新
[InitializeOnLoad]
类); - 有时在一些特殊状态下(如某些 ScriptableObject 状态出错)用它强制刷新;
- 不能用于生成代码的场景,因为它不会触发编译器动作。
- 没有代码变更但你想强制刷新脚本状态(比如刷新
📝 示例:
UnityEditor.EditorUtility.RequestScriptReload();
如果 Unity 没检测到
.cs
文件的变化,这个方法执行后可能没有任何可见效果。
✅ 总结建议
你想做的事 | 使用哪个方法 |
---|---|
你用代码生成了 .cs 文件,希望 Unity 编译并生效 |
✅ RequestScriptCompilation() |
你没有修改代码,只是希望 Unity 重新加载脚本(如热刷新初始化类) | ✅ RequestScriptReload() |
希望模拟 Unity 脚本更新后的自动编译效果 | ✅ RequestScriptCompilation() |
如你在 Unity 编辑器中自动生成代码并希望它立即生效,推荐顺序是:
// 生成文件后
AssetDatabase.Refresh(); // 刷新资产
CompilationPipeline.RequestScriptCompilation(); // 请求编译
⚠️ 一般不建议手动调用 RequestScriptReload()
,除非你遇到 Unity 编辑器某些奇怪状态,正常流程应由 Unity 自动触发 reload。
如果你想实现完整的生成代码 ➜ 自动编译 ➜ 编译完成后再做某事的流程,也可以配合监听 CompilationPipeline.compilationFinished
。
好的!以下是一个 完整示例,展示如何在 Unity 编辑器中:
- 生成一段
.cs
脚本文件; - 自动刷新资源(AssetDatabase.Refresh);
- 请求脚本编译(CompilationPipeline.RequestScriptCompilation);
- 在编译完成后执行某些操作(如打印信息、绑定数据、生成 ScriptableObject 等)。
✅ 示例:Unity 编辑器代码生成 + 自动编译监听
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
using System.IO;
public class AutoScriptGenerator : EditorWindow
{
[MenuItem("Tools/自动生成脚本并编译")]
public static void GenerateScriptAndCompile()
{
string scriptPath = "Assets/Generated/MyGeneratedClass.cs";
// 1. 写入脚本内容
string scriptContent = @"
using UnityEngine;
public class MyGeneratedClass
{
public static void SayHello()
{
Debug.Log(""Hello from generated class!"");
}
}";
File.WriteAllText(scriptPath, scriptContent);
Debug.Log("✅ 已生成脚本:" + scriptPath);
// 2. 刷新资源数据库
AssetDatabase.Refresh();
// 3. 注册编译完成回调(注意:只注册一次,避免重复执行)
CompilationPipeline.compilationFinished -= OnCompilationFinished; // 避免重复绑定
CompilationPipeline.compilationFinished += OnCompilationFinished;
// 4. 请求 Unity 重新编译脚本
CompilationPipeline.RequestScriptCompilation();
}
// 编译完成回调
private static void OnCompilationFinished(object context)
{
Debug.Log("✅ 脚本编译完成!");
// 取消监听(只监听一次)
CompilationPipeline.compilationFinished -= OnCompilationFinished;
// ✅ 编译完成后你可以执行一些初始化逻辑:
// 比如自动生成 ScriptableObject、初始化数据等
// 下面这个类可以通过反射拿到
var type = System.Type.GetType("MyGeneratedClass");
if (type != null)
{
var method = type.GetMethod("SayHello", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
method?.Invoke(null, null); // 执行静态方法
}
}
}
🧪 使用方法
- 创建一个编辑器脚本,保存为
Assets/Editor/AutoScriptGenerator.cs
; - 在 Unity 菜单栏中点击 Tools → 自动生成脚本并编译;
- 脚本会自动生成,并请求 Unity 编译;
- 编译完成后会执行
MyGeneratedClass.SayHello()
方法并打印日志。