JamesB 发表于 2023-2-25 21:07

Unity资源管理和打包

Unity资源读取:

[*]RM: Resources.Load
[*]Addressables: Addressables.LoadAssetAsync
[*]AssetBundle:AssetBundle.Load
<hr/>问题:


[*]读取

[*]RM:只能读取Resources文件夹下的(即路径中包含Resources),并且是按照相对路径读取
[*]Addressables:需要手动设置group和key,再根据key去读取并且无法根据类型读取
[*]AssetBundle:需要手动设置繁琐的依赖关系来达成最佳的内存利用率



[*]优化问题

[*]RM:资源包无法分块读取,内存占用大,加载速度慢,需要一整块资源加载
[*]Addressables:group关系并不影响内存关系,哪里需要加载哪里
[*]AssetBundle:复杂的内存管理,一不小心就内存泄露了



[*]热更新

[*]RM:不存在的 (ノ`Д)ノ
[*]Addressables:快捷方便,通过group设置,可以自动对比出补丁包
[*]AssetBundle:需要自己管理特征码和包体差异等对比


<hr/>我们需要:


[*]简单快捷的读取方式:不管是Resources文件夹以内的还是以外,根据文件名读取和根据类型读取
[*]复杂的依赖关系可以更易于管理:自动管理资源
[*]高效清晰并易于管理的内存管理:Adressables的非依赖的资源读取方式
[*]快捷方便的热更新方案:自动检测出补丁包以便增量更新
<hr/>ResourcesManager-基于Addressables和RM整合出一套资源管理和打包流程

<hr/>简单快捷的读取方式

1. 以文件名为key,建立资源映射表




[*]Refresh all directroy information 刷新映射表
[*]Directories 目标文件夹
[*]Exclude Directories 排除的文件
[*]Ignore Extensions 忽略的文件类型
[*]Auto Detect 是否在文件导入或移动时自动刷新



[*]映射表

[*]后缀
[*]父级文件
[*]Unity类型

[*]注意事项:同名文件会被强制加入_type,如GameObject.prefab有同名的话会变化GameObject_prefab.prefab
<hr/>2. 读取资源的接口

/// <summary>
/// Load resources according to name
/// </summary>
/// <typeparam name="T">Any types Inherited from UnityEngine.Object</typeparam>
/// <param name="assetName">Resource name</param>
/// <returns>Resource</returns>
public T LoadFromName<T>(string assetName) where T : UnityEngine.Object ···

/// <summary>
/// Async load resources according to name
/// </summary>
/// <typeparam name="T">Any types Inherited from UnityEngine.Object</typeparam>
/// <param name="assetName">Resource name</param>
/// <param name="callback">Callback will be invoke when load done</param>
/// <returns>Coroutine for async</returns>
public Coroutine LoadFromNameAsync<T>(string assetName, Action<T> callback) where T : UnityEngine.Object ···

internal T LoadFromNameInernal<T>(string assetName, Action<T> callback, out Coroutine coroutine) where T : UnityEngine.Object ···
internal IEnumerator Load<T>(string assetName, Action<T> callback) where T : UnityEngine.Object ···

[*]根据名字读取资源

[*]LoadFromName:同步读取资源,所有被包括在映射表内的资源都可以直接用这个方法

[*]内部用了Resources.Load和Addressables.LoadAssetAsync的接口
[*]注意:Addressables的同步读取接口是官方非正式公开的,可能有点问题
[*]目前Addressables也没有开放根据type来读取资源的接口,只有泛型

[*]LoadFromNameAsync:异步读取接口,其他同上

/// <summary>
/// Load all types of target
/// </summary>
/// <typeparam name="T">Any types Inherited from UnityEngine.Object</typeparam>
/// <param name="path">Optional: Start such as Resources/Folder</param>
/// <returns>List for all resources</returns>
public List<T> LoadFromType<T>(string path = null) where T : UnityEngine.Object ···

/// <summary>
/// Load all types of target
/// </summary>
/// <param name="type">Any types Inherited from UnityEngine.Object</param>
/// <param name="path">Optional: Start such as Resources/Folder</param>
/// <returns>List for all resources</returns>
public List<UnityEngine.Object> LoadFromType(Type type, string path = null) ···

/// <summary>
/// Async load all types of target
/// </summary>
/// <typeparam name="T">Any types Inherited from UnityEngine.Object</typeparam>
/// <param name="callback">Callback will be invoke when load done</param>
/// <param name="path">Optional: Start such as Resources/Folder</param>
public void LoadFromTypeAsyn<T>(Action<List<T>> callback, string path = null) where T : UnityEngine.Object ···

/// <summary>
/// Async load all types of target
/// </summary>
/// <param name="type">Any types Inherited from UnityEngine.Object</param>
/// <param name="callback">Callback will be invoke when load done</param>
/// <param name="path">Optional: Start such as Resources/Folder</param>
public void LoadFromTypeAsyn(Type type, Action<List<UnityEngine.Object>> callback, string path = null) ···

internal List<UnityEngine.Object> LoadFromTypeInternal(Type type, string path = null, Action<List<UnityEngine.Object>> callback = null) ···

[*]根据类型读取资源

[*]LoadFromType:同步读取path路径下所有type的资源,如果path为空,则遍历映射表

[*]Resources.Load、Addressables.LoadAssetAsync
[*]根据映射表的Unity类型读取
[*]非Resources的同步接口同上,只能用AssetDataBase

[*]LoadFromTypeAsync:异步读取接口,其他同上

<hr/>自动管理资源


[*]通过资源导入或移动的事件(AssetPostprocessor)回调修改映射表
[*]自动添加或修改删除Addressables中的Group和Entry,当资源未被指定Addressables时,自动加入到Temp(Group)中
<hr/>示例


[*]将Static Resources加入到ResoucesManager的Directories中


2. 确认添加之前的状态,映射表和Addressables




3. 加入GameObject1的预设到Static Resources中


4.映射表自动加入了GameObject1的映射关系,Addressables也自动加入到Default(Group)并创建一个Entry用于GameObject1的定向




5. Build Player Content


6. 选择Pack Play Mode(模拟实机加载)



7. 代码加载
public class Test : MonoBehaviour
{
    private void Start()
    {
      ResourcesManager.instance.LoadFromNameAsync<GameObject>("GameObject1", go => { Instantiate(go); });
    }
}
8. 没问题


Adressables的非依赖的资源读取方式

参考之前写的资源管理
增量更新


[*]官方文档
[*]无需在导入或修改时特地分离出更新内容,自动快捷地自动识别出需要更新的资源,并分到到单独一个Group里面,打出单独的asset包用于增量更新
<hr/>
[*]给Group指定好Static Content注明Group中的内容是静态不会更新的



[*]打包


2. 来到相关资源包文件夹检查


3. 修改GameObject2(pos.x+1),新增GameObject3
4. 选择Prepare For Content Update


5. 选择Asset/AddressableAssetsData内的bin文件(记录了上次资源包的相关内容)


6. 检测出相关新增和修改,将相关的Entry会移动到Content Update(Group)中




7. 修改Content Update的设置


8. 再次打包,相关资源包文件夹多出了用于增量更新的资源



Addressables的同步读取

参考自Unity官方示例

[*]脚本中加入官方示例中的两个脚本


2. 看了看脚本核心部分,其实重写解包和读取资源的方式,用了AssetBundle
3. Default Group设置解包和读取资源的Provider
注意:一定要设置Default的Group,而且资源所在Group也需要,我这里就讲需要打包资源所在Group设为Default了


4. 打包
5.脚本读取
private void Start()
{
    StartCoroutine(Load());
}
private IEnumerator Load()
{
    //由于Addressables的初始化是异步过程,MainThread无法通过进程阻塞来等待初始化完成,只能这样了
    yield return ResourcesManager.instance.inited;
    //第一次异步读取需要等待初始化,之后就不需要了
    Instantiate(ResourcesManager.instance.LoadFromName<GameObject>("Cube"));
    Instantiate(ResourcesManager.instance.LoadFromName<GameObject>("GameObject1"));
}
//ResoucesManager内部相关读取
var handle = Addressables.LoadAssetAsync<T>(assetName);
if (handle.IsValid() && handle.Status == AsyncOperationStatus.Succeeded)
{
    if (callback == null)
    {
      if (handle.IsDone)
      {
            return handle.Result;
      }
    }
    ···
}
6. 实机运行,没问题
页: [1]
查看完整版本: Unity资源管理和打包