U4S4L5——动态加载单个资源

本章代码关键字

1
2
3
Addressables.LoadAssetAsync<>()        //通过名字或者标签来异步加载资源
Addressable.ReleaseAsset() //卸载资源
Addressables.LoadSceneAsync() //通过名字异步加载场景

通过资源名或标签名动态加载单个资源

  1. 根据名字或标签加载单个资源相对之前的指定加载资源更加灵活
    主要通过Addressables类中的静态方法传入资源名或标签名进行加载

    注意:

    1. 如果存在同名或同标签的同类型资源,我们无法确定加载的哪一个,它会自动加载找到的第一个满足条件的对象
    2. 如果存在同名或同标签的不同类型资源,我们可以根据泛型类型来决定加载哪一个
  2. 释放资源时需要传入之前记录的AsyncOperationHandle​对象
    注意:一定要保证资源使用完毕过后再释放资源

  3. 场景异步加载可以自己手动激活加载完成的场景

命名空间:UnityEngine.AddressableAssets​ 和 UnityEngine.ResourceManagement.AsyncOperations

Addressables.LoadAssetAsync<>()​ 和使用资源引用标识类加载类似,
也是通过返回的AsyncOperationHandle<>​来添加回调,并确认是否加载成功和获取资源
区别在于该方法需要传入名字或者标签(不需要同时传入)

注意:

  1. 如果存在同名或同标签的同类型资源,我们无法确定加载的哪一个,它会自动加载找到的第一个满足条件的对象
  2. 如果存在同名或同标签的不同类型资源,我们可以根据泛型类型来决定加载哪一个

假设要加载image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void Start()
{
//通过名字加载
AsyncOperationHandle<GameObject> handle = Addressables.LoadAssetAsync<GameObject>("Cube");
handle.Completed += (handle) =>
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{
Instantiate(handle.Result);
}
};
//通过标签加载
Addressables.LoadAssetAsync<GameObject>("Red").Completed += (handle2) =>
{
if (handle2.Status == AsyncOperationStatus.Succeeded)
{
Instantiate(handle2.Result);
}
};
}

输出:

image

释放资源

Addressable.ReleaseAsset()​,注意,请务必在资源加载完毕后才卸载资源,该方法需要传入之前加载资源得到的AsyncOperationHandle<>

和标识类的释放资源不同,不管任何资源,即使是预设体 只要释放后 都会影响之前在使用该资源的对象(只会在真实打包时出现问题)

因此,释放资源必须要在资源完全不使用后才能进行

1
2
3
4
5
6
7
8
9
10
11
12
void Start()
{
AsyncOperationHandle<GameObject> handle1 = Addressables.LoadAssetAsync<GameObject>("Cube");
handle1.Completed += (handle) =>
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{
Instantiate(handle.Result);
}
Addressables.Release(handle);
};
}

显示效果:

image

可见,卸载加载出来的预设体后,预设体的材质也一同丢失了

动态加载场景

  • 参数一:场景名
  • 参数二:加载模式(叠加还是单独,叠加就是两个场景一起显示,单独就是只保留新加载的场景,正常情况为单独,可选,默认是单独)
  • 参数三:场景加载是否激活,可选,默认是true​,如果为false​,加载完成后不会直接切换,需要自己使用返回值中的ActivateAsync​方法
  • 参数四:场景加载的异步操作优先级(可选,默认是100)
1
Addressables.LoadSceneAsync("SampleScene");

如果参数二传入的是UnityEngine.SceneManagement.LoadSceneMode.Additive​,则两个场景就会叠加起来

1
Addressables.LoadSceneAsync("SampleScene", UnityEngine.SceneManagement.LoadSceneMode.Additive);

image

如果参数三传入的是true​,则不会自动激活加载出来的场景,会将加载出来的场景会隐藏,等待手动激活

1
Addressables.LoadSceneAsync("SampleScene", UnityEngine.SceneManagement.LoadSceneMode.Single, false);

image

如要激活加载出来的场景,需要从AsyncOperationHandle<>.Result​获取场景,并执行ActivateAsync()​(需要在场景加载完毕后执行)
原场景是否移除取决于执行Addressables.LoadSceneAsync()​时第二个参数是否传入了UnityEngine.SceneManagement.LoadSceneMode.Single

1
2
3
4
5
6
7
8
9
10
void Start()
{
Addressables.LoadSceneAsync("SampleScene", UnityEngine.SceneManagement.LoadSceneMode.Single, false).Completed += (obj) =>
{
obj.Result.ActivateAsync().completed += (obj) =>
{
//在这里执行加载出来场景后要执行的逻辑
};
};
}

注意:场景资源也是可以释放的,并不会影响当前已经加载出来的场景(笔者测试发现,在真实打包环境下,卸载场景会导致天空盒材质以及场景上的材质丢失问题,与课上说的情况不一致)因为场景的本质只是配置文件

1
2
3
4
5
6
7
8
9
10
11
void Start()
{
Addressables.LoadSceneAsync("SampleScene", UnityEngine.SceneManagement.LoadSceneMode.Single, false).Completed += (handle) =>
{
handle.Result.ActivateAsync().completed += (scene) =>
{
//在这里执行加载出来场景后要执行的逻辑
Addressables.Release(handle);
};
};
}

笔者测试发现,在真实打包环境下,卸载场景会导致天空盒材质以及场景上的材质丢失问题,与课上说的情况不一致,原因不明

image

实现一个Addressable管理器

  1. 资源动态加载可以通过返回的对象AsyncOperationHandle<>​(异步操作句柄) 添加完成监听来处理加载后的资源
  2. 释放资源时也是通过释放返回的对象AsyncOperationHandle<>​(异步操作句柄) 来进行释放
  3. 如果分散在各脚本中自己管理资源难免显得太过凌乱,所以我们可以通过一个资源管理器来管理所有的异步加载返回对象AsyncOperationHandle<>

所以如果我们要自己写一个Addressables​资源管理器,主要就是用来管理AsyncOperationHandle<>​对象的

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class AddressableMgr
{
private static AddressableMgr instance;
public static AddressableMgr Instance => instance ??= new AddressableMgr();

public Dictionary<string, IEnumerator> resDic = new Dictionary<string, IEnumerator>();

private AddressableMgr() { }

public void LoadAssetAsync<T>(string name, Action<AsyncOperationHandle<T>> callBack)
{
string keyName = name + "_" + typeof(T).Name;
AsyncOperationHandle<T> handle;
//如果加载过资源
if (resDic.ContainsKey(keyName))
{
handle = (AsyncOperationHandle<T>)resDic[keyName];
if (handle.IsDone)
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{
callBack?.Invoke(handle);
}
else
{
Debug.LogWarning(keyName + "资源加载失败!");
resDic.Remove(keyName);
}
}
else
{
handle.Completed += (obj) =>
{
if (obj.Status == AsyncOperationStatus.Succeeded)
callBack?.Invoke(obj);
else
{
Debug.LogWarning(keyName + "资源加载失败!");
if (resDic.ContainsKey(keyName))
resDic.Remove(keyName);
}
};
}
return;
}
//如果未加载过资源
handle = Addressables.LoadAssetAsync<T>(name);
handle.Completed += (obj) =>
{
if (obj.Status == AsyncOperationStatus.Succeeded)
callBack?.Invoke(obj);
else
{
Debug.LogWarning(keyName + "资源加载失败!");
if (resDic.ContainsKey(keyName))
resDic.Remove(keyName);
}
};
resDic.Add(keyName, handle);
}

public void Release<T>(string name)
{
string keyName = name + "_" + typeof(T).Name;
if (!resDic.ContainsKey(keyName))
return;
Addressables.Release((AsyncOperationHandle<T>)resDic[keyName]);
resDic.Remove(keyName);
}

public void Clear()
{
resDic.Clear();
AssetBundle.UnloadAllAssetBundles(true);
Resources.UnloadUnusedAssets();
GC.Collect();
}
}