9.YooAsset结合UniTask异步加载实践
9.1 知识点
下载并导入 UniTask 源码
下载UniTask的Release包,把src\UniTask\Assets\Plugins\UniTask
下的UniTask 源码导入工程(建议置于 Assets/ThirdParty/UniTask
或你的第三方目录)。
修改 UniTask 源码(增加 YooAsset 可见性)
找到并修改:UniTask/Runtime/_InternalVisibleTo.cs
,在现有条目下新增一行:
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("UniTask.Linq")]
[assembly: InternalsVisibleTo("UniTask.Addressables")]
[assembly: InternalsVisibleTo("UniTask.DOTween")]
[assembly: InternalsVisibleTo("UniTask.TextMeshPro")]
[assembly: InternalsVisibleTo("UniTask.YooAsset")] // 增加此行代码
拷贝 Yoo 提供的扩展脚本
下载YooAssets提供的Unitask示例,将 YooAssets/Samples/UniTask Sample/UniTask
目录中的 扩展脚本拷贝到你的工程。YooAssets/Samples/UniTask Sample/UniTaskRef
文件夹是通过Package导入UniTask插件才会用到的。
YooAssets所有拓展Unitask的代码其实都在YooAssets/Samples/UniTask Sample/UniTask/Runtime/External
,可以直接把YooAssets所有拓展Unitask的代码丢到Unitask的拓展目录下Assets\Plugins\UniTask\Runtime\External\YooAsset
添加宏定义
在 Project Settings → Player → Scripting Define Symbols 中添加:
UNITASK_YOOASSET_SUPPORT
注意:如果UniTask.YooAsset没有定义UNITASK_YOOASSET_SUPPORT,就不需要加。这里只是示例工程把 UniTask 作为 UPM 包装的,所以能选到 com.cysharp.unitask,进而自动定义 UNITASK_YOOASSET_SUPPORT。
重启 Unity 并验证
重启Unity后若无报错即可开始使用 await handle.ToUniTask()
的写法。可以修改YooAssets示例项目进行验证。
切换场景
/// <summary>
/// 接收事件
/// </summary>
private async void OnHandleEventMessage(IEventMessage message)
{
if (message is SceneEventDefine.ChangeToHomeScene)
{
// YooAssets.LoadSceneAsync("scene_home");
// YooAssets.LoadSceneAsync("Assets/YooAsset/Samples/Space Shooter/GameRes/Scene/scene_home.unity");
var sceneHandle = YooAssets.LoadSceneAsync("Assets/YooAsset/Samples/Space Shooter/GameRes/Scene/scene_home.unity");
await sceneHandle.ToUniTask();
Debug.Log($"scene_home:{sceneHandle.Status}");
}
else if (message is SceneEventDefine.ChangeToBattleScene)
{
// YooAssets.LoadSceneAsync("scene_battle");
// YooAssets.LoadSceneAsync("Assets/YooAsset/Samples/Space Shooter/GameRes/Scene/scene_battle.unity");
var sceneHandle = YooAssets.LoadSceneAsync("Assets/YooAsset/Samples/Space Shooter/GameRes/Scene/scene_battle.unity");
await sceneHandle.ToUniTask();
Debug.Log($"scene_battle:{sceneHandle.Status}");
}
}
加载预制体
// 新增一个中间步骤,防止重复进入
private enum ESteps
{
None,
Ready,
LoadingPlayer, // 新增
SpawnEnemy,
WaitSpawn,
WaitWave,
GameOver,
}
/// <summary>
/// 更新房间
/// </summary>
public async void UpdateRoom()
{
if (_steps == ESteps.None || _steps == ESteps.GameOver)
return;
// 原先的代码
// if (_steps == ESteps.Ready)
// {
// if (_startWaitTimer.Update(Time.deltaTime))
// {
// // 生成实体
// var assetHandle = YooAssets.LoadAssetAsync<GameObject>("player_ship");
// assetHandle.Completed += (AssetHandle handle) =>
// {
// handle.InstantiateSync(_roomRoot.transform);
// };
// _handles.Add(assetHandle);
// _steps = ESteps.SpawnEnemy;
// }
// }
if (_steps == ESteps.Ready)
{
if (_startWaitTimer.Update(Time.deltaTime))
{
_steps = ESteps.LoadingPlayer; // 先切状态,防止下一帧再次进入
// 生成实体(await 写法,不再使用 Completed)
var assetHandle = YooAssets.LoadAssetAsync<GameObject>("player_ship");
_handles.Add(assetHandle); // 继续集中管理引用计数(你的销毁策略用得到)
await assetHandle.ToUniTask(); // 等待加载完成
if (assetHandle.Status == EOperationStatus.Succeed)
{
assetHandle.InstantiateSync(_roomRoot.transform);
// 这里不要立刻 Release,仍按你的 DestroyRoom 统一释放策略来
_steps = ESteps.SpawnEnemy;
}
else
{
Debug.LogError($"Load player_ship failed: {assetHandle.LastError}");
_steps = ESteps.GameOver; // 或者回退/重试,看你设计
}
}
}
// 其余步骤保持原逻辑(SpawnEnemy/WaitSpawn/WaitWave),
// 如果也要 await 资源,参照上面的“先切状态再 await”的方式加一个 LoadingEnemy 等中间态,避免重复进入。
// ...
}
9.2 知识点代码
InternalsVisibleTo.cs
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("UniTask.Linq")]
[assembly: InternalsVisibleTo("UniTask.Addressables")]
[assembly: InternalsVisibleTo("UniTask.DOTween")]
[assembly: InternalsVisibleTo("UniTask.TextMeshPro")]
[assembly: InternalsVisibleTo("UniTask.YooAsset")] // 增加此行代码
GameManager.cs
using System.Collections;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UniFramework.Event;
using UnityEngine.SceneManagement;
using YooAsset;
public class GameManager
{
private static GameManager _instance;
public static GameManager Instance
{
get
{
if (_instance == null)
_instance = new GameManager();
return _instance;
}
}
private readonly EventGroup _eventGroup = new EventGroup();
/// <summary>
/// 协程启动器
/// </summary>
public MonoBehaviour Behaviour;
private GameManager()
{
// 注册监听事件
_eventGroup.AddListener<SceneEventDefine.ChangeToHomeScene>(OnHandleEventMessage);
_eventGroup.AddListener<SceneEventDefine.ChangeToBattleScene>(OnHandleEventMessage);
}
/// <summary>
/// 开启一个协程
/// </summary>
public void StartCoroutine(IEnumerator enumerator)
{
Behaviour.StartCoroutine(enumerator);
}
/// <summary>
/// 接收事件
/// </summary>
private async void OnHandleEventMessage(IEventMessage message)
{
if (message is SceneEventDefine.ChangeToHomeScene)
{
// YooAssets.LoadSceneAsync("scene_home");
// YooAssets.LoadSceneAsync("Assets/YooAsset/Samples/Space Shooter/GameRes/Scene/scene_home.unity");
var sceneHandle = YooAssets.LoadSceneAsync("Assets/YooAsset/Samples/Space Shooter/GameRes/Scene/scene_home.unity");
await sceneHandle.ToUniTask();
Debug.Log($"scene_home:{sceneHandle.Status}");
}
else if (message is SceneEventDefine.ChangeToBattleScene)
{
// YooAssets.LoadSceneAsync("scene_battle");
// YooAssets.LoadSceneAsync("Assets/YooAsset/Samples/Space Shooter/GameRes/Scene/scene_battle.unity");
var sceneHandle = YooAssets.LoadSceneAsync("Assets/YooAsset/Samples/Space Shooter/GameRes/Scene/scene_battle.unity");
await sceneHandle.ToUniTask();
Debug.Log($"scene_battle:{sceneHandle.Status}");
}
}
}
BattleRoom.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UniFramework.Event;
using UniFramework.Utility;
using YooAsset;
using Random = UnityEngine.Random;
[Serializable]
public class RoomBoundary
{
public float xMin, xMax, zMin, zMax;
}
/// <summary>
/// 战斗房间
/// </summary>
public class BattleRoom
{
private enum ESteps
{
None,
Ready,
LoadingPlayer,
SpawnEnemy,
WaitSpawn,
WaitWave,
GameOver,
}
private readonly EventGroup _eventGroup = new EventGroup();
private GameObject _roomRoot;
// 关卡参数
private const int EnemyCount = 10;
private const int EnemyScore = 10;
private const int AsteroidScore = 1;
private readonly Vector3 _spawnValues = new Vector3(6, 0, 20);
private readonly string[] _entityLocations = new string[]
{
"asteroid01", "asteroid02", "asteroid03", "enemy_ship"
};
private ESteps _steps = ESteps.None;
private int _totalScore = 0;
private int _waveSpawnCount = 0;
private readonly UniTimer _startWaitTimer = UniTimer.CreateOnceTimer(1f);
private readonly UniTimer _spawnWaitTimer = UniTimer.CreateOnceTimer(0.75f);
private readonly UniTimer _waveWaitTimer = UniTimer.CreateOnceTimer(4f);
private readonly List<AssetHandle> _handles = new List<AssetHandle>(1000);
/// <summary>
/// 初始化房间
/// </summary>
public void IntRoom()
{
// 创建房间根对象
_roomRoot = new GameObject("BattleRoom");
// 监听游戏事件
_eventGroup.AddListener<BattleEventDefine.PlayerDead>(OnHandleEventMessage);
_eventGroup.AddListener<BattleEventDefine.EnemyDead>(OnHandleEventMessage);
_eventGroup.AddListener<BattleEventDefine.AsteroidExplosion>(OnHandleEventMessage);
_eventGroup.AddListener<BattleEventDefine.PlayerFireBullet>(OnHandleEventMessage);
_eventGroup.AddListener<BattleEventDefine.EnemyFireBullet>(OnHandleEventMessage);
_steps = ESteps.Ready;
}
/// <summary>
/// 销毁房间
/// </summary>
public void DestroyRoom()
{
if (_eventGroup != null)
_eventGroup.RemoveAllListener();
if (_roomRoot != null)
GameObject.Destroy(_roomRoot);
foreach(var handle in _handles)
{
handle.Release();
}
_handles.Clear();
}
/// <summary>
/// 更新房间
/// </summary>
public async void UpdateRoom()
{
if (_steps == ESteps.None || _steps == ESteps.GameOver)
return;
// 原先的代码
// if (_steps == ESteps.Ready)
// {
// if (_startWaitTimer.Update(Time.deltaTime))
// {
// // 生成实体
// var assetHandle = YooAssets.LoadAssetAsync<GameObject>("player_ship");
// assetHandle.Completed += (AssetHandle handle) =>
// {
// handle.InstantiateSync(_roomRoot.transform);
// };
// _handles.Add(assetHandle);
// _steps = ESteps.SpawnEnemy;
// }
// }
if (_steps == ESteps.Ready)
{
if (_startWaitTimer.Update(Time.deltaTime))
{
_steps = ESteps.LoadingPlayer; // 先切状态,防止下一帧再次进入
// 生成实体(await 写法,不再使用 Completed)
var assetHandle = YooAssets.LoadAssetAsync<GameObject>("player_ship");
_handles.Add(assetHandle); // 继续集中管理引用计数(你的销毁策略用得到)
await assetHandle.ToUniTask(); // 等待加载完成
if (assetHandle.Status == EOperationStatus.Succeed)
{
assetHandle.InstantiateSync(_roomRoot.transform);
// 这里不要立刻 Release,仍按你的 DestroyRoom 统一释放策略来
_steps = ESteps.SpawnEnemy;
}
else
{
Debug.LogError($"Load player_ship failed: {assetHandle.LastError}");
_steps = ESteps.GameOver; // 或者回退/重试,看你设计
}
}
}
if (_steps == ESteps.SpawnEnemy)
{
var enemyLocation = _entityLocations[Random.Range(0, 4)];
Vector3 spawnPosition = new Vector3(Random.Range(-_spawnValues.x, _spawnValues.x), _spawnValues.y, _spawnValues.z);
Quaternion spawnRotation = Quaternion.identity;
// 生成实体
var assetHandle = YooAssets.LoadAssetAsync<GameObject>(enemyLocation);
assetHandle.Completed += (AssetHandle handle) =>
{
handle.InstantiateSync(spawnPosition, spawnRotation, _roomRoot.transform);
};
_handles.Add(assetHandle);
_waveSpawnCount++;
if (_waveSpawnCount >= EnemyCount)
{
_steps = ESteps.WaitWave;
}
else
{
_steps = ESteps.WaitSpawn;
}
}
if (_steps == ESteps.WaitSpawn)
{
if (_spawnWaitTimer.Update(Time.deltaTime))
{
_spawnWaitTimer.Reset();
_steps = ESteps.SpawnEnemy;
}
}
if (_steps == ESteps.WaitWave)
{
if (_waveWaitTimer.Update(Time.deltaTime))
{
_waveWaitTimer.Reset();
_waveSpawnCount = 0;
_steps = ESteps.SpawnEnemy;
}
}
}
/// <summary>
/// 接收事件
/// </summary>
/// <param name="message"></param>
private void OnHandleEventMessage(IEventMessage message)
{
if (message is BattleEventDefine.PlayerDead)
{
var msg = message as BattleEventDefine.PlayerDead;
// 创建爆炸效果
var assetHandle = YooAssets.LoadAssetAsync<GameObject>("explosion_player");
assetHandle.Completed += (AssetHandle handle) =>
{
handle.InstantiateSync(msg.Position, msg.Rotation, _roomRoot.transform);
};
_handles.Add(assetHandle);
_steps = ESteps.GameOver;
BattleEventDefine.GameOver.SendEventMessage();
}
else if (message is BattleEventDefine.EnemyDead)
{
var msg = message as BattleEventDefine.EnemyDead;
// 创建爆炸效果
var assetHandle = YooAssets.LoadAssetAsync<GameObject>("explosion_enemy");
assetHandle.Completed += (AssetHandle handle) =>
{
handle.InstantiateSync(msg.Position, msg.Rotation, _roomRoot.transform);
};
_handles.Add(assetHandle);
_totalScore += EnemyScore;
BattleEventDefine.ScoreChange.SendEventMessage(_totalScore);
}
else if (message is BattleEventDefine.AsteroidExplosion)
{
var msg = message as BattleEventDefine.AsteroidExplosion;
// 创建爆炸效果
var assetHandle = YooAssets.LoadAssetAsync<GameObject>("explosion_asteroid");
assetHandle.Completed += (AssetHandle handle) =>
{
handle.InstantiateSync(msg.Position, msg.Rotation, _roomRoot.transform);
};
_handles.Add(assetHandle);
_totalScore += AsteroidScore;
BattleEventDefine.ScoreChange.SendEventMessage(_totalScore);
}
else if (message is BattleEventDefine.PlayerFireBullet)
{
var msg = message as BattleEventDefine.PlayerFireBullet;
// 创建子弹实体
var assetHandle = YooAssets.LoadAssetAsync<GameObject>("player_bullet");
assetHandle.Completed += (AssetHandle handle) =>
{
handle.InstantiateSync(msg.Position, msg.Rotation, _roomRoot.transform);
};
_handles.Add(assetHandle);
}
else if (message is BattleEventDefine.EnemyFireBullet)
{
var msg = message as BattleEventDefine.EnemyFireBullet;
// 创建子弹实体
var assetHandle = YooAssets.LoadAssetAsync<GameObject>("enemy_bullet");
assetHandle.Completed += (AssetHandle handle) =>
{
handle.InstantiateSync(msg.Position, msg.Rotation, _roomRoot.transform);
};
_handles.Add(assetHandle);
}
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com