下面是详细的打包流程、代码示例,以及生成版本文件的完整实现过程。此内容适用于 Unity 项目中使用 AssetBundles 打包并支持版本文件生成与热更新的场景。


1. 打包流程概述

  1. 配置资源文件

    • 将需要打包的资源组织到指定目录中。
    • 设置每个资源的 AssetBundle 名称。
  2. 打包 AssetBundles

    • 使用 Unity 提供的 BuildPipeline.BuildAssetBundles 方法,将资源打包成 .ab 文件。
  3. 生成版本文件

    • 记录所有打包生成的 .ab 文件及其校验值(MD5 或 SHA256)。
    • 生成一个 JSON 格式的版本文件,包含版本号、资源文件名及校验值。
  4. 上传资源和版本文件

    • 将打包好的 .ab 文件和版本文件上传到服务器。

2. 实现步骤

Step 1: 配置资源文件

在 Unity 中为资源设置 AssetBundle 名称:

  1. 选中需要打包的资源(例如:Prefab、Texture)。
  2. Inspector 面板中,设置 AssetBundle 字段为指定的名称。例如:
    • character_assets
    • environment_assets

Step 2: 编写打包脚本

使用 Unity 提供的 API 打包资源。

示例代码:打包 AssetBundles

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
using UnityEditor;
using UnityEngine;
using System.IO;
using System.Security.Cryptography;

public class AssetBundleBuilder
{
[MenuItem("Tools/Build AssetBundles")]
public static void BuildAllAssetBundles()
{
// 打包输出路径
string outputPath = Path.Combine(Application.dataPath, "../AssetBundles");
if (!Directory.Exists(outputPath))
Directory.CreateDirectory(outputPath);

// 打包所有 AssetBundle
BuildPipeline.BuildAssetBundles(outputPath, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);

// 生成版本文件
GenerateVersionFile(outputPath);
Debug.Log("AssetBundles and version file generated successfully!");
}

private static void GenerateVersionFile(string assetBundlePath)
{
// 获取所有打包文件
string[] files = Directory.GetFiles(assetBundlePath, "*", SearchOption.AllDirectories);
var versionData = new VersionData();

foreach (var file in files)
{
if (file.EndsWith(".manifest") || file.EndsWith(".meta")) continue; // 跳过无用文件

string fileName = Path.GetFileName(file);
string hash = CalculateMD5(file); // 计算 MD5

versionData.assetBundles[fileName] = hash;
}

// 保存版本文件
versionData.version = System.DateTime.Now.ToString("yyyyMMddHHmmss"); // 使用时间戳作为版本号
string versionFilePath = Path.Combine(assetBundlePath, "version.json");
File.WriteAllText(versionFilePath, JsonUtility.ToJson(versionData, true));
}

private static string CalculateMD5(string filePath)
{
using (var md5 = MD5.Create())
{
using (var stream = File.OpenRead(filePath))
{
var hash = md5.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}
}
}

[System.Serializable]
public class VersionData
{
public string version;
public System.Collections.Generic.Dictionary<string, string> assetBundles = new System.Collections.Generic.Dictionary<string, string>();
}

Step 3: 上传资源到服务器

将以下内容上传到服务器:

  • 所有打包生成的 .ab 文件。
  • version.json 文件。

常见的上传方式:

  • 使用 FTP 工具(如 FileZilla)手动上传。
  • 配合自动化脚本将文件上传到服务器或存储服务(如 AWS S3、GitHub Releases)。

3. 版本文件的结构和内容

版本文件(version.json)示例:

1
2
3
4
5
6
7
{
"version": "20250123090000",
"assetBundles": {
"character_assets": "f2a29d0c08f2c4a3d1d7ec5b6e8b8a34",
"environment_assets": "e1d4f7600c1f7b4a9e5f9e2b123f9a8d"
}
}

说明:

  • version 表示版本号,用时间戳生成(如 yyyyMMddHHmmss 格式)。
  • assetBundles 是一个字典,存储每个资源文件名及其对应的 MD5 校验值。

4. 客户端检查版本和热更新流程

在客户端,以下是资源检查和下载的基本逻辑:

  1. 获取版本文件

    • 客户端启动时,下载服务器的 version.json
    • 如果客户端不存在本地版本文件,则直接下载所有资源。
  2. 比较版本号

    • 如果服务器版本号高于本地版本号,开始检查资源文件。
  3. 校验资源文件

    • 根据服务器的版本文件中的校验值,逐一检查本地资源文件是否完整。
    • 如果文件丢失或校验值不匹配,则下载该文件。
  4. 下载更新资源

    • 将需要更新的 .ab 文件从服务器下载到本地。
    • 更新本地的版本文件。

5. 示例代码:客户端版本检查与更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
using UnityEngine;
using System.Collections;
using System.IO;
using UnityEngine.Networking;

public class HotUpdateManager : MonoBehaviour
{
private string serverVersionUrl = "https://example.com/version.json"; // 服务器版本文件 URL
private string localVersionPath = Application.persistentDataPath + "/version.json";
private string serverAssetBaseUrl = "https://example.com/"; // 资源下载根目录

void Start()
{
StartCoroutine(CheckForUpdates());
}

IEnumerator CheckForUpdates()
{
// 获取服务器版本文件
UnityWebRequest versionRequest = UnityWebRequest.Get(serverVersionUrl);
yield return versionRequest.SendWebRequest();

if (versionRequest.result != UnityWebRequest.Result.Success)
{
Debug.LogError("Failed to fetch version.json: " + versionRequest.error);
yield break;
}

string serverVersionContent = versionRequest.downloadHandler.text;
var serverVersionData = JsonUtility.FromJson<VersionData>(serverVersionContent);

// 检查本地版本文件
VersionData localVersionData = null;
if (File.Exists(localVersionPath))
{
string localVersionContent = File.ReadAllText(localVersionPath);
localVersionData = JsonUtility.FromJson<VersionData>(localVersionContent);
}

// 检查版本号和资源
if (localVersionData == null || serverVersionData.version != localVersionData.version)
{
Debug.Log("New version detected, updating assets...");
foreach (var asset in serverVersionData.assetBundles)
{
string localFilePath = Path.Combine(Application.persistentDataPath, asset.Key);
if (!File.Exists(localFilePath) || GetMD5(localFilePath) != asset.Value)
{
yield return StartCoroutine(DownloadAsset(asset.Key));
}
}

// 更新本地版本文件
File.WriteAllText(localVersionPath, serverVersionContent);
Debug.Log("Assets updated successfully!");
}
else
{
Debug.Log("Assets are up-to-date.");
}
}

IEnumerator DownloadAsset(string assetName)
{
string url = serverAssetBaseUrl + assetName;
UnityWebRequest request = UnityWebRequest.Get(url);
yield return request.SendWebRequest();

if (request.result == UnityWebRequest.Result.Success)
{
string localPath = Path.Combine(Application.persistentDataPath, assetName);
File.WriteAllBytes(localPath, request.downloadHandler.data);
Debug.Log($"Downloaded: {assetName}");
}
else
{
Debug.LogError($"Failed to download {assetName}: {request.error}");
}
}

private string GetMD5(string filePath)
{
using (var md5 = System.Security.Cryptography.MD5.Create())
using (var stream = File.OpenRead(filePath))
{
var hash = md5.ComputeHash(stream);
return System.BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}
}

通过以上流程,你可以实现完整的热更新功能,包括打包资源、生成版本文件、检查和下载更新资源。

HybridCLR 程序集打包成ab并加载

Unity HybridCLR 热更新程序集打包与加载流程

HybridCLR 是 Unity 的一种轻量级跨平台解决方案,支持加载 AOT(Ahead Of Time)和解释执行的程序集,适用于游戏热更新。以下是完整的 打包流程加载流程


1. 热更新程序集打包流程

1.1 配置热更新程序集工程

步骤 1: 创建独立的热更新工程

  • 在 Unity 项目中,创建一个独立的 C# 项目用于存放热更新代码。
  • 确保该工程编译为 .dll 文件(Class Library 项目)。
  • 使用 .NET Framework 4.x.NET Standard 2.1(与 Unity 项目一致)。

步骤 2: 添加必要引用

  • 引用 Unity 的程序集文件:
    • UnityEngine.dll
    • UnityEngine.CoreModule.dll
    • 这些文件可在 Unity 安装目录的 Editor/Data/ManagedLibrary/ScriptAssemblies 下找到。

步骤 3: 编写热更新代码

  • 将需要动态更新的逻辑代码放入热更新工程。
  • 避免引用 UnityEditor 命名空间。
  • 特别注意:在 HybridCLR 中,一部分代码会运行在 AOT 模式,因此需要合理规划哪些代码进入热更新工程,哪些留在主工程中。

1.2 编译与输出程序集

步骤 1: 编译热更新程序集

  • 使用 Visual Studio 或 Rider 将热更新工程编译为 .dll 文件。
  • 输出文件如:
    • HotUpdate.dll(主程序集)
    • HotUpdate.pdb(符号文件,用于调试)。

步骤 2: 标记为热更新程序集

  • 在 Unity 的主工程中,创建一个文件夹(如 Assets/HotUpdateAssemblies),将编译好的 .dll 文件放入该目录。

步骤 3: 使用 AssetBundle 打包

  • 在 Unity 中创建一个脚本,用于将这些 .dll 文件打包成 AssetBundle:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    using UnityEditor;
    using UnityEngine;

    public class AssetBundleBuilder
    {
    [MenuItem("Tools/Build AssetBundles")]
    public static void BuildHotUpdateAssemblies()
    {
    string outputPath = "Assets/StreamingAssets/HotUpdateBundles";
    if (!System.IO.Directory.Exists(outputPath))
    System.IO.Directory.CreateDirectory(outputPath);

    AssetBundleBuild[] buildMap = new AssetBundleBuild[1];
    buildMap[0] = new AssetBundleBuild
    {
    assetBundleName = "hotupdateassemblies",
    assetNames = new[] { "Assets/HotUpdateAssemblies/HotUpdate.dll" }
    };

    BuildPipeline.BuildAssetBundles(outputPath, buildMap, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);
    Debug.Log("Hot update AssetBundle built successfully!");
    }
    }
  • 这会将 HotUpdate.dll 打包到一个名为 hotupdateassemblies 的 AssetBundle 中。

步骤 4: 上传到资源服务器

  • 将打包生成的 AssetBundle(如 hotupdateassemblies) 上传到远程服务器(如 GitHub Releases、CDN、阿里云 OSS 等),以便运行时下载。

2. 热更新程序集加载流程

2.1 配置 HybridCLR

步骤 1: 安装 HybridCLR

  • 在 Unity 项目中,按照 HybridCLR 的官方文档安装 HybridCLR 插件。
  • 安装后会生成以下文件和配置:
    • HybridCLR/HotUpdateAssemblies 文件夹(存放热更新程序集)。
    • HybridCLR 配置文件(设置热更新相关参数)。

步骤 2: 配置 AOT 元数据

  • AOT 模式需要将部分元数据文件(.dll)打包到游戏中。
  • HybridCLR/HotUpdateAssemblies 文件夹中,放入所有热更新相关的 .dll 文件。

2.2 加载热更新程序集

步骤 1: 下载 AssetBundle

  • 游戏启动时,从远程服务器下载最新的 AssetBundle 文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System.Collections;
using UnityEngine;

public class HotUpdateManager : MonoBehaviour
{
private const string AssetBundleURL = "https://your-server.com/hotupdateassemblies";

IEnumerator Start()
{
// 下载 AssetBundle
using var www = UnityEngine.Networking.UnityWebRequestAssetBundle.GetAssetBundle(AssetBundleURL);
yield return www.SendWebRequest();

if (www.result != UnityEngine.Networking.UnityWebRequest.Result.Success)
{
Debug.LogError("Failed to download AssetBundle: " + www.error);
yield break;
}

// 加载 AssetBundle
var assetBundle = UnityEngine.Networking.DownloadHandlerAssetBundle.GetContent(www);
if (assetBundle == null)
{
Debug.LogError("Failed to load AssetBundle.");
yield break;
}

// 提取 .dll 文件
TextAsset dllAsset = assetBundle.LoadAsset<TextAsset>("HotUpdate.dll");
if (dllAsset == null)
{
Debug.LogError("Failed to load HotUpdate.dll from AssetBundle.");
yield break;
}

LoadHotUpdateAssembly(dllAsset.bytes);
}

private void LoadHotUpdateAssembly(byte[] dllBytes)
{
var assembly = System.Reflection.Assembly.Load(dllBytes);
var type = assembly.GetType("HotUpdate.Program");
var method = type.GetMethod("Main");
method?.Invoke(null, null);
}
}

步骤 2: 加载热更新程序集

  • 使用 System.Reflection.Assembly.Load 加载 HotUpdate.dll,并调用其中的入口方法(如 Main())。

步骤 3: 切换逻辑到热更新代码

  • 在热更新程序集(如 HotUpdate.dll)中,处理游戏的逻辑更新。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    namespace HotUpdate
    {
    public class Program
    {
    public static void Main()
    {
    Debug.Log("HotUpdate assembly loaded successfully!");
    // 热更新逻辑处理...
    }
    }
    }

3. 总结

打包流程

  1. 在独立的 C# 项目中编写热更新代码,编译为 .dll 文件。
  2. .dll 文件放入 Unity 项目中并打包为 AssetBundle。
  3. 将打包的 AssetBundle 上传到远程服务器。

加载流程

  1. 游戏启动时,下载远程的 AssetBundle 文件。
  2. 解压并加载 .dll 文件到内存。
  3. 使用 Assembly.Load 加载程序集,并调用热更新代码入口(如 Main() 方法)。
  4. 切换游戏逻辑到热更新代码。

通过 HybridCLR,这种热更新流程实现了代码与资源的分离更新,提升了项目的维护性和灵活性。

热更程序集打包成