[Unity] 编辑器运行中动态编译执行C#代码
(一)问题
之前写Lua时,修改完代码 reload 就可以热重载代码,调试起来十分方便(重构则十分痛苦)。
现在使用 C# 做开发,目前还没找到比较方便地进行热重载的方式。只能退而求其次,在调试上找找方法,尽量能减少编译重启的次数。
基本原理是:动态编译生成dll,再调用 Assembly 中的方法。之前看到过一个关键词 REPL,原理肯定不同,但加上编辑器扩展或许能实现类似的交互效果。
作用实际上不是很大,基本和打断点调试时在即时窗口中运行代码是类似的(稍微好用一些,毕竟可以执行一段多行代码)。目前主要在测试特效之类时预留接口,便可以使用不同参数动态调试,或者打印一些不太好断点的单例变量。
2021-10 补充:用处还是挺多的。虽然不能添加和修改已有函数,但是程序中的静态方法,以及能获取到对象实例的成员方法都能调用,很适合用来调试已有的UI表现等。比如别人写了一个HUD提示功能,接入到你的模块中时,就不用每改一点代码就重新运行一次了。
(二)提前备注
- 每次编译都会生成不同名的dll(同名的话会报文件占用中的错误),生成目录放在项目\Temp\ 中,关闭 Unity 时会自动清空该目录
(三)执行效果

执行方法,在Console中打印变量

编译生成的Dll们
(四)代码
1. DynamicCodeHelper
编译执行代码函数,其中这一段比较重要,会引用当前 Domain 中的所有程序集,否则调用项目中的方法会报错:
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
_compileParams.ReferencedAssemblies.Add(assembly.Location);
}
完整代码:
using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
using UnityEngine;
public class DynamicCodeHelper
{
private CSharpCodeProvider _provider;
private CSharpCodeProvider Provider
{
get
{
if (_provider == null)
{
DynamicCodeWindow.ColorDebug("[DynamicCode] Create CodeDomProvider");
_provider = new CSharpCodeProvider();
}
return _provider;
}
}
private CompilerParameters _compileParams;
private CompilerParameters CompileParams
{
get
{
if (_compileParams == null)
{
DynamicCodeWindow.ColorDebug("[DynamicCode] Create CompilerParameters");
_compileParams = new CompilerParameters();
// Add ALL of the assembly references
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
_compileParams.ReferencedAssemblies.Add(assembly.Location);
}
_compileParams.GenerateExecutable = false;
_compileParams.GenerateInMemory = false;
}
_compileParams.OutputAssembly = DynamicCodeWindow.OUTPUT_DLL_DIR + "/DynamicCodeTemp" + Time.realtimeSinceStartup + ".dll";
return _compileParams;
}
}
public void ExcuteDynamicCode(string codeStr, bool isUseTextAsAllContent)
{
if (codeStr == null) codeStr = "";
string generatedCode;
if (isUseTextAsAllContent)
{
generatedCode = codeStr;
}
else
{
generatedCode = GenerateCode(codeStr);
}
Debug.Log("[DynamicCode] Compile Start: " + generatedCode);
CompilerResults compileResults = Provider.CompileAssemblyFromSource(CompileParams, generatedCode);
if (compileResults.Errors.HasErrors)
{
Debug.LogError("[DynamicCode] 编译错误!");
var msg = new StringBuilder();
foreach (CompilerError error in compileResults.Errors)
{
msg.AppendFormat("Error ({0}): {1}\n",
error.ErrorNumber, error.ErrorText);
}
throw new Exception(msg.ToString());
}
// 通过反射,调用DynamicCode的实例
//AppDomain a = AppDomain.CreateDomain(AppDomain.CurrentDomain.FriendlyName);
Assembly objAssembly = compileResults.CompiledAssembly;
DynamicCodeWindow.ColorDebug("[DynamicCode] Gen Dll FullName: " + objAssembly.FullName);
DynamicCodeWindow.ColorDebug("[DynamicCode] Gen Dll Location: " + objAssembly.Location);
DynamicCodeWindow.ColorDebug("[DynamicCode] PathToAssembly: " + compileResults.PathToAssembly);
object objDynamicCode = objAssembly.CreateInstance("DynamicCode");
MethodInfo objMI = objDynamicCode.GetType().GetMethod("CodeExecute");
objMI.Invoke(objDynamicCode, null);
}
private string GenerateCode(string methodCode)
{
StringBuilder sb = new StringBuilder();
sb.Append(@"using System;
using UnityEngine;
public class DynamicCode {
public void CodeExecute() {
");
sb.Append(methodCode);
sb.Append("}}");
string code = sb.ToString();
return code;
}
}
2. DynamicCodeWindow
简单的编辑器扩展,不太重要。基本上就是获取文本然后调用DynamicCodeHelper.ExcuteDynamicCode
#if UNITY_EDITOR_WIN
using UnityEditor;
using UnityEngine;
/// <summary>
/// 字符串编译成DLL载入,只在编辑器中使用
/// </summary>
public class DynamicCodeWindow : EditorWindow
{
// 生成在 ..\Client\Client\Temp\DynamicCode\DynamicCodeTemp.dll
public const string OUTPUT_DLL_DIR = @"Temp\DynamicCode";
[MenuItem("TestTool/DynamicRun")]
private static void Open()
{
GetWindow<DynamicCodeWindow>();
}
private static DynamicCodeHelper _instance;
private static DynamicCodeHelper Helper
{
get
{
if (_instance == null)
{
_instance = new DynamicCodeHelper();
}
return _instance;
}
}
private bool isUseTextAsAllContent;
private string content = @"Debug.Log(""Hello"");";
private void OnGUI()
{
isUseTextAsAllContent = EditorGUILayout.ToggleLeft("完全使用文本作为编译内容(手动添加using等)", isUseTextAsAllContent);
content = EditorGUILayout.TextArea(content, GUILayout.Height(200));
if (GUILayout.Button("执行代码"))
{
Run(content, isUseTextAsAllContent);
}
if (GUILayout.Button("重置内容"))
{
if (isUseTextAsAllContent)
{
content = @"using System;
using UnityEngine;
public class DynamicCode {
public void CodeExecute() {
Debug.Log(""Hello"");
}
}";
}
else
{
content = @"Debug.Log(""Hello"");";
}
}
if (GUILayout.Button("新建/打开缓存目录"))
{
if (!System.IO.Directory.Exists(OUTPUT_DLL_DIR))
{
System.IO.Directory.CreateDirectory(OUTPUT_DLL_DIR);
}
System.Diagnostics.Process.Start(OUTPUT_DLL_DIR);
}
}
private static void Run(string code, bool isUseTextAsAllContent)
{
ColorDebug("[DynamicCode] Start......");
string codetmp = code;
Helper.ExcuteDynamicCode(codetmp, isUseTextAsAllContent);
ColorDebug("[DynamicCode] End......");
}
public static void ColorDebug(string content)
{
Debug.Log(string.Format("<color=#ff8400>{0}</color>", content));
}
}
#endif
[Unity] 编辑器运行中动态编译执行C#代码的更多相关文章
- 在C#中动态编译T4模板代码
转: http://www.wxzzz.com/1438.html 资料: https://cnsmartcodegenerator.codeplex.com/SourceControl/latest ...
- 转: angularjs 指令中动态编译的方法(适用于有异步请求的情况) 内嵌指令无效
angular的坑很多 例子: 在directive的link中有一个$http请求,当请求完成后根据返回的值动态做element.append('......');这个操作, 能显示没问题,可问题是 ...
- Python中动态编译函数compile(source, filename, mode, ......)参数filename的作用是什么?
动态编译函数compile调用语法如下: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1) 其中的fi ...
- 在 Linux/windows下 命令行中使用和执行 PHP 代码[交互式php]
[注释]在ubuntu下,升级php到7.1版本,虽然提示的是Interactive mode enabled, 但实际上可以直接书写命令,和interactive shell效果一样. 一:wind ...
- 使用PyQt(Python+Qt)+动态编译36行代码实现的计算器
PyQt是基于跨平台的图形界面C++开发工具Qt加Python包装的一个GPL软件(GPL是GNU General Public License的缩写,是GNU通用公共授权非正式的中文翻译),Qt基于 ...
- JAVA中动态编译的简单使用
一.引用库 pom文件中申明如下: <dependencies> <!-- https://mvnrepository.com/artifact/junit/junit --> ...
- (转)高性能JavaScript:加载和运行(动态加载JS代码)
浏览器是如何加载JS的 当浏览器遇到一个<script>标签时,浏览器首先根据标签src属性下载JavaScript代码,然后运行JavaScript代码,继而继续解析和翻译页面.如果需要 ...
- 在 Linux 命令行中使用和执行 PHP 代码
PHP是一个开源服务器端脚本语言,最初这三个字母代表的是“Personal Home Page”,而现在则代表的是“PHP:Hypertext Preprocessor”,它是个递归首字母缩写.它是一 ...
- 测试博文中添加可执行JS代码
昨天申请开通了博客园的JS权限,今天来看看效果. 测试执行JS 测试执行JS // 运行
随机推荐
- 《Java核心技术·卷Ⅰ:基础知识(原版10》学习笔记 第5章 继承
<Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 目录 <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 5.1 类.超类和子类 5.1 ...
- 网络编程-TCP连接的建立与终止
TCP是一个面向连接的协议.无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接. 1.建立连接 请求端(通常称为客户)发送一个 SYN 段指明客户打算连接的服务器的端口,以及初始序号( I ...
- 博客新手:图片URL的生成
作为一名博客小白,本人是在美化自己的博客时,发现自定义背景等操作需要提供图片的URL,而不是直接上传图片.那么什么是URL呢?我们又该如何获取它呢? 什么是URL 根据维基百科:统一资源定位符(英语: ...
- 【机器学习基础】无监督学习(1)——PCA
前面对半监督学习部分作了简单的介绍,这里开始了解有关无监督学习的部分,无监督学习内容稍微较多,本节主要介绍无监督学习中的PCA降维的基本原理和实现. PCA 0.无监督学习简介 相较于有监督学习和半监 ...
- 🏆【Alibaba中间件技术系列】「RocketMQ技术专题」系统服务底层原理以及高性能存储设计分析
设计背景 消息中间件的本身定义来考虑,应该尽量减少对于外部第三方中间件的依赖.一般来说依赖的外部系统越多,也会使得本身的设计越复杂,采用文件系统作为消息存储的方式. RocketMQ存储机制 消息中间 ...
- C++普通成员函数的调用形式
#include<iostream> using namespace std; class A { public: int data; void foo(int x) { data = x ...
- Redis 源码简洁剖析 04 - Sorted Set 有序集合
Sorted Set 是什么 Sorted Set 命令及实现方法 Sorted Set 数据结构 跳表(skiplist) 跳表节点的结构定义 跳表的定义 跳表节点查询 层数设置 跳表插入节点 zs ...
- AtCoder ABC213 简要题解
这世道连 \(\rm ABC\) 都要写题解来续命了... A - D 略. E 有如下观察: 对于任意的四个方格,出去之后再回来可以调整为先在内部走到固定位置再走出去. 因此只需要考虑在一开始把内部 ...
- Mysql8.0主从配置
环境Centos7.6 ,mysql8.0 Mysql主从配置 1.1 配置环境: 本人在vm下做的实验,linux版本是centos的7.0版本,然后Mysql版本为此时较新的8.0.13版本.做最 ...
- PHP中英文混合字符串处理
转载请注明来源:https://www.cnblogs.com/hookjc/ function cut_str($string, $sublen, $start = 0, $code = 'utf- ...