9.YooAsset+UniTask异步加载实践

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

×

喜欢就点赞,疼爱就打赏