惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 野生西瓜

[Unity 杂货铺] SpriteAtlas 和 SBP 打包 [Unity 杂货铺] 游戏字体选择 - 野生西瓜 [Unity 杂货铺] 游戏项目的结构规划与初始化 [Unity 杂货铺] Git 配置与实践 [Unity 杂货铺] 引擎版本的选择 [Unity] 基础寻路算法 - 环境搭建 [Unity] 引擎脚本相关的字符串优化 [Unity] 基础寻路算法 - 代码实践 [Unity] 资源工作流程 - AssetPostprocessor [Unity] 资源工作流程 - ScriptedImporter [Unity] 资源工作流程 - 辅助工具 [Lua游戏AI开发指南] 笔记零 - 框架搭建 [GAME] [Civilization] 文明6字体及字体大小修改 [Unity] 在软件标题栏显示工作路径 [GAMEDEV] 个人开发如何找到合适的图片素材? [施工中] 博客导航 2021 的书 [BACKUP] Visual Studio Code 配置 [theHunterCOTW] 猎人荒野的召唤-一点资料
[Unity] 编辑器运行中动态编译执行C#代码
野生西瓜 · 2021-07-26 · via 博客园 - 野生西瓜

(一)问题

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

(二)提前备注

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

(三)执行效果

20210726230729.png

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

20210726230936.png

编译生成的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