UH3L13——客户端热更新路径优化

当前需要优化的问题

不同平台的特殊路径,读取表现是不同的:

Application.persistentDataPath Application.streamingAssetsPath
UnityWebRequest​或者WWW​读取
C#文件流读取 UnityWebRequest​或者WWW​读取 C#文件流读取
PC file:/// 无需处理 file:/// 无需处理
Android file:/// 无需处理 直接用 不可用
IOS file:/// 无需处理 file:/// 无需处理

如果我们不对不同平台加以判断,修改读取逻辑,可能就在不同平台上读取出现问题,因此我们需要优化热更新路径的逻辑

多路测试

通过预处理器指令#if ... #elif ... #else .... #endif​,可以选择性的编译C#部分代码
如果#if​和#elif​后边跟随的宏(或者说符号)被定义#define​过,其与#endif​包裹起来的代码就会被编译
如果#if​和#elif​后边跟随的宏(或者说符号)都没有被定义过#define​过,则#else​与#endif​包裹起来的代码就会被编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//#define必须放在文件最开头,所有关键词前面
#define Test

void Start()
{
#if UNITY_ANDROID
print("安卓平台才会编译执行的代码");
#elif UNITY_IOS
print("IOS平台才会编译执行的代码");
#else
print("其他平台会编译执行的代码");
#endif

#if Test
print("定义了Test符号,因此会执行")
#endif

#if Test2
print("未定义了Test1符号,因此不会执行")
#endif
}

平台测试符号

Unity 支持对脚本使用的平台 #define​ 指令如下:

Define 功能
UNITY_EDITOR 用于从游戏代码中调用 Unity Editor 脚本的 #define 指令。
UNITY_EDITOR_WIN 用于 Windows 上的 Editor 代码的 #define 指令。
UNITY_EDITOR_OSX 用于 Mac OS X 上的 Editor 代码的 #define 指令。
UNITY_EDITOR_LINUX 用于 Linux 上的 Editor 代码的 #define 指令。
UNITY_STANDALONE_OSX 用于专门为 Mac OS X(包括 Universal、PPC 和 Intel 架构)编译或执行代码的 #define 指令。
UNITY_STANDALONE_WIN 用于专门为 Windows 独立平台应用程序编译/执行代码的 #define 指令。
UNITY_STANDALONE_LINUX 用于专门为 Linux 独立平台应用程序编译/执行代码的 #define 指令。
UNITY_STANDALONE 用于为任何独立平台(Mac OS X、Windows 或 Linux)编译/执行代码的 #define 指令。
UNITY_WII 用于为 Wii 游戏主机编译/执行代码的 #define 指令。
UNITY_IOS 用于为 iOS 平台编译/执行代码的 #define 指令。
UNITY_IPHONE 已弃用。改用 UNITY_IOS
UNITY_ANDROID 用于 Android 平台的 #define 指令。
UNITY_PS4 用于运行 PlayStation 4 代码的 #define 指令。
UNITY_XBOXONE 用于执行 Xbox One 代码的 #define 指令。
UNITY_LUMIN 用于 Magic Leap OS 平台的 #define 指令。也可以使用 PLATFORM_LUMIN
UNITY_TIZEN 用于 Tizen 平台的 #define 指令。
UNITY_TVOS 用于 Apple TV 平台的 #define 指令。
UNITY_WSA 用于通用 Windows 平台的 #define 指令。此外,根据 .NET Core 和使用 .NET 脚本后端来编译 C# 文件时会定义 NETFX_CORE
UNITY_WSA_10_0 用于通用 Windows 平台的 #define 指令。此外,根据 .NET Core 来编译 C# 文件时会定义 WINDOWS_UWP
UNITY_WINRT UNITY_WSA 相同。
UNITY_WINRT_10_0 等效于 UNITY_WSA_10_0
UNITY_WEBGL 用于 WebGL 的 #define 指令。
UNITY_FACEBOOK 用于 Facebook 平台(WebGL 或 Windows 独立平台)的 #define 指令。
UNITY_ADS 用于从游戏代码中调用 Unity Ads 方法的 #define 指令。5.2 及更高版本。
UNITY_ANALYTICS 用于从游戏代码中调用 Unity Analytics 方法的 #define 指令。5.2 及更高版本。
UNITY_ASSERTIONS 用于断言控制过程的 #define 指令。
UNITY_64 用于 64 位平台的 #define 指令。

取自:平台相关的编译 - Unity 手册(2019.4版)

目标

在客户端热更新逻辑路径进行相关进行修改

  1. 下载AB包路径,按平台划分
  2. 加载文件时,路径前缀处理

代码的修改

我们需要修改以下几处地方

  • DownLoadFile​内:

    我们需要根据不同平台,修改下载资源的地址

    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
    public bool DownLoadFile(string fileName, string localPath)
    {
    try
    {
    #if UNITY_IOS
    string pInfo = "IOS";
    #elif UNITY_ANDROID
    string pInfo = "Android";
    #else
    string pInfo = "PC";
    #endif
    //创建一个FTP连接 用于下载
    FtpWebRequest request = FtpWebRequest.Create(new Uri($"{serverIP}/AB/{pInfo}/" + fileName)) as FtpWebRequest;
    //设置通信凭证 这样才能下载(如果有匿名账号,可以不设置凭证,但是实际开发中建议不要设置匿名账号)
    NetworkCredential credential = new NetworkCredential("MrTang", "MrTang123");
    request.Credentials = credential;
    //其他设置:代理设置为null,请求完毕后关闭控制连接,操作命令设置为下载,指定传输二进制类型数据
    request.Proxy = null;
    request.KeepAlive = false;
    request.Method = WebRequestMethods.Ftp.DownloadFile;
    request.UseBinary = true;
    FtpWebResponse response = request.GetResponse() as FtpWebResponse;
    Stream downLoadStream = response.GetResponseStream();
    using (FileStream file = File.Create(localPath))
    {
    byte[] bytes = new byte[2048];
    int contentLength = downLoadStream.Read(bytes, 0, bytes.Length);
    while (contentLength > 0)
    {
    file.Write(bytes, 0, contentLength);
    contentLength = downLoadStream.Read(bytes, 0, bytes.Length);
    }
    file.Close();
    downLoadStream.Close();
    }
    return true;
    }
    catch (Exception e)
    {
    Debug.Log($"{fileName}下载失败:{e.Message}");
    return false;
    }
    }
  • GetLocalABCompareFileInfo​内:

    由于GetLocalABCompareFileInfo​协程方法内使用的是UnityWebRequest​读取的Application.streamingAssetsPath
    因此我们需要对安卓平台的读取路径作特殊处理,也就是不加file:///​前缀,而其他平台就需要加上

    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
    public void GetLocalABCompareFileInfo(UnityAction<bool> overCallBack)
    {
    //如果可读可写文件夹内存在对比文件,说明之前我们已经下载过了
    if (File.Exists("file:///" + Application.persistentDataPath + "/ABCompareInfo.txt"))
    {
    StartCoroutine(GetLocalABCompareFileInfo("file:///" + Application.persistentDataPath + "/ABCompareInfo.txt", overCal
    }
    //当可读可写文件夹内不存在对比文件时,才来加载默认的可读可写文件(第一次进游戏才会发生)
    else if (File.Exists(Application.streamingAssetsPath + "/ABCompareInfo.txt"))
    {
    #if UNITY_ANDROID
    string path = Application.streamingAssetsPath;
    #else
    string path = "file:///" + Application.streamingAssetsPath;
    #endif
    StartCoroutine(GetLocalABCompareFileInfo(path + "/ABCompareInfo.txt", overCallBack));
    }
    //如果两个都不进,证明第一次并且无默认资源
    else
    overCallBack(false);
    }

    //本地AB包加载并解析
    private IEnumerator GetLocalABCompareFileInfo(string filePath, UnityAction<bool> overCallBack)
    {
    UnityWebRequest request = UnityWebRequest.Get(filePath);
    yield return request.SendWebRequest();
    //获取文件成功,解析本地的资源解析
    if (request.result == UnityWebRequest.Result.Success)
    {
    GetRemoteABCompareFileInfo(request.downloadHandler.text, localABInfo);
    overCallBack(true);
    }
    else
    overCallBack(false);
    }