Unity 热更新的几个方案

 • 

刚刚接触到热更新这块,感觉作为一个独立开发者的尊严和荣光都没了.....
以下是几个思路。

1. 脚本存为 TextAsset,和其余所有内容一起放到 Asset Bundle 里。客户端拿到 Asset Bundle 后用 Reflection 创建 type。

Link: http://docs.unity3d.com/Manual/scriptsinassetbundles.html

AssetBundle bundle = www.assetBundle;  
// Load the TextAsset object
TextAsset txt = bundle.Load("myBinaryAsText", typeof(TextAsset)) as TextAsset;  
// Load the assembly and get a type (class) from it
var assembly = System.Reflection.Assembly.Load(txt.bytes);  
var type = assembly.GetType("MyClassDerivedFromMonoBehaviour");  
// Instantiate a GameObject and add a component with the loaded class
GameObject go = new GameObject();  
go.AddComponent(type);  

好处:方便。
坏处:不能用 Prefab 存脚本的 Public 属性值,因为所有的脚本都要在读取 Asset Bundle 后重新挂载到 GameObject 上去。

2. Downloading the Hydra

Link: http://angryant.com/2010/01/05/downloading-the-hydra/
Angry Ant 在 10 年的办法。大体就是预先编译好 DLL,然后客户端去拿 DLL,然后运行时的时候加载。
好处:脚本不用改为 TextAsset
坏处:仍然不能保留 Scripts 在 Scene / Prefab 里面的 Reference,即:如果你预先通过 Asset Bundle 加载了一个场景到内存然后再加载一个包含场景需要脚本的 DLL 到内存(或者顺序相反,反正结果一样),这个时候 LoadScene 的结果是场景所有的 Script 仍然会显示为 Missing。Assembly.Load () 是可以加载到运行时,但是不代表 Unity 能认出这个 type。
相关讨论

3. Runtime Evaluating / Compiling CSharp

这个比较奇技淫巧,而且不支持 AOT,不过 Android 上就无所谓了。
具体有好几种思路:
第一种,用 Mono.CSharp.Run / Mono.CSharp.Evaluate

int cnt = 0;  
while (cnt < 2)  
{
    // this needs to be run twice
    foreach (System.Reflection.Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) {
        if (assembly == null) {
            UnityEngine.Debug.Log ("Null Assembly");
            continue;
        }
        UnityEngine.Debug.Log (assembly);
        try {
            Mono.CSharp.Evaluator.ReferenceAssembly (assembly);
        } catch (NullReferenceException e) {
            UnityEngine.Debug.Log ("Bad Assembly");
        }
    }
    cnt++;
}
Mono.CSharp.Evaluator.Run("using UnityEngine;");  
Mono.CSharp.Evaluator.Evaluate ("1+2;");  

第二种,用 Mono.CSharp.Compile

var evaluator = new Evaluator(  
                new CompilerSettings(),
                new Report(new ConsoleReportPrinter()));

            // Make it reference our own assembly so it can use IFoo
            evaluator.ReferenceAssembly(typeof(IFoo).Assembly);

            // Feed it some code
            evaluator.Compile(
                @"
    public class Foo : MonoCompilerDemo.IFoo
    {
        public string Bar(string s) { return s.ToUpper(); }
    }");

            for (; ; )
            {
                string line = Console.ReadLine();
                if (line == null) break;

                object result;
                bool result_set;
                evaluator.Evaluate(line, out result, out result_set);
                if (result_set) Console.WriteLine(result);
            }

参见:http://blog.davidebbo.com/2012/02/quick-fun-with-monos-csharp-compiler-as.html

第三种,用System.CodeDom.Compiler / System.Reflection.Emit

CSharpCodeProvider provider = new CSharpCodeProvider();  
            CompilerParameters parameters = new CompilerParameters();

            foreach (System.Reflection.Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                //Dbg.Log("refer: {0}", assembly.FullName);
                if( assembly.FullName.Contains("Cecil") || assembly.FullName.Contains("UnityEngine") )
                    continue;
                parameters.ReferencedAssemblies.Add(assembly.Location);
            }
            parameters.IncludeDebugInformation = true;
            // Reference to System.Drawing library
//            parameters.ReferencedAssemblies.Add ("System.dll");
//            parameters.ReferencedAssemblies.Add ("System.Core.dll");
            parameters.ReferencedAssemblies.Add (typeof(MonoBehaviour).Assembly.Location);
            //parameters.ReferencedAssemblies.Add("/Applications/Unity/Unity.app/Contents/Frameworks/Managed/UnityEngine.dll");
            // True - memory generation, false - external file generation
            parameters.OutputAssembly = "AutoGen.dll";
            parameters.GenerateInMemory = false;
            // True - exe file generation, false - dll file generation
            parameters.GenerateExecutable = false;

            CompilerResults results = provider.CompileAssemblyFromSource(parameters,scripts);
            if (results.Errors.HasErrors)
            {
                foreach (CompilerError error in results.Errors)
                {
                    UnityEngine.Debug.LogError("Error " + error.ErrorNumber + error.ErrorText);
                }
            }

上面是 CodeDom 的例子,不过 Unity 似乎阉割了这个部分,一直报错,我没有在这个方向再往下看。(Edit : http://www.justzht.com/c-repl-fun/) 至于 CodeDom 和 Emit 的对比,参见:http://stackoverflow.com/questions/2366921/reflection-emit-vs-codedom

以上所有 Runtime 的方法:
好处:可以传 string 执行方法
坏处:不够稳定,以及要自己造一套轮子

4. sLua / uLua / UniLua

好处:可以传 string 执行方法
坏处:效率低(不过可以接受啦),以及不是自己的轮子,有时候不是很好 Debug。