(一)问题

   之前写Lua时,修改完代码 reload 就可以热重载代码,调试起来十分方便(重构则十分痛苦)。

   现在使用 C# 做开发,目前还没找到比较方便地进行热重载的方式。只能退而求其次,在调试上找找方法,尽量能减少编译重启的次数。

   基本原理是:动态编译生成dll,再调用 Assembly 中的方法。之前看到过一个关键词 REPL,原理肯定不同,但加上编辑器扩展或许能实现类似的交互效果。

   作用实际上不是很大,基本和打断点调试时在即时窗口中运行代码是类似的(稍微好用一些,毕竟可以执行一段多行代码)。目前主要在测试特效之类时预留接口,便可以使用不同参数动态调试,或者打印一些不太好断点的单例变量。

   2021-10 补充:用处还是挺多的。虽然不能添加和修改已有函数,但是程序中的静态方法,以及能获取到对象实例的成员方法都能调用,很适合用来调试已有的UI表现等。比如别人写了一个HUD提示功能,接入到你的模块中时,就不用每改一点代码就重新运行一次了。

(二)提前备注

  1. 每次编译都会生成不同名的dll(同名的话会报文件占用中的错误),生成目录放在项目\Temp\ 中,关闭 Unity 时会自动清空该目录

(三)执行效果

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

编译生成的Dll们

(四)代码

1. DynamicCodeHelper

编译执行代码函数,其中这一段比较重要,会引用当前 Domain 中的所有程序集,否则调用项目中的方法会报错:

  1. foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
  2. {
  3. _compileParams.ReferencedAssemblies.Add(assembly.Location);
  4. }

完整代码:

  1. using Microsoft.CSharp;
  2. using System;
  3. using System.CodeDom.Compiler;
  4. using System.Reflection;
  5. using System.Text;
  6. using UnityEngine;
  7. public class DynamicCodeHelper
  8. {
  9. private CSharpCodeProvider _provider;
  10. private CSharpCodeProvider Provider
  11. {
  12. get
  13. {
  14. if (_provider == null)
  15. {
  16. DynamicCodeWindow.ColorDebug("[DynamicCode] Create CodeDomProvider");
  17. _provider = new CSharpCodeProvider();
  18. }
  19. return _provider;
  20. }
  21. }
  22. private CompilerParameters _compileParams;
  23. private CompilerParameters CompileParams
  24. {
  25. get
  26. {
  27. if (_compileParams == null)
  28. {
  29. DynamicCodeWindow.ColorDebug("[DynamicCode] Create CompilerParameters");
  30. _compileParams = new CompilerParameters();
  31. // Add ALL of the assembly references
  32. foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
  33. {
  34. _compileParams.ReferencedAssemblies.Add(assembly.Location);
  35. }
  36. _compileParams.GenerateExecutable = false;
  37. _compileParams.GenerateInMemory = false;
  38. }
  39. _compileParams.OutputAssembly = DynamicCodeWindow.OUTPUT_DLL_DIR + "/DynamicCodeTemp" + Time.realtimeSinceStartup + ".dll";
  40. return _compileParams;
  41. }
  42. }
  43. public void ExcuteDynamicCode(string codeStr, bool isUseTextAsAllContent)
  44. {
  45. if (codeStr == null) codeStr = "";
  46. string generatedCode;
  47. if (isUseTextAsAllContent)
  48. {
  49. generatedCode = codeStr;
  50. }
  51. else
  52. {
  53. generatedCode = GenerateCode(codeStr);
  54. }
  55. Debug.Log("[DynamicCode] Compile Start: " + generatedCode);
  56. CompilerResults compileResults = Provider.CompileAssemblyFromSource(CompileParams, generatedCode);
  57. if (compileResults.Errors.HasErrors)
  58. {
  59. Debug.LogError("[DynamicCode] 编译错误!");
  60. var msg = new StringBuilder();
  61. foreach (CompilerError error in compileResults.Errors)
  62. {
  63. msg.AppendFormat("Error ({0}): {1}\n",
  64. error.ErrorNumber, error.ErrorText);
  65. }
  66. throw new Exception(msg.ToString());
  67. }
  68. // 通过反射,调用DynamicCode的实例
  69. //AppDomain a = AppDomain.CreateDomain(AppDomain.CurrentDomain.FriendlyName);
  70. Assembly objAssembly = compileResults.CompiledAssembly;
  71. DynamicCodeWindow.ColorDebug("[DynamicCode] Gen Dll FullName: " + objAssembly.FullName);
  72. DynamicCodeWindow.ColorDebug("[DynamicCode] Gen Dll Location: " + objAssembly.Location);
  73. DynamicCodeWindow.ColorDebug("[DynamicCode] PathToAssembly: " + compileResults.PathToAssembly);
  74. object objDynamicCode = objAssembly.CreateInstance("DynamicCode");
  75. MethodInfo objMI = objDynamicCode.GetType().GetMethod("CodeExecute");
  76. objMI.Invoke(objDynamicCode, null);
  77. }
  78. private string GenerateCode(string methodCode)
  79. {
  80. StringBuilder sb = new StringBuilder();
  81. sb.Append(@"using System;
  82. using UnityEngine;
  83. public class DynamicCode {
  84. public void CodeExecute() {
  85. ");
  86. sb.Append(methodCode);
  87. sb.Append("}}");
  88. string code = sb.ToString();
  89. return code;
  90. }
  91. }

2. DynamicCodeWindow

简单的编辑器扩展,不太重要。基本上就是获取文本然后调用DynamicCodeHelper.ExcuteDynamicCode

  1. #if UNITY_EDITOR_WIN
  2. using UnityEditor;
  3. using UnityEngine;
  4. /// <summary>
  5. /// 字符串编译成DLL载入,只在编辑器中使用
  6. /// </summary>
  7. public class DynamicCodeWindow : EditorWindow
  8. {
  9. // 生成在 ..\Client\Client\Temp\DynamicCode\DynamicCodeTemp.dll
  10. public const string OUTPUT_DLL_DIR = @"Temp\DynamicCode";
  11. [MenuItem("TestTool/DynamicRun")]
  12. private static void Open()
  13. {
  14. GetWindow<DynamicCodeWindow>();
  15. }
  16. private static DynamicCodeHelper _instance;
  17. private static DynamicCodeHelper Helper
  18. {
  19. get
  20. {
  21. if (_instance == null)
  22. {
  23. _instance = new DynamicCodeHelper();
  24. }
  25. return _instance;
  26. }
  27. }
  28. private bool isUseTextAsAllContent;
  29. private string content = @"Debug.Log(""Hello"");";
  30. private void OnGUI()
  31. {
  32. isUseTextAsAllContent = EditorGUILayout.ToggleLeft("完全使用文本作为编译内容(手动添加using等)", isUseTextAsAllContent);
  33. content = EditorGUILayout.TextArea(content, GUILayout.Height(200));
  34. if (GUILayout.Button("执行代码"))
  35. {
  36. Run(content, isUseTextAsAllContent);
  37. }
  38. if (GUILayout.Button("重置内容"))
  39. {
  40. if (isUseTextAsAllContent)
  41. {
  42. content = @"using System;
  43. using UnityEngine;
  44. public class DynamicCode {
  45. public void CodeExecute() {
  46. Debug.Log(""Hello"");
  47. }
  48. }";
  49. }
  50. else
  51. {
  52. content = @"Debug.Log(""Hello"");";
  53. }
  54. }
  55. if (GUILayout.Button("新建/打开缓存目录"))
  56. {
  57. if (!System.IO.Directory.Exists(OUTPUT_DLL_DIR))
  58. {
  59. System.IO.Directory.CreateDirectory(OUTPUT_DLL_DIR);
  60. }
  61. System.Diagnostics.Process.Start(OUTPUT_DLL_DIR);
  62. }
  63. }
  64. private static void Run(string code, bool isUseTextAsAllContent)
  65. {
  66. ColorDebug("[DynamicCode] Start......");
  67. string codetmp = code;
  68. Helper.ExcuteDynamicCode(codetmp, isUseTextAsAllContent);
  69. ColorDebug("[DynamicCode] End......");
  70. }
  71. public static void ColorDebug(string content)
  72. {
  73. Debug.Log(string.Format("<color=#ff8400>{0}</color>", content));
  74. }
  75. }
  76. #endif

[Unity] 编辑器运行中动态编译执行C#代码的更多相关文章

  1. 在C#中动态编译T4模板代码

    转: http://www.wxzzz.com/1438.html 资料: https://cnsmartcodegenerator.codeplex.com/SourceControl/latest ...

  2. 转: angularjs 指令中动态编译的方法(适用于有异步请求的情况) 内嵌指令无效

    angular的坑很多 例子: 在directive的link中有一个$http请求,当请求完成后根据返回的值动态做element.append('......');这个操作, 能显示没问题,可问题是 ...

  3. Python中动态编译函数compile(source, filename, mode, ......)参数filename的作用是什么?

    动态编译函数compile调用语法如下: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1) 其中的fi ...

  4. 在 Linux/windows下 命令行中使用和执行 PHP 代码[交互式php]

    [注释]在ubuntu下,升级php到7.1版本,虽然提示的是Interactive mode enabled, 但实际上可以直接书写命令,和interactive shell效果一样. 一:wind ...

  5. 使用PyQt(Python+Qt)+动态编译36行代码实现的计算器

    PyQt是基于跨平台的图形界面C++开发工具Qt加Python包装的一个GPL软件(GPL是GNU General Public License的缩写,是GNU通用公共授权非正式的中文翻译),Qt基于 ...

  6. JAVA中动态编译的简单使用

    一.引用库 pom文件中申明如下: <dependencies> <!-- https://mvnrepository.com/artifact/junit/junit --> ...

  7. (转)高性能JavaScript:加载和运行(动态加载JS代码)

    浏览器是如何加载JS的 当浏览器遇到一个<script>标签时,浏览器首先根据标签src属性下载JavaScript代码,然后运行JavaScript代码,继而继续解析和翻译页面.如果需要 ...

  8. 在 Linux 命令行中使用和执行 PHP 代码

    PHP是一个开源服务器端脚本语言,最初这三个字母代表的是“Personal Home Page”,而现在则代表的是“PHP:Hypertext Preprocessor”,它是个递归首字母缩写.它是一 ...

  9. 测试博文中添加可执行JS代码

    昨天申请开通了博客园的JS权限,今天来看看效果. 测试执行JS 测试执行JS // 运行

随机推荐

  1. IE8中li添加float属性,中英数字混合BUG

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  2. C# 同步 异步 回调 状态机 async await Demo

    源码 https://gitee.com/s0611163/AsyncAwaitDemo 为什么会研究这个? 我们项目的客户端和服务端通信用的是WCF,我就想,能不能用异步的方式调用WCF服务呢?或者 ...

  3. Go 变量及基本数据类型2

    #### Go 变量及基本数据类型(二)今天学习一下基本数据类型,包括整数类型,小数类型,字符类型##### 整数类型用来存放整数数值的, 如0,1,-10,2222等; 整数型的类型有: 类型 有无 ...

  4. iPhone12和iPhone12pro的区别有什么?

    阅读全部 说法一 iPhone12和iPhone12pro的区别有:颜色.价格.运行内存.拍照.屏幕最高亮度.电池容量.材质.重量等,具体对比如下: 颜色:iPhone12五色可选,青春绚丽:iPho ...

  5. Kubernetes 证书默认1年过期时间修改

    使用过的kubeadm搭建K8s集群的朋友知道,默认自动生成的证书有效期只有 1 年,因此需要每年手动更新一次证书,这种形式显然对实际生产环境来说很不友好:因此下面教给大家修改这个过期时间的终极方法. ...

  6. ERNIE代码解析

    原创作者 |疯狂的Max ERNIE代码解读 考虑到ERNIE使用BRET作为基础模型,为了让没有基础的NLPer也能够理解代码,笔者将先为大家简略的解读BERT模型的结构,完整代码可以参见[1]. ...

  7. 有关softmax函数代码实现的思考

    有关softmax函数代码实现的思考 softmax函数 def softmax2(x): if x.ndim == 2: x = x.T x = x - np.max(x, axis=0) y = ...

  8. django之js模板插件artTemplate的使用

    安装: 方式1:artTemplate模板源码下载地址:https://aui.github.io/art-template/zh-cn/index.html 方式2:使用node.js进行安装:np ...

  9. plsql 数据库事件触发器

    --4.数据库事件触发器 需要超管的权限 /* 数据库事件触发器有数据库级和模式级两种. 前者定义在整个数据库上,触发事件是数据库事件,如数据库的启动.关闭,对数据库的登录或退出. 后者定义在模式上, ...

  10. 如何使用自对弈强化学习训练一个五子棋机器人Alpha Gobang Zero

    前言 2016年3月,Alpha Go 与围棋世界冠军.职业九段棋手李世石进行围棋人机大战,以4比1的总比分获胜,在当时引起了轩然大波.2017年10月,谷歌公布了新版五子棋程序 AlphaGo Ze ...