如果開發的時候按之前的一個Hotfix工程,一個Unity工程,開發會很麻煩。因此我們可以把Hotfix部分的代碼放入到Unity當中,並增加一個標記,到時候把這些代碼整合成一個dll文件即可。
具體思路
ILRuntime的原理就是熱更代碼單獨生成一個dll文件,然後Unity啓動的時候讀取這個dll文件,熱更新的時候只需要熱更dll文件即可。之前的Hotfix工程就是把工程內的代碼導成dll文件,我們可以將這些代碼放入到Unity當中,使用一個標記來和非熱更代碼區分開來,比如在文件夾或文件上加@Hotfix的後綴。然後我們可以用一個打dll的工具,把這熱更的代碼文件打成dll即可。這樣操作之後就不需要兩個工程來回切,方便了開發。
之前用Hotfix工程生成hotfix.dll的時候,是引用了Assembly-CSharp.dll文件,而當我們把Hotfix代碼放入Unity中後,Assembly-CSharp.dll中也會包含這些代碼,所以我們打hotfix.dll的時候不能使用它了。需要我們自己先將Unity中沒有@Hotfix標記的代碼編譯成一個unity.dll文件,然後利用這個dll和標記了@Hotfix的代碼編譯成我們需要的hotfix.dll文件,即可。
整合項目
首先我們把Hotfix的腳本放到Unity當中,然後添加@Hotfix後綴用來做區分,如圖
打DLL工具
然後去製作我們的打dll工具,新建一個控制檯應用叫BuildDllTool
我們需要的參數有,Unity Assets目錄的路徑,生成的dll文件的導出路徑,Unity一些系統dll文件的路徑(例如UnityEngine.dll等),編譯配置路徑(這一塊內容還不是很瞭解,因爲也是網上找的代碼,後面在研究研究。文件這裏先分享下: 編譯配置 提取碼: xub3 ),編譯選項。代碼如下
using System;
using System.Threading;
namespace BuildDllTool
{
class Program
{
static void Main(string[] args)
{
if (args.Length == 5)
{
Console.WriteLine("Unity Asset 路徑:" + args[0]);
Console.WriteLine("dll 輸出路徑:"+ args[1]);
Console.WriteLine("Unity 系統的 dll 文件路徑:" + args[2]);
Console.WriteLine("編譯配置路徑:" + args[3]);
Console.WriteLine("編譯選項:" + args[4]);
var result = ScriptBiuldToDll.Build(args[0], args[1], args[2], args[3], args[4]);
Console.WriteLine("退出");
}
else
{
Console.WriteLine("參數不匹配!");
Console.WriteLine("退出!");
}
Thread.Sleep(500);
System.Diagnostics.Process.GetCurrentProcess().Close();
}
}
}
編譯dll的代碼如下,
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
namespace BuildDllTool
{
class ScriptBiuldToDll
{
public enum BuildStatus
{
Success = 0,
Fail
}
static public BuildStatus Build(string unityAssetsPath, string dllPath, string unitySystemDllPath, string compilerDirectoryPath, string define)
{
//編譯項目的base.dll
Console.WriteLine("準備編譯dll 10%");
//清空dll的存放文件夾
if (Directory.Exists(dllPath))
{
Directory.Delete(dllPath, true);
}
Directory.CreateDirectory(dllPath);
//Unity 中存放腳本的文件
string[] searchPath = new string[] { "Scripts", "ThridPartys" };
for (int i = 0; i < searchPath.Length; i++)
{
searchPath[i] = Path.Combine(unityAssetsPath, searchPath[i]);
}
//找出所有的腳本
List<string> files = new List<string>();
foreach (var s in searchPath)
{
var fs = Directory.GetFiles(s, "*.*", SearchOption.AllDirectories).ToList();
var _fs = fs.FindAll(f =>
{
var _f = f.ToLower();
var exten = Path.GetExtension(_f);
if ((!_f.Contains("editor")) && (exten.Equals(".dll") || exten.Equals(".cs")))
{
return true;
}
return false;
});
files.AddRange(_fs);
}
files = files.Distinct().ToList();
for (int i = 0; i < files.Count; i++)
{
files[i] = files[i].Replace('/', '\\').Trim('\\');
}
Console.WriteLine("開始整理script 20%");
//項目中用到的dll
var refDlls = files.FindAll(f => f.EndsWith(".dll"));
//unity內腳本,用於先生成unity的dll文件,供hotfix.dll編譯用
var unityCs = files.FindAll(f => !f.EndsWith(".dll") && !f.Contains("@Hotfix"));
//熱更腳本,用於生成hotfix.dll
var hotfixCs = files.FindAll(f => !f.EndsWith(".dll") && f.Contains("@Hotfix"));
//臨時目錄
var tempDirect = "d:/bd_temp";
if (Directory.Exists(tempDirect))
{
Directory.Delete(tempDirect, true);
}
Directory.CreateDirectory(tempDirect);
//除去不需要引用的dll
for (int i = refDlls.Count - 1; i >= 0; i--)
{
var str = refDlls[i];
if (str.Contains("Editor") || str.Contains("iOS") || str.Contains("Android") || str.Contains("StreamingAssets"))
{
refDlls.RemoveAt(i);
}
}
//拷貝dll到臨時目錄
for (int i = 0; i < refDlls.Count; i++)
{
var copyto = Path.Combine(tempDirect, Path.GetFileName(refDlls[i]));
File.Copy(refDlls[i], copyto, true);
refDlls[i] = copyto;
}
//添加系統的dll
refDlls.Add("System.dll");
refDlls.Add("System.Core.dll");
refDlls.Add("System.XML.dll");
refDlls.Add("System.Data.dll");
//添加Unity系統的dll
string[] dllPaths = unitySystemDllPath.Split(',');
foreach (string dll in dllPaths)
{
var dllfile = Directory.GetFiles(dll, "*.dll", SearchOption.AllDirectories);
foreach (var d in dllfile)
{
if (Path.GetFileNameWithoutExtension(d).StartsWith("Assembly-CSharp"))
{
continue;
}
refDlls.Add(d);
}
}
var unityDllPath = dllPath + "unity.dll";
Console.WriteLine("複製編譯代碼 30%");
//拷貝非熱更的cs文件到臨時目錄
for (int i = 0; i < unityCs.Count; i++)
{
var copyto = Path.Combine(tempDirect, Path.GetFileName(unityCs[i]));
int count = 1;
while (File.Exists(copyto))
{
//爲解決mono.exe error: 文件名太長問題
copyto = copyto.Replace(".cs", "") + count + ".cs";
count++;
}
File.Copy(unityCs[i], copyto);
unityCs[i] = copyto;
}
//檢測dll,移除無效dll
for (int i = refDlls.Count - 1; i >= 0; i--)
{
var r = refDlls[i];
if (File.Exists(r))
{
var fs = File.ReadAllBytes(r);
try
{
var assm = Assembly.Load(fs);
}
catch
{
Console.WriteLine("移除無效的 dll :" + r);
refDlls.RemoveAt(i);
}
}
}
Console.WriteLine("[1/2]開始編譯 unity.dll 40%");
BuildStatus unityResult = BuildStatus.Success;
//編譯 unity.dll
try
{
unityResult = BuildDll(refDlls.ToArray(), unityCs.ToArray(), unityDllPath, compilerDirectoryPath, define);
}
catch (Exception e)
{
Console.WriteLine("unity.dll 編譯失敗:" + e);
throw;
}
Console.WriteLine("[2/2]開始編譯hotfix.dll 70%");
//將unity.dll加入
refDlls.Add(unityDllPath);
//編譯hotfix.dll
var hotfixDllPath = dllPath + "hotfix.dll";
BuildStatus hotfixResult = BuildStatus.Success;
try
{
hotfixResult = BuildDll(refDlls.ToArray(), hotfixCs.ToArray(), hotfixDllPath, compilerDirectoryPath, define);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
Console.WriteLine("清理臨時文件 95%");
Directory.Delete(tempDirect, true);
if (unityResult == BuildStatus.Success && unityResult == hotfixResult)
{
Console.WriteLine("編譯成功!");
return BuildStatus.Success;
}
else
{
Console.WriteLine("編譯失敗!");
return BuildStatus.Fail;
}
}
/// <summary>
/// 編譯dll
/// </summary>
static public BuildStatus BuildDll(string[] refAssemblies, string[] codefiles, string output, string compilerDirectoryPath, string define)
{
// 設定編譯參數,DLL代表需要引入的Assemblies
CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = false;
//在內存中生成
cp.GenerateInMemory = true;
//生成調試信息
if (define.IndexOf("IL_DEBUG") >= 0)
{
cp.IncludeDebugInformation = true;
}
else
{
cp.IncludeDebugInformation = false;
}
//cp.TempFiles = new TempFileCollection(".", true);
cp.OutputAssembly = output;
//warning和 error分開,不然各種warning當成error,改死你
cp.TreatWarningsAsErrors = false;
cp.WarningLevel = 1;
//編譯選項
cp.CompilerOptions = "-langversion:latest /optimize /unsafe /define:" + define;
if (refAssemblies != null)
{
foreach (var d in refAssemblies)
{
cp.ReferencedAssemblies.Add(d);
}
}
// 編譯代理
CodeDomProvider provider;
if (string.IsNullOrEmpty(compilerDirectoryPath))
{
provider = CodeDomProvider.CreateProvider("CSharp");
}
else
{
provider = CodeDomProvider.CreateProvider("cs", new Dictionary<string, string> {
{ "CompilerDirectoryPath", compilerDirectoryPath }
});
}
CompilerResults cr = provider.CompileAssemblyFromFile(cp, codefiles);
if (true == cr.Errors.HasErrors)
{
StringBuilder sb = new StringBuilder();
foreach (CompilerError ce in cr.Errors)
{
sb.Append(ce.ToString());
sb.Append(Environment.NewLine);
}
Console.WriteLine(sb);
}
else
{
return BuildStatus.Success;
}
return BuildStatus.Fail;
}
}
}
然後我們將其生成爲exe程序,放到Unity項目中(例如:Unity項目/Tools/BuildHotfixDll文件夾下)。然後前面的編譯配置也可放在該文件夾下。
Unity中調用
然後我們在Editor下添加菜單欄,用來調用我們的exe文件生成dll文件即可。我們在Editor目錄下創建ILRuntimeBuildWindow.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace EditorTool
{
public class ILRuntimeBuildWindow : EditorWindow
{
Vector2 mLogScroll;
string mLogs = string.Empty;
public void OnGUI()
{
GUILayout.BeginVertical();
{
GUILayout.Label("腳本打包");
GUILayout.Space(5);
GUILayout.BeginHorizontal();
{
if (GUILayout.Button("1.編譯 Hotfix.dll", GUILayout.Width(200), GUILayout.Height(30)))
{
mLogs = string.Empty;
string outpath = Application.streamingAssetsPath + "/hotfix_dll/";
BuildDLL(Application.dataPath + "/", outpath);
AssetDatabase.Refresh();
}
}
GUILayout.EndHorizontal();
if (!string.IsNullOrEmpty(mLogs))
{
mLogScroll = EditorGUILayout.BeginScrollView(mLogScroll, GUILayout.Height(400));
mLogs = EditorGUILayout.TextArea(mLogs);
EditorGUILayout.EndScrollView();
}
}
GUILayout.EndVertical();
}
public void BuildDLL(string codeSource, string export, Action compileFinishedCallback = null, Action<string> outPutReceivedEvent = null)
{
string exePath = Environment.CurrentDirectory + "/Tools/BuildHotfixDll/BuildDllTool.exe";
if (!File.Exists(exePath))
{
Debug.Log("編譯工具不存在!");
return;
}
//這裏是引入unity所有引用的dll
var u3dUI = string.Format(@"{0}\UnityExtensions\Unity", EditorApplication.applicationContentsPath);
var u3dEngine = string.Format(@"{0}\Managed\UnityEngine", EditorApplication.applicationContentsPath);
string libDll = Environment.CurrentDirectory + "/Library/ScriptAssemblies";
string dllPath = u3dUI + "," + u3dEngine + "," + libDll;
if (Directory.Exists(u3dUI) == false || Directory.Exists(u3dEngine) == false || Directory.Exists(libDll) == false)
{
EditorUtility.DisplayDialog("提示", "dll文件目錄不存在,請修改ILRuntimeBuildWindow類中,u3dUI u3dEngine libDll的dll目錄", "OK");
return;
}
//編譯配置文件目錄
string compilerDirectoryPath = Environment.CurrentDirectory + "/Tools/BuildHotfixDll/roslyn";
var define = GetScriptingDefineSymbols();
//執行exe文件,傳遞參數
var p = new Process();
p.EnableRaisingEvents = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.FileName = exePath;
p.StartInfo.Arguments = string.Format("{0} {1} {2} {3} {4}", codeSource, export, dllPath, compilerDirectoryPath, define);
p.Exited += (sender, e) =>
{
compileFinishedCallback?.Invoke();
};
p.OutputDataReceived += (sender, e) =>
{
mLogs += (e.Data + "\n");
};
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.StandardOutputEncoding = Encoding.GetEncoding("gb2312");
p.Start();
p.BeginOutputReadLine();
p.WaitForExit();
EditorUtility.ClearProgressBar();
}
//獲取編譯選項
string GetScriptingDefineSymbols()
{
List<string> validDefines = new List<string>();
foreach (var define in EditorUserBuildSettings.activeScriptCompilationDefines)
{
if (!define.Contains("UNITY_EDITOR"))
{
validDefines.Add(define);
}
}
return string.Join(";", validDefines);
}
}
}
然後將其加入菜單欄中調用即可
using UnityEditor;
namespace EditorTool
{
public class EditorToolMenu
{
[MenuItem("Tools/Build Hotfix Dll")]
public static void ExecuteBuildDLL()
{
var window = (ILRuntimeBuildWindow)EditorWindow.GetWindow(typeof(ILRuntimeBuildWindow), false, "Build Hotfix Dll");
window.Show();
}
}
}
選擇菜單欄Tool ->Build Hotfix Dll打開我們的工具窗口,點擊編譯即可。編譯成功如下
注意這樣打出的dll是沒有pdb文件的,因此我們可以在ILRuntimeHelp類中讀取dll的代碼處,去掉讀取pdb的部分即可。