修改BundleEditor

添加了热更之后,打包就有了普通打包和热更打包的区别,我们需要修改BundleEditorBuild方法

首先在BundleEditor添加NormalBuild方法,代表的是普通打包

1
2
3
4
5
6
[MenuItem("Tools/打AB包")]
public static void NormalBuild()
{
Build();
}

然后修改Build方法,给这个方法添加参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 public static void Build(bool hotfix = false,string abmd5Path = "",string hotCount = "1")
{
//...
for (int i = 0; i < oldNames.Length; i++)
{
AssetDatabase.RemoveAssetBundleName(oldNames[i], true);
EditorUtility.DisplayProgressBar("清除AB包名", "名字:" + oldNames[i],i*1.0f/oldNames.Length);
}
if (hotfix)//+++
{
ReadMD5Calc(abmd5Path,hotCount);
}
else
{
WriteABMD5();
}
AssetDatabase.SaveAssets();
//...

}

修改BuildApp脚本

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
[MenuItem("Build/标准包")]
public static void Build()
{
//打AB包
BundleEditor.NormalBuild();//调用NormalBuild而不是Build
SaveVersion(PlayerSettings.bundleVersion, PlayerSettings.applicationIdentifier);
//...
}
//...
#region 打包机调用打包PC版本
public static void BuildPC()
{
//打AB包
BundleEditor.NormalBuild();
//...
}
#endregion

#region 打包机调用打包安卓版本
public static void BuildAndroid()
{
//...
//打AB包
BundleEditor.NormalBuild();
//...
}
#endregion
#region 打包机调用打包IOS版本
public static void BuildIOS()
{
//...
//打AB包
BundleEditor.NormalBuild();
//...
}
#endregion

进行热更包打包

热更包打包的流程是:先打普通AB包——读取指定的ABMD5文件中的MD5信息——将旧MD5信息与新AB包的MD5比对,筛选出要打的包和新包——存储新包

修改BundleEditor,添加m_PacMD5Dic,用来储存旧ABMD5信息方便筛选。

添加ReadMD5Calc方法,这是生成热更包的主要方法

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
   /// <summary>
/// 储存指定ABMD5.bytes读出来的信息
/// </summary>
private static Dictionary<string,ABMD5Base> m_PacMD5Dic = new Dictionary<string,ABMD5Base>();
//...
/// <summary>
/// 读取比较ABMD5并生成热更包
/// </summary>
/// <param name="abmd5Path">旧ABMD5文件路径</param>
/// <param name="hotCount">热更次数</param>
static void ReadMD5Calc(string abmd5Path,string hotCount)
{
m_PacMD5Dic.Clear();
using(FileStream fs = new FileStream(abmd5Path,FileMode.Open, FileAccess.Read))
{
BinaryFormatter bf = new BinaryFormatter();
ABMD5 aBMD5 = bf.Deserialize(fs) as ABMD5;
foreach (ABMD5Base aBMD5Base in aBMD5.ABMD5List)
{
m_PacMD5Dic.Add(aBMD5Base.Name, aBMD5Base);
}
}

List<string> changeList = new List<string>();
DirectoryInfo directoryInfo = new DirectoryInfo(m_BundleTargetPath);
FileInfo[] abFiles = directoryInfo.GetFiles("*", SearchOption.AllDirectories);
for (int i = 0; i < abFiles.Length; i++)
{
//每次打AssetBundle时每个AssetBundle都会对应一个manifest文件,对应此Bundle的各种信息
//manifest文件只是帮助查看信息,并不放进热更包里。AssetBundle加载时用不到manifest
if (!abFiles[i].Name.EndsWith(".manifest") && !abFiles[i].Name.EndsWith(".meta"))
{
string name = abFiles[i].Name;
string md5 = MD5Manager.Instance.BuildFileMD5(abFiles[i].FullName);
ABMD5Base aBMD5Base = null;
//如果热更包有新的AB包,加入changeList当中
if (!m_PacMD5Dic.ContainsKey(name))
{
changeList.Add(name);
}
else
{
if (m_PacMD5Dic.TryGetValue(name, out aBMD5Base))
{
if (md5 != aBMD5Base.MD5)
{
changeList.Add(name);
}
}
}
}
}

CopyABAndGenerateConfig(changeList, hotCount);
}
/// <summary>
/// 拷贝热更包并且自动生成服务器配置表
/// </summary>
/// <param name="changeList">热更包列表</param>
/// <param name="hotCount">热更版本</param>
static void CopyABAndGenerateConfig(List<string> changeList,string hotCount)
{

}

热更包存储

在工程文件夹内新建Hot文件夹,用来存储热更包

将Hot文件夹上传SVN

BundleEditor中添加Hot文件夹路径

1
2
3
4
5
6
public class BundleEditor 
{
//...
private static string m_HotPath = Application.dataPath + "/../Hot/" + EditorUserBuildSettings.activeBuildTarget.ToString();
//...
}

BundleEditor中添加CopyABAndGenerateConfig方法和DeleteAllFile方法

每次生成热更包时都要清理旧的热更包,热更包的hotCount只在热更包对应的服务器URL指示里用到

配合Jenkins,旧的热更包在被清理之前就被归档了。

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
    static void CopyABAndGenerateConfig(List<string> changeList,string hotCount)
{
if (!Directory.Exists(m_HotPath))
{
Directory.CreateDirectory(m_HotPath);
}
DeleteAllFile(m_HotPath);
foreach (string abName in changeList)
{
if (!abName.EndsWith(".manifest"))
{
File.Copy(m_BundleTargetPath + "/" + abName, m_HotPath + "/" + abName);
}
}
}
/// <summary>
/// 删除指定文件目录下的所有文件,不递归删除文件夹
/// </summary>
/// <param name="fullPath">文件目录</param>
static void DeleteAllFile(string fullPath)
{
if(Directory.Exists(fullPath))
{
try
{
DirectoryInfo directory = new DirectoryInfo(fullPath);
FileInfo[] files = directory.GetFiles("*",SearchOption.AllDirectories);
foreach (FileInfo file in files)
{
if (file.Name.EndsWith(".meta"))
{
continue;
}
File.Delete(file.FullName);
}
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
}

完全删除文件的方法,可以在BuildApp文件内的DeleteDir方法查看

热更包配置表

热更配置表的内容

游戏每次更新,有固定的版本号,每个版本都可能会打一次或多次热更补丁(Patches),每个热更补丁有固定的补丁号(补丁号就是我们热更打包工具中“hotCount”指示的内容)

ServerInfo:服务器整体的数据结构,里面记录了各个版本的信息

VersionInfo:记录游戏产品的版本号,以及此版本下对应的补丁。如果有渠道服,还要记录不同渠道的版本信息

Patches:记录某个版本某个补丁的信息。包括此补丁的补丁号(Version)、对于此补丁的描述(Description)、所有的补丁文件(Files

Patch:记录具体的每个补丁的信息。包括此补丁对应的AB包名(Name)、下载地址(Url)、平台(Platform)、MD5码、AB包的大小(Size

ServerInfo数据结构

我们在ResFrame——Frames——ResourceFrm文件夹内新建Download文件夹 ,在其中新建ServerInfo脚本

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
using System.Collections.Generic;
using System.Xml.Serialization;

[System.Serializable]
public class ServerInfo
{
[XmlElement("GameVersion")]
public VersionInfo[] GameVersion;
}

[System.Serializable]
public class VersionInfo
{
[XmlAttribute]//不加参数会自动应用Version作为参数
public string Version;
[XmlElement]
public Patches[] Patches;
}

[System.Serializable]
public class Patches
{
[XmlAttribute]
public int Version;
[XmlAttribute]
public string Description;
[XmlElement]
public List<Patch> Files;
}

[System.Serializable]
public class Patch
{
[XmlAttribute]
public string Name;
[XmlAttribute]
public string Url;
[XmlAttribute]
public string Platform;
[XmlAttribute]
public string MD5;
[XmlAttribute]
public float Size;
}

生成配置表

CopyABAndGenerateConfig方法中添加生成的方法:

每次生成热更包时有一个Patch.Xml和各个热更包放在一起,它里面就记录了这些热更包的具体信息

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
static void CopyABAndGenerateConfig(List<string> changeList,string hotCount)
{
if (!Directory.Exists(m_HotPath))
{
Directory.CreateDirectory(m_HotPath);
}
DeleteAllFile(m_HotPath);
foreach (string abName in changeList)
{
if (!abName.EndsWith(".manifest"))
{
File.Copy(m_BundleTargetPath + "/" + abName, m_HotPath + "/" + abName);
}
}

//生成服务器Patches
DirectoryInfo directoryInfo = new DirectoryInfo(m_HotPath);
FileInfo[] files = directoryInfo.GetFiles("*",SearchOption.AllDirectories);
Patches patches = new Patches();
patches.Version = 1;//???
patches.Files = new List<Patch>();
foreach (FileInfo file in files)
{
Patch patch = new Patch();
patch.Name = file.Name;
patch.Url = "http://127.0.0.1/AssetBundle/" + PlayerSettings.bundleVersion + "/" + hotCount + "/" + file.Name;
patch.Platform = EditorUserBuildSettings.activeBuildTarget.ToString();
patch.MD5 = MD5Manager.Instance.BuildFileMD5(file.FullName);
patch.Size = file.Length / 1024.0f;
patches.Files.Add(patch);
}
BinarySerializeOpt.XmlSerialize(m_HotPath + "/Patch.xml", patches);
}

这里的127.0.0.1是Apache本地服务器的入口,端口号是默认的80,所以这里不用指定端口号。

这里指定的URL就是Apache服务器的文件目录(本地地址例:www/AssetBundle/0.1/1/热更文件),详情见下一节