以前的加载资源步骤
// 1. 加载此名称的AB包
ETModel.Game.Scene.GetComponent<ResourcesComponent>().LoadBundle("config.unity3d");
// 2. 因为是config包,所以添加Config组件
Game.Scene.AddComponent<ConfigComponent>();
// 3. 找出需要Config配置的类型
// ConfigComponent获取注册到事件系统中的所有类型
List<Type> types = Game.EventSystem.GetTypes();
...
// 只加载对应的配置类型
if (!configAttribute.Type.Is(AppType.ClientH))
...
// 实例化一个类型
object obj = Activator.CreateInstance(type);
// 转换成ACategory配置基类
ACategory iCategory = obj as ACategory;
// 调用初始化方法和结束初始化方法
iCategory.BeginInit();
iCategory.EndInit();
// 4. UnitConfig的定义
[Config((int)(AppType.ClientH | AppType.ClientM | AppType.Gate | AppType.Map))]
public partial class UnitConfigCategory : ACategory<UnitConfig>{}
// 5. ACategory定义了BeginInit方法,在其中调用ConfigHelper.GetText方法
public override void BeginInit()
{
...
ConfigHelper.GetText(typeof (T).Name); // Name:UnitConfig
...
}
// 6. 通过ResourcesComponent获取对应AB包中的对应名称资源的prefab
public static string GetText(string key)
{
...
GameObject config = (GameObject)ETModel.Game.Scene.GetComponent<ResourcesComponent>().GetAsset("config.unity3d", "Config");
...
}
// 7. 扩展一个GameObject的扩展方法
// 用于从ReferenceCollector脚本中获取对应key的prefab
public static T Get<T>(this GameObject gameObject, string key) where T : class
{
return gameObject.GetComponent<ReferenceCollector>().Get<T>(key);
}
// 8. 使用扩展方法, 获取挂载在prefab上的UnitConfig.txt的内容
string configStr = config.Get<TextAsset>(key).text;
// 9. 返回给之前ACategory定义的BeginInit方法继续处理
// 对字符串进行分割 ,逐行读取, 解析Json反序列化成Object
T t = ConfigHelper.ToObject<T>(str2);
public static T ToObject<T>(string str)
{
return JsonHelper.FromJson<T>(str);
}
// 10. 完成AB包的加载和反序列化成UnitConfig对象的过程, 卸载AB包
ETModel.Game.Scene.GetComponent<ResourcesComponent>().UnloadBundle("config.unity3d");
ET中使用需要修改的地方
- Entity:
// Unity\Assets\Model\Base\Object\Entity.cs
// Unity\Assets\Hotfix\Base\Object\Entity.cs
#if !SERVER
if (this.ViewGO != null)
{
// 这里用Addressables.ReleaseInstance判断对象是否是AAS实例化的
if (!Addressables.ReleaseInstance(this.ViewGO))
{
UnityEngine.Object.Destroy(this.ViewGO);
}
// UnityEngine.Object.Destroy(this.ViewGO);
}
#endif
- UI组件
- AddressableAssetComponent组件
- UnitFactory
- 加载资源
- 实例化
使用Addressables
Unity Addressable Importer
先导入这个库!!!!!!!, 不然可能导致这个工具不工作.
资源自动导入AAS工具的使用方法:
- 打开目录中的文件:
Packages/manifest.json
- 添加依赖
"com.littlebigfun.addressable-importer": "https://github.com/favoyang/unity-addressable-importer.git"
AssetGroupTemplates组模板设置:
这个部分的设置都比较好理解,Bundle Naming可以选择其他的,我是为了测试Unity Cloud Build所以一直用的No Hash
Update Restriction
:Can Change Post Release
作为非静态包, 发布后可以修改Can Not Change Post Release
作为静态包, 发布后不可修改- 如果更新了静态包内容, 那么您需要运行
Tools->Check for Content Update Restrictions
命令。这将从静态组中取出任何修改后的资产,并将它们移动到一个新组中。生成新的资产组.
- 如果更新了静态包内容, 那么您需要运行
使用Addressables
在package管理器中更新至最新版.
初始化
禁用可寻址项自动初始化。现在,它将在第一次调用时初始化自身(例如加载或实例化)。要在启动时初始化而不是第一次使用,请调用Addressables.Initialize()
。
构建包体内容方式
- 在编辑器中build content, 打开
Addressables Groups window
, 选择Build
>New Build
>Default Build Script
. - 用API则使用:
AddressableAssetSettings.BuildPlayerContent()
运行时加载资源
加载Loading一个asset会加载它所有的依赖项(all dependencies)到内存中. 注意: 此时没有实例化.
实例化需要使用Addressables提供的接口
异步加载:
Addressables.LoadAssetAsync<GameObject>("Asset Address Name");
异步实例化:
Addressables.InstantiateAsync("Asset Address Name");
当资产完成加载/实例化时,您可以提供一个回调来处理它
public class AddressablesExample : MonoBehaviour
{
GameObject myGameObject;
private void Start()
{
...
Addressables.LoadAssetAsync<GameObject>("AssetAddress").Completed += OnLoadDone;
}
private void OnLoadDone(UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<GameObject> obj)
{
// 在生产环境中,应该添加异常处理来捕获null结果等情况的异常。
myGameObject = obj.Result;
}
}
实例化相关
Addressables.InstantiateAsync
有一些相关的开销,因此如果您需要每帧实例化相同的对象数百次,请考虑通过Addressables
API去Load资源,然后通过其他方法实例化。
在本例中,您将调用Addressables.LoadAssetAsync
,然后保存结果并为该结果调用GameObject.Instantiate()
。
这允许灵活地以同步方式调用实例化。缺点是可寻址系统不知道您创建了多少实例,如果管理不当,可能会导致内存问题。例如,引用纹理的预设将不再具有要引用的有效加载纹理,从而导致渲染问题(或更糟)。这类问题可能很难追踪,因为您可能不会立即触发内存卸载.
不再被引用的资产(由分析器中蓝色部分的末尾表示)并不一定意味着资产已被卸载。一个常见的应用场景涉及到一个资产包中的多个资产。
销毁实例化的对象
Addressables.ReleaseInstance
,
或者关闭当前场景. 此场景可以在Additive模式或Single模式下加载(并因此关闭)
InstantiateAsync和其他加载调用是可选的trackHandle参数。当设置为false时,必须保持AsyncOperationHandle句柄的引用,在释放实例时使用。这更有效,但是需要更多的开发工作。 相当于不追踪,不增加引用计数.
如果将trackHandle设置为false,则只能使用句柄调用Addressables.ReleaseInstance,而不是使用实际的GameObject。
如果你使用Addressables.ReleaseInstance
释放一个不是由Addressables
创建的对象, 或者是传入trackHandle == false
,系统检测到这一点并返回false,表示该方法无法释放指定的实例。在这种情况下,实例不会被销毁。
数据加载释放?
不需要释放AsyncOperationHandle.Result
的接口,将仍然需要操作本身被释放。例如Addressables.LoadResourceLocationsAsync
和Addressables.GetDownloadSizeAsync
.它们加载您可以访问的数据,直到操作被释放。这个版本应该通过Addressables.Release
来完成释放操作。
在AsyncOperationHandle.Result
字段中不返回任何内容的操作有一个可选参数,用于在完成时自动释放操作句柄。
- 如果完成后不再需要这些操作句柄之一,请将
autoReleaseHandle
参数设置为true,以确保操作句柄已清除. - 如果需要在操作句柄完成后检查其状态,则希望
autoReleaseHandle
为false。
这些接口的示例有Addressables.DownloadDependenciesAsync
和Addressables.UnloadScene。
UnityEngine.Object.Destroy和Addressables.ReleaseInstance
ReleaseInstance不能卸载不识别的对象, 失败则返回false.
加载场景
Addressables.LoadSceneAsync
您可以使用此方法以Single mode
模式加载场景,该模式将关闭所有打开的场景. 或者以Additive mode
附加模式加载场景.
关闭场景
Addressables.UnloadSceneAsync
或以Single mode
加载一个新场景,会自动关闭其他打开的场景.
加载组件component
你不能直接通过Addressables加载GameObject的component。您必须加载或实例化GameObject,然后从中检索组件引用.
加载子资产
例如: FBX文件中的animation clips
动画片段.
要加载资产中的所有子对象
Addressables.LoadAssetAsync<IList<Sprite>>("MySpriteSheetAddress");
要加载资产中的单个子对象
Addressables.LoadAssetAsync<Sprite>("MySpriteSheetAddress[MySpriteName]");
资产引用计数
- 加载到内存
LoadAssetAsync
:- 调用3次,将获得
AsyncOperationHandle
结构的三个不同实例,它们都引用相同的底层操作。 每次返回都是指向唯一的AsyncOperationHandle
操作对象.
- 调用3次,将获得
- 实例化
InstantiateAsync
:- 对同一个地址调用三次InstantiateAsync会导致所有相关资产的ref-count为3。
这是因为每个InstantiateAsync的结果都是唯一的实例。另一个区别在于InstantiateAsync和其他加载调用是可选的trackHandle参数。当设置为false时,必须保持AsyncOperationHandle的句柄的引用,在释放实例时使用。这更有效,但是需要更多的开发工作。相当于不追踪,不增加引用计数.
卸载时需要使用Addressables.Release
去减少引用计数.
资产引用类型
AssetReference类提供了一种访问Addressable Assets的方法,而不需要知道它们的地址。
任何可序列化的组件都可以支持一个AssetReference变量(例如,一个游戏脚本、ScriptableObject或其他可序列化的类)。
加载或实例化AssetReference类型对象的方法
public AssetReference assetRefMember;
加载资产
assetRefMember.LoadAssetAsync<GameObject>();
实例化资产
assetRefMember.InstantiateAsync(pos, rot);
以上方法也是异步方法. 可以提供一个回调来处理返回结果.
如果将包含子资产的资产(例如SpriteAtlas或FBX)添加到资产引用中,您就可以选择引用资产本身或子资产。你看到的下拉菜单变成了两个。第一个选择是资产本身,第二个选择是子资产。如果您在下拉菜单中选择第二个,它将被视为对主要资产的引用。
build的注意事项
可寻址资产系统在运行时需要一些文件来知道加载什么以及如何加载。这些文件是在您 build Addressables data并在StreamingAssets文件夹中结束时生成的,StreamingAssets是Unity中的一个特殊文件夹,其中包含了构建中的所有文件. 当您构建Addressables content
,时,系统将这些文件置于库中。然后,当您构建应用程序时,系统将所需的文件复制到StreamingAssets,构建并从文件夹中删除它们。通过这种方式,您可以为多个平台构建数据,而在每个构建中只包含相关的数据。
提前下载
Addressables.DownloadDependenciesAsync()
这个调用返回的AsyncOperationHandle结构
包含一个PercentComplete
属性,您可以使用它来监视和显示下载进度。你也可以让应用程序等待,直到内容已经加载。
如果希望在下载之前征求用户的同意,可以使用Addressables.GetDownloadSize()
返回从给定地址或标签下载内容所需的空间.
您可以使用预加载功能来显示下载已经开始,然后继续下载,而不是使用百分比完成值来等待内容加载。此实现将需要加载或等待画面来处理在需要时资产尚未完成加载的实例。
三种模式的构建脚本
使用资产数据库(FastMode)
使用资产数据库模式(BuildScriptFastMode)允许您在运行游戏流时快速运行游戏。它直接通过资产数据库加载资产,以便快速迭代,而不需要进行分析或创建资产包。
模拟组(VirtualMode)
可以用Event Viewer进行分析, 模拟使用bundle加载, 进行详细的性能评估和资产使用情况
模拟组模式(BuildScriptVirtualMode)在不创建资产包的情况下分析布局和依赖项的内容。资产通过ResourceManager从资产数据库加载,就像通过bundle加载一样。要查看包在游戏过程中何时加载或卸载,请在Addressables事件查看器窗口(窗口>资产管理>可寻址>事件查看器)中查看资产使用情况。
模拟组模式帮助您模拟加载策略,并调整内容组以找到生产版本的正确平衡。
使用现有的方式进行构建(Use Existing Build)
使用现有的构建模式与已部署的应用程序构建最接近,
使用此模式前, 需要先build出资产内容Content,称为打包,
AddressableAssetSettings.BuildPlayerContent()
,Build
>New Build
>Default Build Script
Content更新流程
静态内容包
不会去更新的包,在这种结构中,静态内容随应用程序一起发布.
然而,当您不想发布整个新的应用程序构建时,可寻址资产系统也可以适应需要更改静态内容包的情况。
动态内容包
动态内容驻留在网上,最好是在较小的包中,以最小化每次更新所需的数据量。
更新原理
Addressables使用内容目录将地址映射到每个资产,并指定在何处以及如何加载它。为了向您的应用程序提供修改映射的能力,您的原始应用程序必须知道此目录的在线副本。要进行设置,请在AddressableAssetSettings检查器上启用Build Remote Catalog
设置。这确保将目录的副本构建到指定路径并从指定路径加载。一旦你的应用程序发布,这个加载路径就不能改变。内容更新过程创建目录的新版本(具有相同的文件名)来覆盖文件.
应用程序的版本文件
构建应用程序将生成一个惟一的应用程序内容版本字符串,该字符串标识每个应用程序应该加载的内容目录。一个给定的服务器可以包含应用程序的多个版本的目录,而不会产生冲突。我们将需要的数据存储在addressables_content_state.bin
文件中。这包括版本字符串,以及标记为StaticContent
的组中包含的任何资产的hash信息。默认情况下,此文件与您的AddressableAssetSettings.asset位于相同的文件夹中。
addressables_content_state.bin
文件包含Addressables系统中每个静态内容资产组
的hash和依赖项信息。构建到StreamingAssets文件夹的所有组都应该标记为静态内容,尽管大型远程组也可以从这种指定中受益。这个hash信息确定是否有任何静态内容组包含更改的资产,因此需要将这些资产转移到其他地方。
Unique Bundle IDs
用于运行时更新包 , 而不是启动时检测
当将资产包加载到内存中时,Unity强制要求不能用相同的内部名称加载两个包。这可能对在运行时更新包造成一些限制。由于Addressables支持在初始化之外更新目录,那么就可以更新已加载的内容。
要实现这一目标,必须做到以下两点之一。
- 第一个选项是选择是在更新目录之前卸载所有addressables内容。
- 第二个选项是确保更新后的资产包具有唯一的内部标识符。这将允许您在旧包仍在内存中时加载新包。
我们有一个选项来启用第二个选项。在AddressableAssetSettings检查器中打开Unique Bundle IDs
。此选项的缺点是需要重新构建依赖项链上的包。意思是如果你在一个组中改变了一个材质,默认情况下只有材质包会被重建。使用Unique Bundle IDs
,引用该材质的任何资产都需要重新构建。
在运行时检查内容更新
您可以添加一个自定义脚本来定期检查是否有新的Addressables内容更新。使用以下函数调用来启动更新:
public static AsyncOperationHandle<List<string>> CheckForCatalogUpdates(bool autoReleaseHandle = true)
其中List
如果有新的内容,您可以向用户显示一个按钮来执行更新,或者自动执行更新。请注意,开发人员需要确保陈旧的资产得到释放。目录列表可以是空的,如果是空的,下面的脚本将更新所有需要更新的目录:
public static AsyncOperationHandle<List<IResourceLocator>> UpdateCatalogs(IEnumerable<string> catalogs = null, bool autoReleaseHandle = true)
返回值是更新的定位器(IResourceLocator)列表。
Building for content updates 构建更新的内容
如果您在任何StaticContent groups中都有修改过的资产,那么您需要运行Check for Content Update Restrictions
命令。这将从静态组中取出任何修改后的资产,并将它们移动到一个新组中。生成新的资产组.
- 在Unity编辑器中打开Addressables组窗口(Window> Asset Management > Addressables >Groups)。
- 在Addressables组窗口中,选择顶部菜单栏上的工具,然后
Check for Content Update Restrictions
. - 在打开的构建数据文件对话框中,选择
addressables_content_state.bin
文件(默认情况下,该文件位于Assets/AddressableAssetsData项目目录中)。
此数据用于确定自上次构建应用程序以来修改了哪些资产或依赖项。系统将这些资产移动到一个新的组,为内容更新构建做准备。
注意:如果您的所有更改都局限于非静态组,则此命令
Check for Content Update Restrictions
将不起任何作用。
重要提示:在运行准备操作之前,Unity建议对版本控制系统进行分支。prepare operation
会以适合更新内容的方式重新排列资产组。分支确保了下次你发行一个新的播放器时,你可以回到你喜欢的内容安排。
Building for content updates 构建内容更新
- 打开Addressables Groups window在Unity Editor(Window > Asset Management > Addressables > Groups).
- 在Addressables组窗口中,在顶部菜单中选择Build,然后选择
Update a Previous Build
. - 在打开的“
Build Data File
”对话框中,选择现有应用程序生成的生成文件夹。构建文件夹必须包含一个addressables_content_state.bin
文件。
建生成内容目录( content catalog)、hash文件和资产包(AB包)。
生成的内容目录与所选应用程序构建中的目录具有相同的名称,覆盖了旧的目录和散列文件。应用程序加载散列文件以确定是否有新的目录可用。系统从应用程序附带的或已经下载的现有包加载未修改的资产。
系统使用来自addressables_content_state.bin文件的内容版本字符串和位置信息来创建资产包(AB包)。不包含更新内容的资产包是使用与为更新选择的构建中的文件名相同的文件名编写的。如果一个资产包包含更新的内容,就会生成一个新的资产包,其中包含更新的内容,并有一个新的文件名,以便它可以与原始文件共存。只有具有新文件名的资产包必须复制到你存放content的目录位置。
系统还为静态内容构建资产包,但是您不需要将它们上载到内容托管位置,因为没有Addressables资产条目引用它们。
本地的静态AB包在有更新之后, 会作为死数据一直在用户机器上.Remote_Static包如果已经被缓存在用户机器上, 更新之后也会变成死数据.不再被引用.
旧的Remote_NonStatic包被替换为一个新版本,其区别在于它的散列文件。修改后的版本将使用这个新包进行更新。
content_update_group包由将要向前引用的修改资产组成
异步操作句柄
AsyncOperationHandle
结构
这个句柄的主要目的是允许访问操作的状态和结果。操作的结果在调用Addressables.Release
或Addressables.ReleaseInstance
之前有效.
当操作完成时,AsyncOperationHandle.Status
是Succeeded
或者 Failed
, 如果成功,可以通过AsyncOperationHandle.Result
访问结果.
您可以定期检查操作状态,或者使用AsyncOperationHandle.Complete
注册完成的回调。当您不再需要返回的AsyncOperationHandle结构提供的资产时,应该使用Addressables.Release
方法来释放它。
可以支持异步await
通过AsyncOperationHandle.Task
属性.
public async Start()
{
AsyncOperationHandle<Texture2D> handle = Addressables.LoadAssetAsync<Texture2D>("mytexture");
await handle.Task;
// The task is complete. Be sure to check the Status is successful before storing the Result.
}
AsyncOperationHandle.Task属性在WebGL上不可用,因为该平台不支持多线程操作。
请注意,allowSceneActivation
设置为false并使用SceneManager.LoadSceneAsync
加载的场景 或使用Addressables.LoadSceneAsync
的,并为activateOnLoad
参数设置false的场景, 可能会导致后续异步操作被阻止并无法完成。
关于Addressables Hosting
如果您重置端口号,您必须执行完整的应用程序构建来生成和嵌入正确的URL.
您应该修改所有构建路径变量以指向项目资产文件夹外的公共目录。
内存管理的一个例子
在一个资产包(stuff)中有三个资产(tree、tank和cow)。
- 加载
tree
时,探查器显示tree
的1个ref计数,以及1个stuff
计数。 - 稍后,当
tank
加载时,profiler将显示tree
和tank
的各1个ref计数,以及stuff bundle
的2个ref计数。 - 如果释放
tree
,ref count变为0,蓝色条消失。
在本例中,tree
资源在此时实际上没有卸载。可以加载资产包或其部分内容,但不能部分卸载资产包。在bundle本身完全卸载之前,任何资产都不会卸载。此规则的例外是引擎接口 Resources.UnloadUnusedAssets
方法。在上述场景中执行此方法将导致tree
卸载。由于可寻址系统无法意识到这些事件,探查器图只反映可寻址引用计数(而不是确切的内存容量)。请注意,如果选择使用Resources.UnloadUnusedAssets
,则该操作非常缓慢,并且只应在不会显示出任何故障的界面(例如加载界面)上调用。
更新日志
- 添加了PrimaryKey到IResourceLocation。默认情况下,这是地址。
- ReleaseInstance不会销毁它无法识别的对象, 返回bool, 销毁成功返回true,失败返回false.
- 您可以使用LoadResourceLocationsAsync,然后将结果映射回一个地址。
- 添加ResourceType到IResourceLocation
- 这允许您在加载位置之前了解它的类型。
-
- 这允许您在加载位置之前了解它的类型。