yyl
2026-03-31 0fa617a09eedf6bdb25eda55fac1d3344859fd93
Main/System/Hero/UIHeroController.cs
@@ -23,7 +23,8 @@
   private const int MAX_CONCURRENT_LOADS = 4;
   private static int currentLoadingCount = 0;
   private static readonly object loadLock = new object();
   private static int initializationOrder = 0; // 用于分帧延迟的序号
   private static int lastInitFrame = -1; // 上一次执行Initialize的帧号,用于确保每帧最多1次
   private static GameObjectPoolManager.GameObjectPool cachedUIHeroPool; // 缓存UIHero预制体池
   public Action onComplete;
   public async UniTask Create(int _skinID, float scale = 0.8f, Action _onComplete = null, string motionName = "idle", bool isLh = false)
@@ -81,12 +82,10 @@
         {
            //图片替换
            lhImg.SetTexture2DPNG(skinConfig.Tachie);
            lhImg.SetNativeSize();
            if (skeletonGraphic != null)
            {
               skeletonGraphic.enabled = false;
            }
            lhImg.enabled = true;
            lhImg.raycastTarget = false;
            return;
         }
@@ -201,7 +200,9 @@
      }
      onComplete = _onComplete;
      pool = GameObjectPoolManager.Instance.GetPool(await UILoader.LoadPrefabAsync("UIHero"));
      if (cachedUIHeroPool == null)
         cachedUIHeroPool = GameObjectPoolManager.Instance.GetPool(await UILoader.LoadPrefabAsync("UIHero"));
      pool = cachedUIHeroPool;
      if (this == null) return;
      if (!transform.gameObject.activeSelf)
@@ -251,6 +252,10 @@
      }
      skeletonGraphic.enabled = true;
      // 注册到SpineUpdateManager,批量更新减少跨语言调用开销
      SpineUpdateManager.Instance.Register(skeletonGraphic);
      // 不可见时完全停止更新,滚动列表场景收益很大
      skeletonGraphic.updateWhenInvisible = Spine.Unity.UpdateMode.Nothing;
      SetMaterialNone();
      spineAnimationState = skeletonGraphic.AnimationState;
@@ -262,7 +267,7 @@
      spineAnimationState.Complete -= OnAnimationComplete;
      spineAnimationState.Complete += OnAnimationComplete;
#if UNITY_EIDTOR
#if UNITY_EDITOR
      await UniTask.Delay(100);
      if (skeletonGraphic != null && skeletonGraphic.material != null)
      {
@@ -292,6 +297,9 @@
      {
         spineAnimationState.Complete -= OnAnimationComplete;
      }
      // 从SpineUpdateManager中移除
      if (skeletonGraphic != null)
         SpineUpdateManager.Instance.Unregister(skeletonGraphic);
      if (pool != null)
         pool.Release(instanceGO);
      skeletonGraphic = null;
@@ -452,7 +460,35 @@
         Debug.LogWarning("skeletonGraphic is null, cannot set material to none");
         return;
      }
      skeletonGraphic.material = null;
      skeletonGraphic.material = GetDefaultSpineMaterial();
   }
   /// <summary>
   /// 获取默认的 Spine 渲染材质。
   /// 对于包含非 Normal 混合模式(Additive/Multiply/Screen)插槽的角色,
   /// 返回 atlas 原始材质(Blend One OneMinusSrcAlpha),保证 PMA 混合正确;
   /// 对于普通角色,返回 null(使用 Canvas 默认材质,行为不变)。
   /// </summary>
   private Material GetDefaultSpineMaterial()
   {
      if (skeletonGraphic == null || skeletonGraphic.Skeleton == null)
         return null;
      var slotsData = skeletonGraphic.Skeleton.Data.Slots;
      for (int i = 0; i < slotsData.Count; i++)
      {
         if (slotsData.Items[i].BlendMode != Spine.BlendMode.Normal)
         {
            // 存在非 Normal 混合模式的插槽,必须使用 Spine 材质
            // Canvas 默认材质的 Blend SrcAlpha OneMinusSrcAlpha 会导致
            // PMA Additive 效果(vertex alpha=0)完全消失
            var dataAsset = skeletonGraphic.skeletonDataAsset;
            if (dataAsset != null && dataAsset.atlasAssets.Length > 0)
               return dataAsset.atlasAssets[0].PrimaryMaterial;
            break;
         }
      }
      return null;
   }
   /// <summary>
@@ -475,30 +511,16 @@
         // 异步创建instanceGO和加载资源(真正的异步,不阻塞)
         await CreateInstanceAndLoadAssetsAsync(skinConfig, isLh, cancellationToken);
         // 获取当前序号用于分帧延迟
         int myOrder;
         lock (loadLock)
         {
            myOrder = initializationOrder++;
         }
         // 再次检查是否已被取消
         cancellationToken.ThrowIfCancellationRequested();
         // 在 skeletonGraphic.Initialize() 前进行分帧延迟
         // 根据 MAX_CONCURRENT_LOADS 调整延迟,避免所有对象同时执行 Initialize
         int delayFrames = (myOrder % MAX_CONCURRENT_LOADS);
         if (delayFrames > 0)
         // 确保每帧最多执行1次Initialize(true),避免同帧大量骨骼解析导致卡顿
         while (Time.frameCount == lastInitFrame)
         {
            for (int i = 0; i < delayFrames; i++)
            {
               cancellationToken.ThrowIfCancellationRequested();
               await UniTask.NextFrame(cancellationToken);
            }
            cancellationToken.ThrowIfCancellationRequested();
            await UniTask.NextFrame(cancellationToken);
         }
         // 再次检查是否已被取消(可能在延迟期间被取消)
         cancellationToken.ThrowIfCancellationRequested();
         lastInitFrame = Time.frameCount;
         if (skeletonGraphic == null || skeletonGraphic.skeletonDataAsset == null)
         {
@@ -508,6 +530,7 @@
         skeletonGraphic.initialSkinName = skinConfig.InitialSkinName;
         skeletonGraphic.Initialize(true);
         Debug.Log($"[UIHeroController] Spine Initialize完成, skeleton valid: {skeletonGraphic.IsValid}, enabled: {skeletonGraphic.enabled}");
         // 初始化完成后设置皮肤
         if (!string.IsNullOrEmpty(skinConfig.InitialSkinName))
@@ -524,13 +547,18 @@
         // 初始化完成后才显示模型
         skeletonGraphic.enabled = pendingEnabled;
         // 注册到SpineUpdateManager,批量更新减少跨语言调用开销
         SpineUpdateManager.Instance.Register(skeletonGraphic);
         // 不可见时完全停止更新,滚动列表场景收益很大
         skeletonGraphic.updateWhenInvisible = Spine.Unity.UpdateMode.Nothing;
         if (pendingGray)
         {
            skeletonGraphic.material = MaterialUtility.GetDefaultSpriteGrayMaterial();
         }
         else
         {
            skeletonGraphic.material = null;
            skeletonGraphic.material = GetDefaultSpineMaterial();
         }
         // 检查是否有待设置的速度,如果有则设置
@@ -573,7 +601,7 @@
      }
      catch (System.Exception e)
      {
         Debug.LogError($"英雄初始化异常: {e.Message}");
         Debug.LogError($"英雄初始化异常: {e.Message}\n{e.StackTrace}");
         isInitializing = false;
      }
      finally
@@ -633,8 +661,10 @@
      // 检查是否已被取消
      cancellationToken.ThrowIfCancellationRequested();
      // 创建pool和instanceGO
      pool = GameObjectPoolManager.Instance.GetPool(UILoader.LoadPrefab("UIHero"));
      // 创建pool和instanceGO(使用缓存避免重复加载预制体)
      if (cachedUIHeroPool == null)
         cachedUIHeroPool = GameObjectPoolManager.Instance.GetPool(await UILoader.LoadPrefabAsync("UIHero"));
      pool = cachedUIHeroPool;
      if (instanceGO == null)
      {
@@ -650,6 +680,7 @@
      // 真正的异步加载资源 - 不阻塞主线程
      string assetName = isLh ? skinConfig.Tachie : skinConfig.SpineRes;
      Debug.Log($"[UIHeroController] 开始加载spine 资源: {assetName}");
      SkeletonDataAsset loadedAsset = await ResManager.Instance.LoadAssetAsync<SkeletonDataAsset>("Hero/SpineRes/", assetName);
      // 再次检查是否已被取消
@@ -658,6 +689,7 @@
      if (loadedAsset != null)
      {
         skeletonGraphic.skeletonDataAsset = loadedAsset;
         Debug.Log($"[UIHeroController] Spine资源加载成功: {assetName}, atlas count: {loadedAsset.atlasAssets?.Length}");
      }
      else
      {