Unity引擎异步实例化功能详解
Unity引擎异步实例化功能详解
在游戏开发中,同时实例化大量对象容易导致卡顿,影响用户体验。Unity引擎提供了AssetBundle.InstantiateAsync和Object.InstantiateAsync两种异步实例化方案,可以有效提升性能并保持游戏流畅性。
AssetBundle.InstantiateAsync
功能介绍
为了避免同时实例化大量AssetBundle中的游戏对象而导致的卡顿,用户可以利用InstantiateAsync API进行异步实例化,以此来提升性能并保持游戏流畅性。当需要在场景中创建游戏对象时,只需在AssetBundle加载完成之后,异步调用InstantiateAsync API。使用InstantiateAsync API不会影响原始AssetBundle的工作流程。
实现步骤
public class Example : MonoBehaviour
{
AssetBundle myLoadedAssetBundle;
void Start()
{
// Load Assetbundle
myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle"));
}
void Update()
{
// Press the space key to start coroutine
if (Input.GetKeyDown(KeyCode.Space))
{
if (myLoadedAssetBundle == null)
{
Debug.Log("Failed to load AssetBundle!");
return;
}
// Use a coroutine to Instantiate in the background
StartCoroutine(InstantiateAsyc());
}
}
IEnumerator InstantiateAsyc()
{
yield return myLoadedAssetBundle.InstantiateAsync(myLoadedAssetBundle.GetAllAssetNames()[0]);
}
}
Object.InstantiateAsync
执行过程
用户代码中调用Instantiate时,引擎内分为两步执行:
- 通过transfer序列化构建出对象;
- 通过IntegrateMainThread初始化新创建的对象,进入引擎事件生命周期。
使用同步Instantiate时,这两步会在同一帧内完成。小游戏平台上的InstantiateAsync方法通过分帧处理来优化实例化过程,将对象构建和初始化操作拆分到两帧中执行:
- 执行第一步transfer序列化构建对象时,会使用JobSystem来加速,但依旧会在一帧内完成;
- 在第二步IntegrateMainThread增加了一个队列。如果单帧时间内有多次的InstantiateAsync调用,则可根据IntegrationTime的时间长度,分多帧完成。
小游戏分帧Integration
由于小游戏是单线程,使用JobSystem加速transfer序列化构建对象无法达到预期的效果。另外对于单次实例包含大量对象,例如包含Collider、Renderer等的Prefab,在完成IntegrateMainThread进入引擎事件生命周期后,首次更新会有较长的创建物理几何体、编译shader等耗时。
因此,从实际的测试来看,执行第二步IntegrateMainThread所在的那一帧总时间远高于执行第一步构建对象所在的帧。因此在小游戏平台上,我们对第二步IntegrateMainThread进一步分帧处理,允许单次InstantiateAsync调用能够拆分成更多帧执行。当前版本为保证Renderer和粒子等继承自Behaviour的对象运行时序正确,允许最多分成4帧执行。纹理、shader等资源以及用户的MonoBehaviour不受该限制影响。
实例化方式 | 实例化总耗时 | 单帧最高时长 | 7帧总耗时 | 平均每帧耗时 |
---|---|---|---|---|
Instantiate | 73.2ms | 73.2ms | 160ms | 22.8ms |
InstantiateAsync | 84ms | 35.6ms | 135ms | 19.2ms |
从测试结果看,在实例化相同的对象时,单帧最高时长为73.10ms。替换为InstantiateAsync后,降低为35.6ms。另外,异步接口因为将工作负载拆分到了4帧完成,7帧总耗时降低为135ms,同步接口耗时约160ms。
注意事项
InstantiateAsync的分帧效果受IntegrationTime时长设置影响较大,默认为每帧2ms,可通过AsyncInstantiateOperation.SetIntegrationTimeMS接口设置时长。更小的值能够减少卡顿,但会延长实例化的总耗时。
使用InstantiateAsync接口时需要注意,在完成对象的实例化前,不能提前卸载对象所在的AB和其依赖AB。否则可能出现材质或Prefab引用丢失的情况,导致游戏逻辑或渲染出错。