最近我们在项目中遇到了一个问题:经常需要修改游戏逻辑,如果每次修改都需要重新打包发布,那将会非常耗时,于是我们开始寻找解决方案。最后我们找到了 Unity HybridCLR 热更新技术,实现了游戏逻辑的热更新。
HybridCLR的介绍
HybridCLR 是 Unity 的一个插件,用于实现 C# 脚本的热更新。它将 C# 脚本编译成 dll,并将 dll 加载到内存中。当脚本被更新时,HybridCLR 会自动重新编译新的 dll,并将其加载到内存中,从而实现了热更新。
HybridCLR 的优点
使用 HybridCLR 可以实现实时热更新,同时减少了程序重启的成本。因为它只需要重新编译 dll 并加载到内存中,不需要将整个游戏程序重新运行。这就大大提高了开发效率,减少了开发时间。
HybridCLR 的实现方法
HybridCLR 的实现方法就是使用了 Mono.Cecil 这个工具来动态生成并修改 dll。Mono.Cecil 是一个专门用于操作 .NET 程序集的库,它可以读取、修改和写入程序集。我们可以使用 Mono.Cecil 读取原有 C# 脚本的程序集,并将其注入到目标程序集中。
实现代码
下面是一个简单的实现代码:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;
using HybridCLR;
public class HybridCLRManager : MonoBehaviour
{
private static HybridCLRManager instance;
public static HybridCLRManager Instance
{
get { return instance; }
}
private Dictionary<string, Assembly> assemblies = new Dictionary<string, Assembly>();
private void Awake()
{
instance = this;
}
public void LoadAssembly(string path)
{
string key = Path.GetFileNameWithoutExtension(path);
// 加载程序集
Assembly assembly = HybridCLRManager.LoadAssembly(path);
if (assembly != null)
{
assemblies[key] = assembly;
Debug.Log("Load assembly: " + key);
}
}
public void UnloadAssembly(string key)
{
if (assemblies.ContainsKey(key))
{
assemblies.Remove(key);
Debug.Log("Unload assembly: " + key);
}
}
public void CallMethod(string methodName)
{
// 调用方法
foreach (var assembly in assemblies.Values)
{
Type type = assembly.GetType("TestClass");
MethodInfo method = type.GetMethod(methodName);
if (method != null)
{
object instance = Activator.CreateInstance(type);
method.Invoke(instance, null);
Debug.Log("Call method: " + methodName);
}
}
}
private static Assembly LoadAssembly(string path)
{
byte[] assemblyBytes = File.ReadAllBytes(path);
Assembly assembly = Assembly.Load(assemblyBytes);
if (assembly != null)
{
HijackAssemblyResolver hijackResolver = new HijackAssemblyResolver(Path.GetDirectoryName(path));
HybridCLRAppDomain.Instance.AssemblyResolve += hijackResolver.Resolve;
}
return assembly;
}
}
// 当程序集加载失败时会回调 resolver,要求返回正确的程序集
class HijackAssemblyResolver
{
private string directory;
public HijackAssemblyResolver(string directory)
{
this.directory = directory;
}
public Assembly Resolve(object sender, ResolveEventArgs args)
{
Assembly assembly = null;
// 解析未找到的程序集
string assemblyName = new AssemblyName(args.Name).Name;
string path = Path.Combine(directory, assemblyName + ".dll");
Debug.LogWarning("HybridCLR resolve: " + path);
byte[] assemblyBytes = File.ReadAllBytes(path);
assembly = Assembly.Load(assemblyBytes);
return assembly;
}
}
// 测试用例
public class TestClass
{
public void TestMethod()
{
Debug.Log("This is a test method.");
}
}
在上面的代码中,我们定义了一个 HybridCLRManager,用于加载、卸载程序集和调用方法。
在 LoadAssembly 方法中,我们使用 Assembly.Load 方法加载程序集,并使用 HijackAssemblyResolver 作为程序集解析回调。它可以用于解决在不同平台运行时,不同版本的程序集无法自动加载的问题。
在 CallMethod 方法中,我们调用了 TestClass 类中的 TestMethod 方法。当热更程序集中没有 TestMethod 方法时,将调用默认程序集中的 TestMethod 方法。
注意:在使用 HybridCLR 技术时,我们需将要热更的代码放到一个类库工程中进行编译,然后将生成的 dll 拷贝到 Unity 中的指定位置。这里需要大家在编写时,将代码逻辑和热更新逻辑分离起来。
总结
使用 Unity HybridCLR 热更新技术,可以大大提高开发效率,同时减少开发时间和成本。通过使用 Mono.Cecil 和 Assembly.Load 来动态编译和加载程序集,实现了实时热更新的功能。如果大家对其它热更新方案感兴趣,可以参考我的其它博客。