using System; using System.Threading; using Spine; using Spine.Unity; using UnityEngine; using UnityEngine.UI; using Cysharp.Threading.Tasks; public class UIHeroController : MonoBehaviour { private GameObjectPoolManager.GameObjectPool pool; private int skinID; protected SkeletonGraphic skeletonGraphic; public Spine.AnimationState spineAnimationState; private GameObject instanceGO; private bool isInitializing = false; private bool isInitialized = false; 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) { if (skinID == _skinID) { //避免重复创建 if (skeletonGraphic != null) { SetMaterialNone(); if (isLh) { var skinConfigTmp = HeroSkinConfig.Get(skinID); if (skinConfigTmp != null && skinConfigTmp.Tachie.IsSpine()) { skeletonGraphic.enabled = true; } } else { skeletonGraphic.enabled = true; } } return; } if (skeletonGraphic != null) { skeletonGraphic.enabled = false; } skinID = _skinID; var skinConfig = HeroSkinConfig.Get(skinID); if (isLh) { //X轴偏移,Y轴偏移,缩放,是否水平翻转(0否1是) if (skinConfig.TachieParam.Length == 4) { this.transform.localPosition = new Vector3(skinConfig.TachieParam[0], skinConfig.TachieParam[1], 0); this.transform.localScale = Vector3.one * skinConfig.TachieParam[2]; this.transform.localRotation = Quaternion.Euler(0, skinConfig.TachieParam[3] == 0 ? 0 : 180, 0); } else { this.transform.localPosition = Vector3.zero; this.transform.localScale = Vector3.one; this.transform.localRotation = Quaternion.identity; } //立绘特殊处理,没有spine动画的改用图片 var lhImg = this.AddMissingComponent(); if (!skinConfig.Tachie.IsSpine()) { //图片替换 lhImg.SetTexture2DPNG(skinConfig.Tachie); lhImg.SetNativeSize(); if (skeletonGraphic != null) { skeletonGraphic.enabled = false; } lhImg.enabled = true; lhImg.raycastTarget = false; return; } else { if (skeletonGraphic != null) { skeletonGraphic.enabled = true; } lhImg.enabled = false; } } else { this.transform.localScale = Vector3.one * scale; } onComplete = _onComplete; // 取消之前的异步任务,避免多次调用导致错乱 CancelLoadTask(); // 创建新的取消令牌 loadCancellationToken = new CancellationTokenSource(); // 使用 UniTask 进行异步初始化,将instanceGO创建和资源加载都移到异步处理 DelayedInitializeAsync(skinConfig, motionName, isLh, loadCancellationToken.Token).Forget(); } /// /// 取消之前的加载任务 /// private void CancelLoadTask() { if (loadCancellationToken != null && !loadCancellationToken.IsCancellationRequested) { loadCancellationToken.Cancel(); loadCancellationToken.Dispose(); } } public bool HasAnimation(string motionName) { if (skeletonGraphic == null || skeletonGraphic.Skeleton == null) { Debug.LogWarning("skeletonGraphic or Skeleton is null, cannot check animation: " + motionName); return false; } return skeletonGraphic.Skeleton.ContainsMotion(motionName); } protected void OnDestroy() { // 取消正在进行的加载任务 CancelLoadTask(); if (spineAnimationState != null) { spineAnimationState.Complete -= OnAnimationComplete; } if (pool != null) pool.Release(instanceGO); skeletonGraphic = null; pool = null; } private string pendingAnimationName = null; private bool pendingAnimationLoop = false; private bool pendingAnimationReplay = true; private float? pendingSpeed = null; private bool pendingEnabled = true; // 改为普通bool,false表示未设置,true表示需要启用 private bool pendingGray = false; // 改为普通bool,表示是否需要设置灰度 /// /// 播放 Spine 动画 /// /// 动作名 /// 循环 /// 如果相同动作是否再次重播,比如跑步重播就会跳帧不顺滑 public virtual TrackEntry PlayAnimation(string motionName, bool loop = false, bool replay = true) { // 如果正在初始化中,保存动画参数,等待初始化完成后再播放 if (isInitializing) { pendingAnimationName = motionName; pendingAnimationLoop = loop; pendingAnimationReplay = replay; return null; } if (spineAnimationState == null) { Debug.LogWarning("spineAnimationState is null, cannot play animation: " + motionName); return null; } if (GetCurrentAnimationName() == motionName && !replay) return null; // 直接使用 ToString() 而不是调用 GetAnimationName try { return spineAnimationState.SetAnimation(0, motionName.ToString(), loop); } catch (System.Exception e) { Debug.LogError("播放动画失败: " + motionName + ", 错误: " + e.Message); return null; } } // 播放第一个动画(作为默认动画) string GetFistSpineAnim() { if (skeletonGraphic == null || skeletonGraphic.Skeleton == null) { Debug.LogWarning("skeletonGraphic or Skeleton is null, cannot get first animation"); return "idle"; // 返回默认动画名称 } var skeletonData = skeletonGraphic.Skeleton.Data; if (skeletonData.Animations.Count > 0) { return skeletonData.Animations.Items[0].Name; } else { Debug.LogError("Spine 数据中没有找到任何动画!武将皮肤:" + skinID); } return "idle"; // 返回默认动画名称 } /// /// 获取当前正在播放的 Spine 动画名称 /// /// 当前动画名称,如果没有动画则返回空字符串 public string GetCurrentAnimationName() { if (spineAnimationState == null || spineAnimationState.GetCurrent(0) == null) { return string.Empty; } return spineAnimationState.GetCurrent(0).Animation.Name; } /// /// 动画完成事件处理 /// protected virtual void OnAnimationComplete(Spine.TrackEntry trackEntry) { onComplete?.Invoke(); } //越大越快 public void SetSpeed(float speed) { // 如果正在初始化中,保存速度参数,等待初始化完成后再设置 if (isInitializing) { pendingSpeed = speed; return; } if (spineAnimationState == null) { Debug.LogWarning("spineAnimationState is null, cannot set speed"); return; } spineAnimationState.TimeScale = speed; } public void SetEnabled(bool isEnable) { // 如果正在初始化中,保存启用状态,等待初始化完成后再设置 if (isInitializing) { pendingEnabled = isEnable; return; } if (skeletonGraphic == null) { Debug.LogWarning("skeletonGraphic is null, cannot set enabled state"); return; } skeletonGraphic.enabled = isEnable; } public void SetGray() { // 如果正在初始化中,标记需要设置灰度,等待初始化完成后再设置 if (isInitializing) { pendingGray = true; return; } if (skeletonGraphic == null) { Debug.LogWarning("skeletonGraphic is null, cannot set gray material"); return; } skeletonGraphic.material = MaterialUtility.GetDefaultSpriteGrayMaterial(); } public void SetMaterialNone() { // 如果正在初始化中,标记需要设置无材质,等待初始化完成后再设置 if (isInitializing) { pendingGray = false; return; } if (skeletonGraphic == null) { Debug.LogWarning("skeletonGraphic is null, cannot set material to none"); return; } skeletonGraphic.material = null; } /// /// 延迟初始化,结合并发控制和分帧延迟 /// 1. 资源加载使用真正的异步(LoadAssetAsync) /// 2. skeletonGraphic.Initialize() 前进行分帧延迟,避免主线程卡顿 /// private async UniTaskVoid DelayedInitializeAsync(HeroSkinConfig skinConfig, string motionName, bool isLh, CancellationToken cancellationToken) { isInitializing = true; try { // 检查是否已被取消 cancellationToken.ThrowIfCancellationRequested(); // 获取加载信号量 - 限制并发数,避免资源竞争 await AcquireLoadSlotAsync(cancellationToken); // 异步创建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) { for (int i = 0; i < delayFrames; i++) { cancellationToken.ThrowIfCancellationRequested(); await UniTask.NextFrame(cancellationToken); } } // 再次检查是否已被取消(可能在延迟期间被取消) cancellationToken.ThrowIfCancellationRequested(); if (skeletonGraphic == null || skeletonGraphic.skeletonDataAsset == null) { Debug.LogError("资源加载失败,无法初始化模型"); return; } skeletonGraphic.initialSkinName = skinConfig.InitialSkinName; skeletonGraphic.Initialize(true); // 初始化完成后设置皮肤 if (!string.IsNullOrEmpty(skinConfig.InitialSkinName)) { var skeleton = skeletonGraphic.Skeleton; skeleton.SetSkin(skinConfig.InitialSkinName); skeleton.SetSlotsToSetupPose(); skeletonGraphic.Update(0); } spineAnimationState = skeletonGraphic.AnimationState; spineAnimationState.Data.DefaultMix = 0f; // 初始化完成后才显示模型 skeletonGraphic.enabled = pendingEnabled; if (pendingGray) { skeletonGraphic.material = MaterialUtility.GetDefaultSpriteGrayMaterial(); } else { skeletonGraphic.material = null; } // 检查是否有待设置的速度,如果有则设置 if (pendingSpeed.HasValue) { spineAnimationState.TimeScale = pendingSpeed.Value; pendingSpeed = null; } // 检查是否有待播放的动画,如果有则优先播放外部调用的动画 if (!string.IsNullOrEmpty(pendingAnimationName)) { isInitializing = false; PlayAnimation(pendingAnimationName, pendingAnimationLoop, pendingAnimationReplay); // 清除所有待播放动画参数 pendingAnimationName = null; pendingAnimationLoop = false; pendingAnimationReplay = true; } else { // 如果没有外部调用的动画,播放默认动画 if (motionName == "") motionName = GetFistSpineAnim(); isInitializing = false; PlayAnimation(motionName, true); } spineAnimationState.Complete -= OnAnimationComplete; spineAnimationState.Complete += OnAnimationComplete; isInitialized = true; isInitializing = false; } catch (System.OperationCanceledException) { // 任务被取消,正常返回 isInitializing = false; } catch (System.Exception e) { Debug.LogError($"英雄初始化异常: {e.Message}"); isInitializing = false; } finally { // 释放加载槽位 ReleaseLoadSlot(); } } /// /// 获取加载槽位(支持 WebGL 的并发控制) /// private async UniTask AcquireLoadSlotAsync(CancellationToken cancellationToken) { while (true) { // 检查是否已被取消 cancellationToken.ThrowIfCancellationRequested(); lock (loadLock) { if (currentLoadingCount < MAX_CONCURRENT_LOADS) { currentLoadingCount++; return; } } // 如果已达到最大并发数,等待下一帧再试 await UniTask.NextFrame(cancellationToken); } } /// /// 释放加载槽位 /// private void ReleaseLoadSlot() { lock (loadLock) { currentLoadingCount--; } } /// /// 异步创建instanceGO和加载资源(用于非立绘) /// 使用真正的异步加载,不阻塞主线程 /// private async UniTask CreateInstanceAndLoadAssetsAsync(HeroSkinConfig skinConfig, bool isLh, CancellationToken cancellationToken) { // 确保transform处于激活状态 if (!transform.gameObject.activeSelf) { transform.SetActive(true); } // 检查是否已被取消 cancellationToken.ThrowIfCancellationRequested(); // 创建pool和instanceGO pool = GameObjectPoolManager.Instance.GetPool(UILoader.LoadPrefab("UIHero")); if (instanceGO == null) { instanceGO = pool.Request(); instanceGO.transform.SetParent(transform); //transform 的Pivot Y是0,让instanceGO 居中 instanceGO.transform.localPosition = new Vector3(0, instanceGO.GetComponent().sizeDelta.y * 0.5f); instanceGO.transform.localScale = Vector3.one; instanceGO.transform.localRotation = Quaternion.identity; } skeletonGraphic = instanceGO.GetComponentInChildren(true); // 真正的异步加载资源 - 不阻塞主线程 string assetName = isLh ? skinConfig.Tachie : skinConfig.SpineRes; SkeletonDataAsset loadedAsset = await ResManager.Instance.LoadAssetAsync("Hero/SpineRes/", assetName); // 再次检查是否已被取消 cancellationToken.ThrowIfCancellationRequested(); if (loadedAsset != null) { skeletonGraphic.skeletonDataAsset = loadedAsset; } else { transform.SetActive(false); if (pool != null) pool.Release(instanceGO); skeletonGraphic = null; Destroy(instanceGO); Debug.LogError("未配置spine"); } } /// /// 检查是否已完成初始化 /// public bool IsInitialized() { return isInitialized && !isInitializing; } }