在 Unity 中使用 UnityAction<Object> callback
的主要原因是为了处理异步加载资源的结果,并实现非阻塞的代码逻辑。以下是详细的解释:
1. 异步操作的特性
当使用 LoadResAsync
这类异步加载方法时,资源加载需要一定时间(可能是几毫秒或更久),但代码不会停下来等待加载完成。此时需要一种机制,在加载完成后通知调用方:“你要的资源准备好了,现在可以用了。”
UnityAction<Object> callback
就是用来接收这个“通知”的委托(Delegate),它会在资源加载完成后被调用,并将加载到的资源(Object
类型)传递出去。
2. 为什么不用返回值?
如果是同步加载,可以直接返回资源:
// 同步方法(会卡顿)
public Object LoadRes(string abName, string resName) {
LoadAB(abName);
return abDic[abName].LoadAsset(resName);
}
但异步加载不能直接返回结果,因为资源加载需要时间,方法执行到 yield return
时会立即返回一个协程,而不会等待加载完成。此时必须通过回调传递结果。
3. 回调的作用
通过 callback
参数,调用方可以自定义加载完成后的逻辑。例如:
LoadResAsync("character", "player", (obj) => {
GameObject player = (GameObject)obj;
player.transform.position = Vector3.zero;
Debug.Log("玩家加载完成!");
});
这里的 callback
允许调用方在资源加载完成后执行实例化、初始化位置、打印日志等操作,而无需修改 LoadResAsync
方法本身。
4. 为什么用 UnityAction<Object>
?
UnityAction
是 Unity 内置的委托类型,专为 Unity 的 API 设计,可以直接在 Inspector 中绑定事件(虽然此处是代码调用,但保持一致性是好的)。- 如果不需要 Unity 的特定集成,也可以用 .NET 的
Action<Object>
,但UnityAction
更符合 Unity 开发习惯。
5. 与协程(Coroutine)的配合
协程 IEnumerator
通过 yield return
暂停执行,但不会阻塞主线程。当异步加载完成后,协程继续执行,此时调用 callback.Invoke(obj)
,将加载结果传递给外部代码。
6. 解耦和扩展性
- 解耦加载逻辑和使用逻辑:加载资源的方法不需要知道资源加载后要做什么(实例化?直接使用?更新 UI?),它只负责加载并通知调用方。
- 支持多个回调:可以通过
+=
绑定多个回调方法,实现多模块协同。
7. 示例对比:没有回调会怎样?
如果没有回调,调用方需要自己管理协程,并轮询资源是否加载完成:
IEnumerator LoadPlayer() {
var loader = LoadResAsync("character", "player");
yield return loader; // 需要自己管理协程
Object obj = ???; // 无法直接获取结果!
}
这种方式不仅代码复杂,还会破坏封装性。
总结:为什么用 UnityAction<Object> callback
?
- 异步编程的必然选择:无法直接返回值,必须通过回调传递结果。
- 非阻塞设计:保持主线程流畅,避免卡顿。
- 解耦和扩展性:分离加载逻辑和使用逻辑,支持灵活的业务扩展。
- Unity 生态友好:使用
UnityAction
符合 Unity 开发习惯,方便集成事件系统。
替代方案:使用 UnityEvent
或 Action<Object>
如果需要更强大的事件管理(如 Inspector 配置、多播委托),可以用 UnityEvent
:
public UnityEvent<Object> onLoadComplete; // 在 Inspector 中绑定事件
但如果是纯代码调用,UnityAction<Object>
更加轻量。