使用 Addressables 来管理资源

2021-12-09

使用 Addressables 来管理资源

一、安装

打开Package Manager,在Unity Technologies的目录下找到Addressables,更新或下载。
安装

二、配置

依次打开Windows/Asset Management/Addressables/Groups菜单。

首次打开后会提示需要创建配置文件,点击Create Addressables Settings
创建组

Assets目录下会生成AddressableAssetsData文件夹。
配置文件夹

Ⅰ. 认识资源组(AssetGroups)与组策略(AssetGroups Schemas)概念

  1. 点击Manage Groups,返回刚才的Addressables Groups面板。此时已经有了两个内置的组。

  2. Built In Data存储的是工程中Resources文件夹下的资源,随App构建的场景资源,以及其他一些必要的资源等。

  3. Default Local Group (Default)组存储的是工程中被标记为Addressables的资源,假如不对其进行手动分组,则默认放在这个组中。

  4. 点击Create,可以指定模板来复制一个组或者创建一个全新的组。模板存储在AddressableAssetsData/AssetGroupTemplates文件夹下,默认有一个名为Packed Assets的模板,新创建的组存储在AddressableAssetsData/AssetGroups文件夹下。我们手动建立一个名为Remote的新组。
    AssetGroups

  5. 点击Remote组的Add Schema按钮,添加Content Update RestrictionContent Packing & Loading策略。注意不要额外添加Resources and Built In Scene策略,这个已经由Built In Data组负责了。

    AssetGroups文件夹中有一个Schemas文件夹,存放着所有资源组的组策略序列化文件,命名规则为$"{组名}_{策略类型}"
    组策略

  6. 配置Remote组策略。

    • 修改在Content Update Restriction下的Update Restriction选项。

      Can Change Post Release的意思是,当前资源组进行最终构建时会完全覆盖上一次构建结果(打包出的文件名与上一次不同,上一次的包彻底无效化,部署时可以直接删掉),一般这种方式叫做 全量更新

      Can not Change Post Release则意味着,当前资源组构建时需要与上一次构建结果进行比对(Addressables Groups/Tools/Check for Content Update Restrictions),上一次构建出的包不发生变化,新构建的包则建立在旧包的基础上(相同的资源存储在旧包内,修改和添加的资源存储在新包内,部署时需要将新包和旧包一起发到服务器上),这种方式一般叫做 增量更新

    • 修改Content Packing & Loading下的Build PathLoad Path选项。

      • LocalBuildPath 本地构建路径,当App发布时,会将这个路径下的包拷贝到App的StreamingAssets里。

      • LocalLoadPath 本地加载路径,当App运行时,会从这个路径下读取资源包,一般也是在App的StreamingAssets里。

        Local___Path 说明这个组中的资源包会包含在App的安装包内。如果所有资源组都设置为Local,则这个App安装包是 全资源包 (全资源包一般是设置为Can not Change Post Release的)。

        Local___Path

      • RemoteBuildPath 远程构建路径,当构建资源包后,需要将这个路径下的包拷贝到服务器上。

      • RemoteLoadPath 远程加载路径,当App运行时,会从这个地址下载catalog,并与本地catalog对比来判断是否需要更新资源。

      如果想对 全资源包 进行更新,可以点击Addressables Groups/Tools/Check for Content Update Restrictions,会自动生成差异化资源组,将这些资源组设置为远程并构建部署,则可以将App内包含的旧资源进行覆盖。

    • Advanced Options 是更精细化控制资源包构建与加载流程的选项,无特殊情况保持默认即可。需要注意的是Include in Build选项,可以控制当前资源包是否参与本次构建。

Ⅱ. 总体配置

AddressableAssetSettings负责整体配置资源包的构建参数。

设置资源包地址的Profiles

Profiles/Profiles In Use 控制了当前资源包路径设置。

我们刚才对资源包的路径进行了本地与远程的设置,但是并没有深入了解这些路径是如何拼接而成的。比如为什么本地资源包会在构建到StreamingAssets文件夹中呢。
Profiles

点击Manage Profiles,弹出上图的Addressables Profiles面板,之前我们设置的本地路径与远程路径就是在这里定义的。总体上路径由固定的字符串、中括号与花括号组成,中括号内包含了发布时编辑器所确定下来的变量,比如运行平台等;花括号则包含了App运行时所获取的变量,也可以在代码内手动指定一个自定义的变量。

这就解释了为什么我们将资源包指定为Local___Path时,最终会存在于App的StreamingAssets文件夹中,是因为[UnityEngine.AddressableAssets.Addressables.BuildPath]这个地址就是编辑器发布App时所指定目录下的StreamingAssets文件夹地址,而{UnityEngine.AddressableAssets.Addressables.RuntimePath}这个地址则是App运行时的StreamingAssets文件夹地址。

我们也可以新建一个测试用的Profile和一个发布用的Profile,只需要把RemoteLoadPath中的http地址指向测试用服务器地址和正式版服务器地址即可。

设置是否需要远程更新

假如我们的App不需要进行远程资源加载和更新,则保持Content Updata/Build Remote Catalog不勾选即可,否则则需要将其勾选。
Update

Disable Catalog Update on Startup则决定是否在App启动时自动更新catalog。
假如我们希望App启动时将所有的更新包先行下载下来,则可以将其勾选,并在代码中手动调用
Addressables.InitializeAsync()
Addressables.CheckForCatalogUpdates()
Addressables.UpdateCatalogs()
等方法,获取到需要更新的资源包列表,并对其依次进行手动更新。

假如我们希望在App运行中需要某个资源时才会去从远程下载,则可以保持其不勾选的默认状态。这种方式下Addressables系统会在App启动时自动调用上述的一系列方法将本地catalog与远程同步,但是并不会更新资源包。

三、资源管理

  1. 将资源导入工程中

  2. 此时Inspector面板上新添加了一个Addressable选项。
    将需要打包的资源勾选,出现一长串的资源路径,这个路径就是我们加载这个资源时所需要的路径参数

  3. 点击Select按钮,弹出资源组面板,刚添加的资源会位于默认组员组Default Local Group (Default)中。
    Assets

  4. 如果能保证不冲突的话,我们也可以将这个路径手动简化,或者让编辑器自动对其进行简化。在资源组面板右键资源并点击Simplify Addressable Names,可以将复杂的路径简化为不带类型后缀的文件名,方便使用。
    Simply

  5. 也可以为资源指定标签。将一系列资源指定为同一个标签后,则可以在App运行时将其以按标签加载的方式同时加载进来。比如我们将工程中的Lua脚本全部指定一个Scripts的标签等。

四、构建资源包

Ⅰ. 首次构建

首次构建资源包需要点击资源组面板的Build/New Build/Default Build Script
Build

打包成功后在AddressableAssetsData文件夹下生成一个保存当前资源状态的bin文件,这个文件是后续资源增删改时用来与前一次打包做对比用的,并且它保存了至关重要的catalog文件信息。

同时在RemoteBuildPath位置生成catalog以及远程资源包。
RemoteBuildPath

此时我们可以随即进行App的构建。

每次构建资源包,都需要重新构建App。

如果希望更新资源包,不能使用Build/New Build方式。

Ⅱ. 更新包

将资源增删改完毕之后,点击Tools/Check for Content Update Restrictions
Update Assets

选择首次构建时生成的bin文件,弹出Content Update Preview面板,点击Apply Changes

我们这里的默认资源组的Content Update Restriction选项设置的是Can not Change Post Release,因为本地资源已经跟随App一同发布了,修改本地资源是没有意义的,除非重新构建App。
Content Update Preview

编辑器替我们自动生成了新的远程资源组,并将有变动的资源放了进去。

这里不要混淆 远程资源组Content Update Restriction 两个概念,远程资源组既可以标记为不可修改,也可以标记为可修改。
Content Update Groups

我们可以把这些资源放到其他远程组里,也可以保持不变,这方面的策略应该顾及资源包的粒度,文件大小等,严格执行起来还是比较烧脑的。

资源组重新设定完毕后,点击Build/Update a Previous Build,并再次选择首次构建时生成的bin文件,以更新方式构建资源包。

切勿以Build/New Build的方式构建,如果不小心点了,两个解决方式,git reset --hard或者重新打包App……

Update Build

此时打开远程构建目录(RemoteBuildPath),发现已经生成了由新的远程资源组构建的资源包,而catalog文件的修改日期也已经发生了变化,说明虽然catalog文件名没有发生变化,但是其内容已经更新了。

我这里截图的catalog文件名发生了变化,是写文测试的时候把之前的构建结果给删了,实际上应该是不会发生变化的,请忽略。o_0

New Build Result

我们允许的流程是:构建资源包->构建App->以更新方式构建资源包->以更新方式构建资源包...

我们不允许的流程是:...->构建App->构建资源包...

原因是每次点击Build/New Build/***,catalog文件名都会发生变化,而App只记录上一次构建资源包时的catalog文件名,假如我们在构建App之后重新构建了资源包,则旧的App无法识别新的catalog,从而更新失败。

五、部署

RemoteBuildPath目录下的文件上传至服务器,包括资源包以及catalog文件,保证RemoteLoadPath可访问即可。

实际部署还是比教程写的要麻烦很多的。权限,跨域,负载能力,加密,防止各种网络攻击等,各种手段都要用上。我们仅仅是验证技术路线,麻烦事就先不考虑啦。

如果仅仅是测试的话,其实编辑器还提供了一个Host功能
Hosting

点击Create/LocalHosting,新建一个服务端,指定端口,勾选Enable即可。

注意我们需要将Profiles/RemoteLoadPath修改为http://本机IP或者localhost:端口号,默认Profiles的远程加载地址多了一个[BuildTarget],这是访问不到的。

Hosting 2

Hosting 3

六、加载

新建一个脚本,用来测试资源的加载。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.UI;

public class SwitchSprites : MonoBehaviour
{
    [SerializeField] private RawImage _emojiImage;
    [SerializeField] private Text _infoText;

    private int _indicator;

    private AsyncOperationHandle<IList<Texture>> _operation;

    private async void Start()
    {
        _operation = Addressables.LoadAssetsAsync<Texture>("emoji", null);
        await _operation.Task;

        _infoText.text = "Emoji Load Completed";

        GetComponent<Button>().onClick.AddListener(() =>
        {
            _indicator++;
            if (_indicator >= _operation.Result.Count)
            {
                _indicator = 0;
            }

            _emojiImage.texture = _operation.Result[_indicator];
        });
    }

    private void OnDestroy()
    {
        Addressables.Release(_operation);
    }
}

这里一上来直接就可以Addressables.LoadAssetsAsync<Texture>(),是因为我测试的时候没有勾选AddressableAssetSettings/Content Update /Disable Catalog Update on Startup,所以App在加载时自动更新了catalog,而资源包下载则类似于Lazy 模式,即用即下载(缓存中如果有匹配的资源包则直接加载)。真正使用这套系统时,一般会采用勤快一点的模式,先更新必要的资源包,读条一波,然后App才正式运行。