Project Details
- Unity 2019.1.14f1
- Addressables 1.1.7
什么是Addressables?
- 可以取代Asset Bundle的高阶管理系统
- 天涯海角都会追踪到的信标(Address)
- 自动化仓储管理
- 只加载有关联的资源(系统会处理关联性)
- 可以实例化场景,自动关联相关的Asset
- 运行阶段优良的内存管理
- 除脚本外的资源都可以标记为寻址式资源
- Build Script可以让你自己写打包流程(命名规则,打包处理,默认压缩LZ4)
不需要手动去管理AssetBundle,可以通过Addressables系统自动化.
Demo
类似皇室战争的demo, 演示Addressable Asset System用法
如图所示,弓箭手卡牌(按钮),点击后会在场地实例化一个弓箭手.
- 按钮点击事件绑定了 当前场景中的
GameManager对象
里的CharacterManager脚本
中的SpawnCharacter方法
SpawnCharacter 大量生产角色
普通实例化做法
在SpawnCharacter方法中实例化弓箭手, 普通的做法是:
// Inspector面板上放了Archer Red红色弓箭手的prefab
// 类型是GameObject
public GameObject m_archerObject;
// 同步实例化
// 资源已经在本地端
// 大量加载容易造成卡顿
Instantiate(m_archerObject);
Addressables实例化做法
// AssetReference类型 会指向可寻址资源
public AssetReference m_ArcherObject;
// 异步实例化
// 系统不会等待
// 大量实例化不会卡住系统
m_ArcherObject.InstantiateAsync();
同时需要把Archer Red的prefab
勾选可寻址信标.
- 勾选
Addressable
后,会在Addressables窗口中出现此资源 - 也可以直接将prefab拖入Addressables窗口
在Addressables窗口中右键创建一个AssetGroups
,这个群组中的所有资源可以视为一个加载包.
在项目面板会生成一个同名的包对象. 可以更改Asset Bundle相关设置
再去GameManager对象
的Inspector面板上修改public AssetReference m_ArcherObject;
的引用, 因为已经将资源加入到Addressable中,所以这里可以下拉框选到对应的可寻址资源.
Addressables实例化多个对象
- 将需要实例化的多个对象加入Addressables中
- 在脚本的Inspector面板上选取对应的资源
- 放入 弓箭手,法师,战士资源.
CharacterManager脚本代码如下:
public class CharacterManager : MonoBehaviour
{
// 一次加载多个资源时,用List存放这些资源的AssetReference地址
public List<AssetReference> m_Characters;
// 资源是否已经加载完毕
bool m_AssetsReady = false;
// 资源总数
int m_ToLoadCount;
int m_CharacterIndex = 0;
// Start is called before the first frame update
void Start()
{
// 计数,需要多少个资源
m_ToLoadCount = m_Characters.Count;
foreach (var character in m_Characters)
{
// 因为不知道这个资源是不是在本地, 所以要确定已经加载完成
// 因为是异步加载,注册一个完成后的回调函数
character.LoadAssetAsync<GameObject>().Completed += OnCharacterAssetLoaded;
}
}
public void SpawnCharacter(int characterType)
{
// 当Asset全部加载完毕后,才允许实例化
if (m_AssetsReady)
{
// 返回半径为5的球体内的一个随机点。
Vector3 position = Random.insideUnitSphere * 5;
// 设置xz轴, y轴为0
position.Set(position.x, 0, position.z);
// 异步实例化
m_Characters[characterType].InstantiateAsync(position, Quaternion.identity);
}
}
// 回调函数
void OnCharacterAssetLoaded(AsyncOperationHandle<GameObject> obj)
{
// 加载成功一个, 总数减一
m_ToLoadCount--;
// 直到加载全部完成 设置flag 为true
if (m_ToLoadCount <= 0)
m_AssetsReady = true;
}
// 卸载资产Addressables.Release();资产可能会立即卸载,也可能不会立即卸载,具体取决于现有的依赖项
private void OnDestroy() //TODO: Should we teach instantiate with game objects and then manually release?
{
foreach (var character in m_Characters)
{
// 释放操作及其相关资源。
character.ReleaseAsset();
}
}
}
需要注意:
- 在可寻址资源系统中,因为不知道这个资源是不是在本地, 所以要确定是否已经加载完成,因为是异步加载,注册一个完成后的回调函数
- 以前需要思考这个包是否下载,去服务器请求,下载之后解包,然后加载. 用了AAS之后,这些步骤由AAS替你完成.
举个例子: 请求加载一个法师
模型资源, 但是这个资源 不在本地,需要从远程地址(服务器)去下载,只有下载完成之后加载好才去做实例化
的工作.
Addressables窗口
用来放所有被放入Addressables的资产.
- 默认以
路径
作为资产的地址(信标)
.这是可以更改的.- 呼叫此地址的时候,会找到对应的Asset
- 可以设置
Labels
. 用来分类,区分一些同名资产.
profile 配置文件
默认是Default,可以根据不同的需求增加不同的方案,比如正式发布配置方案,测试配置方案等.
Send Profiler Events
勾选后,可以在编辑器下运行时查看所有Asset的加载与卸载情况.- 默认不勾选,因为正式发布时不需要,影响性能.
蓝绿色背景
代表 已加载的资产.绿色条状
代表 当前引用计数.
用Label功能
Label功能可以复数勾选.
- 先将需要的资源插入信标(拖入AAS)
在AddressableAssetSetting中加入Label标签.
给对应的可寻址资源下拉框选择对应的标签.
在对应脚本上的
AssetLabelReference
属性下拉框选择标签
这样这个脚本就会在Start的时候,把所有有Tower标签的资源加载进来.(demo中塔顺序不是对应的,只为演示).
public class TowerManager : MonoBehaviour
{
// 塔prefabs
public IList<GameObject> m_Towers;
// AssetLabelReference Label标签引用对象
public AssetLabelReference m_TowerLabel;
// 界面上的塔卡片 按钮
public Button[] m_TowerCards;
// Start is called before the first frame update
void Start()
{
// 将所有包(AssetGroups)中的带有这个标签的Asset全部拿出来
// 每拿取完成一个之后会调用OnResourcesRetrieved
Addressables.LoadAssetsAsync<GameObject>(m_TowerLabel, null).Completed += OnResourcesRetrieved;
}
private void OnResourcesRetrieved(AsyncOperationHandle<IList<GameObject>> obj)
{
// 拿取的结果存储在IList<GameObject>中
// demo中塔顺序不是对应的,只为演示
m_Towers = obj.Result;
//Activate the tower cards since their assets are now loaded
foreach(var towerCard in m_TowerCards)
{
// 将卡牌按钮显示可按
towerCard.interactable = true;
}
}
public void InstantiateTower(int index)
{
if(m_Towers != null)
{
// 随机放在一些位置
Vector3 position = Random.insideUnitSphere * 5;
position.Set(position.x, 0, position.z);
Instantiate(m_Towers[index], position, Quaternion.identity, null);
}
}
}
查询机制
如果我需要一把剑, 但是标有剑的Label有100把,那么这个时候就需要查询功能,不需要全部去加载100把剑.
或者可以用更多的Label来区分, 比如青铜系列, 剑, 单手等等.
因为异步加载的关系, 用Label标签加载的资源可能不是预期的顺序.
AAS提供了一个查询机制(不加载,也不实例化,只是查询对应的可寻址资源清单表).
LoadResourceLocationsAsync(label)
取出来的清单会放入IResourceLocation
中,然后根据清单加载哪把剑. 自己使用LoadAssetAsync
加载.
本地路径和远端路径
以前的流程是:
- 本地打包,拿去服务端测,有问题再回本地修复,再去测.
为了简化这种流程,AAS提供了一些模式:
- 快速模式
- 第一阶段(研发阶段)本地端资源.
- 快速模式直接通过资产数据库加载资产,以便快速迭代,无需创建分析或资产包(AB包)。
- 虚拟模式
- 也是本地端资源,会虚拟一个服务器端功能
- 分析布局和依赖关系的内容,而无需创建资产包(AB包)。
- 打包模式
- 不会用本地资源,会需要包资源.
- 使用已构建的资产包(AB包)。
每个群组包都可以设置放在本地路径还是远端路径.
用处说明: 游戏打包之后, 嵌在包里的资源(本地),以及服务器端的资源(远端).
游戏运行时会请求服务器资源下载.
4个路径的说明:
如果群组包
是本地包(跟随游戏包体一起发布的包)
,则会放入Local Build Path
路径下.
如果是远端包(游戏开始时去请求服务器下载的包)
,会放入Remote Build Path
路径下.
本地测试的话,本地打包路径和本地加载路径是相同的.
远端打包的话,路径是在一个文件夹下,而加载的路径是 根据http的一个IP和port去加载.
利用AAS附带的虚拟HTTP服务器模拟
开启虚拟资源服务器
根据地址选取一个,记住名称,在后面配置远程路径时需要用到, 再添加一个Hosting Services服务来开启端口.
点击Enable Services
按钮开启服务,会得到一个端口号. 名称也会需要用到.
然后开始配置AddressableAssetSetting里的远程路径设置:
- Profiles 配置方案新建一个, 改名为开发时的配置方案名称.
- 将
[PrivateIpAddress_4]
改为[PrivateIpAddress]
和之前Hosting Services设置对应.
设置对应的包为远程包
首先,更改profile
为新建的deve
方案,然后选择AessetGroup
里的Tower
包更改远程构建路径和加载路径. 可以预览的看到.
测试
将Play Mode Script
改为Packed Paly Mode
, 点击Build
->Build Play Contant
. 这样就完成了打包.
点击播放就可以正常运行,包在对应的目录中.\HostedData\StandaloneWindows64\towers_assets_all_ee248a6bacba1b7989b2b4d9c590083b.bundle
以上就是远端加载流程. 只要把包放到服务器上,把本地设置改掉就可以真实测试了.
安装包和加载包设置(渐进式递增更新)
不用重新下载整个包. 从App Store下载最小的APP包. 第一次启动加载资料包.
利用Label设计换皮与各种功能包
切换到QualityLevelsDemo
场景.
将材质放入Addressables, 并且信标(地址)可以相同,设置不同的Label.
名称相同比如: 帽子,可以分类为A角色的帽子,B角色的帽子, 他们的名称可以相同,用不同的Label就可以区分他们.
public class TextureController : MonoBehaviour
{
// 渲染材质
public Renderer m_ReferencedMaterial;
public void SwitchToHighDef()
{
// 加载对应名称和Label的资源
LoadTexture("ArcherSkin", "Skin2");
}
void LoadTexture(string key, string label)
{
Addressables.LoadAssetsAsync<Texture2D>(new List<object> { key, label }, null, Addressables.MergeMode.Intersection).Completed
+= TextureLoaded;
}
void TextureLoaded(AsyncOperationHandle<IList<Texture2D>> obj)
{
m_ReferencedMaterial.material.mainTexture = obj.Result[0];
}
}
运行之后,点击就会成一个穿红色衣服的角色.
修改纹理之后,不想要重新打包的做法
静态的本地包可以增量式打包.
将材质包组设置为静态内容.
- 打包一个可执行文件. 用Unity的build生成exe.
- 在一些情况下,你想修改包体,而不像重新打包(一个包很大,重新打包很麻烦)
- 现在可以通过ASS增量式打包(具体规则参考文档)
- 修改了Skin2的材质
- 打包流程就需要改变
- 点击
Prepare For Content Update
- 相关说明文档
- 选择原本对应的设置,ASS会提示变动的文件,让你选择是否要分离出一个更新包.
- 之后分离出一个更新包(非静态的)
Content Update组
- 最后再打成
远端包
,选择Build For Content Update
,选择原本对应的设置.
- 玩家手中的exe执行档就会从远端下载这个
Content Update组
里的更新包,完成替换.
远程包如果有修改,不管多大都需要重新打一个完整的. 比如包A(资源1,2,3),修改3的话,资源12也需要重新打进去.
本地包(静态内容,随包发布),如果修改了资源,可以增量式打一个修改的小包,通过远程包的形式替换掉本地包的内容.
场景的异步加载
将场景放入Addressables可以自动关联所有需要的资源.
可以根据信标(地址)用Addressables.LoadSceneAsync
方式异步加载场景.
区别于资源,场景要用
Addressables.LoadSceneAsync
方法加载
public class SceneManager : MonoBehaviour
{
public string m_SceneAddressToLoad;
public void LoadGameplayScene()
{
// 加载传入String场景名称,异步加载场景
Addressables.LoadSceneAsync(m_SceneAddressToLoad, UnityEngine.SceneManagement.LoadSceneMode.Single).Completed += OnSceneLoaded;
}
void OnSceneLoaded(AsyncOperationHandle<SceneInstance> obj)
{
//Addressables.UnloadSceneAsync(new SceneInstance());
// LOGIC THAT KICKSTARTS THE GAMEPLAY
}
}