hch
6 天以前 b97398002c77755882ecb8f5d6dcb0db98970f0f
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)