hch
5 天以前 b97398002c77755882ecb8f5d6dcb0db98970f0f
0312 武将加载优化
1个文件已修改
187 ■■■■ 已修改文件
Main/System/Hero/UIHeroController.cs 187 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Hero/UIHeroController.cs
@@ -1,5 +1,6 @@
using System;
using System.Threading;
using Spine;
using Spine.Unity;
using UnityEngine;
@@ -16,8 +17,14 @@
    private GameObject instanceGO;
    private bool isInitializing = false;
    private bool isInitialized = false;
    private static int activeInitializationCount = 0; // 当前正在初始化的卡片数量
    private static readonly object initializationLock = new object();
    private CancellationTokenSource loadCancellationToken; // 用于取消之前的加载任务
    // 使用 UniTask 提供的并发控制 - 支持 WebGL
    // 限制同时加载的资源数量(默认为4,可根据设备性能调整)
    private const int MAX_CONCURRENT_LOADS = 4;
    private static int currentLoadingCount = 0;
    private static readonly object loadLock = new object();
    private static int initializationOrder = 0; // 用于分帧延迟的序号
    public Action onComplete;
    public void Create(int _skinID, float scale = 0.8f, Action _onComplete = null, string motionName = "idle", bool isLh = false)
@@ -99,17 +106,35 @@
        }
        onComplete = _onComplete;
        // 立绘需要立即显示,其他情况延迟初始化
        if (isLh)
        {
            // 取消之前的异步任务
            CancelLoadTask();
            // 立绘立即初始化
            ForceInitialize(motionName);
        }
        else
        {
            // 取消之前的异步任务,避免多次调用导致错乱
            CancelLoadTask();
            // 创建新的取消令牌
            loadCancellationToken = new CancellationTokenSource();
            // 使用 UniTask 进行异步初始化,将instanceGO创建和资源加载都移到异步处理
            DelayedInitializeAsync(skinConfig, motionName).Forget();
            DelayedInitializeAsync(skinConfig, motionName, loadCancellationToken.Token).Forget();
        }
    }
    /// <summary>
    /// 取消之前的加载任务
    /// </summary>
    private void CancelLoadTask()
    {
        if (loadCancellationToken != null && !loadCancellationToken.IsCancellationRequested)
        {
            loadCancellationToken.Cancel();
            loadCancellationToken.Dispose();
        }
    }
@@ -126,6 +151,9 @@
    protected void OnDestroy()
    {
        // 取消正在进行的加载任务
        CancelLoadTask();
        if (spineAnimationState != null)
        {
            spineAnimationState.Complete -= OnAnimationComplete;
@@ -294,40 +322,58 @@
    }
    /// <summary>
    /// 延迟初始化,避免批量创建时卡顿 - 使用 UniTask
    /// 延迟初始化,结合并发控制和分帧延迟
    /// 1. 资源加载使用真正的异步(LoadAssetAsync)
    /// 2. skeletonGraphic.Initialize() 前进行分帧延迟,避免主线程卡顿
    /// </summary>
    private async UniTaskVoid DelayedInitializeAsync(HeroSkinConfig skinConfig, string motionName)
    private async UniTaskVoid DelayedInitializeAsync(HeroSkinConfig skinConfig, string motionName, CancellationToken cancellationToken)
    {
        isInitializing = true;
        try
        {
            // 增加当前初始化计数器
            int currentIndex;
            lock (initializationLock)
            // 检查是否已被取消
            cancellationToken.ThrowIfCancellationRequested();
            // 获取加载信号量 - 限制并发数,避免资源竞争
            await AcquireLoadSlotAsync(cancellationToken);
            // 异步创建instanceGO和加载资源(真正的异步,不阻塞)
            await CreateInstanceAndLoadAssetsAsync(skinConfig, isLh: false, cancellationToken);
            // 获取当前序号用于分帧延迟
            int myOrder;
            lock (loadLock)
            {
                currentIndex = activeInitializationCount++;
                myOrder = initializationOrder++;
            }
        // 根据当前初始化序号计算延迟时间
        int delayFrames = Mathf.Min(currentIndex*2, 60);
            // 再次检查是否已被取消
            cancellationToken.ThrowIfCancellationRequested();
        for (int i = 0; i < delayFrames; i++)
        {
            await UniTask.NextFrame();
        }
            // 在 skeletonGraphic.Initialize() 前进行分帧延迟
            // 根据 MAX_CONCURRENT_LOADS 调整延迟,避免所有对象同时执行 Initialize
            int delayFrames = (myOrder % MAX_CONCURRENT_LOADS);
            if (delayFrames > 0)
            {
                for (int i = 0; i < delayFrames; i++)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    await UniTask.NextFrame(cancellationToken);
                }
            }
        // 异步创建instanceGO和加载资源
        await CreateInstanceAndLoadAssetsAsync(skinConfig, isLh: false);
            // 再次检查是否已被取消(可能在延迟期间被取消)
            cancellationToken.ThrowIfCancellationRequested();
        if (skeletonGraphic == null || skeletonGraphic.skeletonDataAsset == null)
        {
            Debug.LogError("资源加载失败,无法初始化模型");
            return;
        }
            if (skeletonGraphic == null || skeletonGraphic.skeletonDataAsset == null)
            {
                Debug.LogError("资源加载失败,无法初始化模型");
                return;
            }
        skeletonGraphic.initialSkinName = skinConfig.InitialSkinName;
        skeletonGraphic.Initialize(true);
            skeletonGraphic.initialSkinName = skinConfig.InitialSkinName;
            skeletonGraphic.Initialize(true);
            // 初始化完成后设置皮肤
            if (!string.IsNullOrEmpty(skinConfig.InitialSkinName))
@@ -338,13 +384,12 @@
                skeletonGraphic.Update(0);
            }
            spineAnimationState = skeletonGraphic.AnimationState;
            spineAnimationState.Data.DefaultMix = 0f;
            // 初始化完成后才显示模型
            skeletonGraphic.enabled = pendingEnabled;
            if (pendingGray)
            {
                skeletonGraphic.material = MaterialUtility.GetDefaultSpriteGrayMaterial();
@@ -353,18 +398,17 @@
            {
                skeletonGraphic.material = null;
            }
            // 检查是否有待设置的速度,如果有则设置
            if (pendingSpeed.HasValue)
            {
                spineAnimationState.TimeScale = pendingSpeed.Value;
                pendingSpeed = null; // 清除待设置速度
                pendingSpeed = null;
            }
            // 检查是否有待播放的动画,如果有则优先播放外部调用的动画
            if (!string.IsNullOrEmpty(pendingAnimationName))
            {
                // 临时设置isInitializing为false,以便PlayAnimation能正常播放
                isInitializing = false;
                PlayAnimation(pendingAnimationName, pendingAnimationLoop, pendingAnimationReplay);
                // 清除所有待播放动画参数
@@ -377,12 +421,11 @@
                // 如果没有外部调用的动画,播放默认动画
                if (motionName == "")
                    motionName = GetFistSpineAnim();
                // 临时设置isInitializing为false,以便PlayAnimation能正常播放
                isInitializing = false;
                PlayAnimation(motionName, true);
            }
            spineAnimationState.Complete -= OnAnimationComplete;
            spineAnimationState.Complete += OnAnimationComplete;
@@ -391,16 +434,52 @@
        }
        catch (System.OperationCanceledException)
        {
            // 任务被取消,正常处理
            // 任务被取消,正常返回
            isInitializing = false;
        }
        catch (System.Exception e)
        {
            Debug.LogError($"英雄初始化异常: {e.Message}");
            isInitializing = false;
        }
        finally
        {
            // 减少当前初始化计数器
            lock (initializationLock)
            // 释放加载槽位
            ReleaseLoadSlot();
        }
    }
    /// <summary>
    /// 获取加载槽位(支持 WebGL 的并发控制)
    /// </summary>
    private async UniTask AcquireLoadSlotAsync(CancellationToken cancellationToken)
    {
        while (true)
        {
            // 检查是否已被取消
            cancellationToken.ThrowIfCancellationRequested();
            lock (loadLock)
            {
                activeInitializationCount--;
                if (currentLoadingCount < MAX_CONCURRENT_LOADS)
                {
                    currentLoadingCount++;
                    return;
                }
            }
            // 如果已达到最大并发数,等待下一帧再试
            await UniTask.NextFrame(cancellationToken);
        }
    }
    /// <summary>
    /// 释放加载槽位
    /// </summary>
    private void ReleaseLoadSlot()
    {
        lock (loadLock)
        {
            currentLoadingCount--;
        }
    }
@@ -503,8 +582,9 @@
    /// <summary>
    /// 异步创建instanceGO和加载资源(用于非立绘)
    /// 使用真正的异步加载,不阻塞主线程
    /// </summary>
    private async UniTask CreateInstanceAndLoadAssetsAsync(HeroSkinConfig skinConfig, bool isLh)
    private async UniTask CreateInstanceAndLoadAssetsAsync(HeroSkinConfig skinConfig, bool isLh, CancellationToken cancellationToken)
    {
        // 确保transform处于激活状态
        if (!transform.gameObject.activeSelf)
@@ -512,9 +592,12 @@
            transform.SetActive(true);
        }
        // 检查是否已被取消
        cancellationToken.ThrowIfCancellationRequested();
        // 创建pool和instanceGO
        pool = GameObjectPoolManager.Instance.GetPool(UILoader.LoadPrefab("UIHero"));
        if (instanceGO == null)
        {
            instanceGO = pool.Request();
@@ -526,21 +609,19 @@
        }
        skeletonGraphic = instanceGO.GetComponentInChildren<SkeletonGraphic>(true);
        // 在主线程中加载资源,避免Unity API线程安全问题
        // 使用UniTask.Yield()来确保在主线程中执行
        await UniTask.Yield();
        if (isLh)
        // 真正的异步加载资源 - 不阻塞主线程
        string assetName = isLh ? skinConfig.Tachie : skinConfig.SpineRes;
        SkeletonDataAsset loadedAsset = await ResManager.Instance.LoadAssetAsync<SkeletonDataAsset>("Hero/SpineRes/", assetName);
        // 再次检查是否已被取消
        cancellationToken.ThrowIfCancellationRequested();
        if (loadedAsset != null)
        {
            skeletonGraphic.skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>("Hero/SpineRes/", skinConfig.Tachie);
            skeletonGraphic.skeletonDataAsset = loadedAsset;
        }
        else
        {
            skeletonGraphic.skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>("Hero/SpineRes/", skinConfig.SpineRes);
        }
        if (skeletonGraphic.skeletonDataAsset == null)
        {
            transform.SetActive(false);
            if (pool != null)