| | |
| | | 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) |
| | |
| | | { |
| | | //图片替换 |
| | | lhImg.SetTexture2DPNG(skinConfig.Tachie); |
| | | lhImg.SetNativeSize(); |
| | | if (skeletonGraphic != null) |
| | | { |
| | | skeletonGraphic.enabled = false; |
| | | } |
| | | lhImg.enabled = true; |
| | | lhImg.raycastTarget = false; |
| | | return; |
| | | } |
| | |
| | | } |
| | | |
| | | 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) |
| | |
| | | } |
| | | |
| | | skeletonGraphic.enabled = true; |
| | | // 注册到SpineUpdateManager,批量更新减少跨语言调用开销 |
| | | SpineUpdateManager.Instance.Register(skeletonGraphic); |
| | | // 不可见时完全停止更新,滚动列表场景收益很大 |
| | | skeletonGraphic.updateWhenInvisible = Spine.Unity.UpdateMode.Nothing; |
| | | SetMaterialNone(); |
| | | |
| | | spineAnimationState = skeletonGraphic.AnimationState; |
| | |
| | | spineAnimationState.Complete -= OnAnimationComplete; |
| | | spineAnimationState.Complete += OnAnimationComplete; |
| | | |
| | | #if UNITY_EIDTOR |
| | | #if UNITY_EDITOR |
| | | await UniTask.Delay(100); |
| | | if (skeletonGraphic != null && skeletonGraphic.material != null) |
| | | { |
| | |
| | | { |
| | | spineAnimationState.Complete -= OnAnimationComplete; |
| | | } |
| | | // 从SpineUpdateManager中移除 |
| | | if (skeletonGraphic != null) |
| | | SpineUpdateManager.Instance.Unregister(skeletonGraphic); |
| | | if (pool != null) |
| | | pool.Release(instanceGO); |
| | | skeletonGraphic = null; |
| | |
| | | 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> |
| | |
| | | // 异步创建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) |
| | | { |
| | |
| | | |
| | | skeletonGraphic.initialSkinName = skinConfig.InitialSkinName; |
| | | skeletonGraphic.Initialize(true); |
| | | Debug.Log($"[UIHeroController] Spine Initialize完成, skeleton valid: {skeletonGraphic.IsValid}, enabled: {skeletonGraphic.enabled}"); |
| | | |
| | | // 初始化完成后设置皮肤 |
| | | if (!string.IsNullOrEmpty(skinConfig.InitialSkinName)) |
| | |
| | | // 初始化完成后才显示模型 |
| | | 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(); |
| | | } |
| | | |
| | | // 检查是否有待设置的速度,如果有则设置 |
| | |
| | | } |
| | | catch (System.Exception e) |
| | | { |
| | | Debug.LogError($"英雄初始化异常: {e.Message}"); |
| | | Debug.LogError($"英雄初始化异常: {e.Message}\n{e.StackTrace}"); |
| | | isInitializing = false; |
| | | } |
| | | finally |
| | |
| | | // 检查是否已被取消 |
| | | 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) |
| | | { |
| | |
| | | |
| | | // 真正的异步加载资源 - 不阻塞主线程 |
| | | string assetName = isLh ? skinConfig.Tachie : skinConfig.SpineRes; |
| | | Debug.Log($"[UIHeroController] 开始加载spine 资源: {assetName}"); |
| | | SkeletonDataAsset loadedAsset = await ResManager.Instance.LoadAssetAsync<SkeletonDataAsset>("Hero/SpineRes/", assetName); |
| | | |
| | | // 再次检查是否已被取消 |
| | |
| | | if (loadedAsset != null) |
| | | { |
| | | skeletonGraphic.skeletonDataAsset = loadedAsset; |
| | | Debug.Log($"[UIHeroController] Spine资源加载成功: {assetName}, atlas count: {loadedAsset.atlasAssets?.Length}"); |
| | | } |
| | | else |
| | | { |