| | |
| | | using Spine.Unity; |
| | | using UnityEngine; |
| | | using UnityEngine.UI; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public class UIHeroController : MonoBehaviour |
| | | { |
| | |
| | | |
| | | public Spine.AnimationState spineAnimationState; |
| | | private GameObject instanceGO; |
| | | private bool isInitializing = false; |
| | | private bool isInitialized = false; |
| | | private static int activeInitializationCount = 0; // 当前正在初始化的卡片数量 |
| | | private static readonly object initializationLock = new object(); |
| | | |
| | | public Action onComplete; |
| | | public void Create(int _skinID, float scale = 0.8f, Action _onComplete = null, string motionName = "idle", bool isLh = false) |
| | |
| | | Debug.LogError("未配置spine"); |
| | | return; |
| | | } |
| | | skeletonGraphic.initialSkinName = skinConfig.InitialSkinName; |
| | | skeletonGraphic.Initialize(true); |
| | | // 初始化完成后设置皮肤 |
| | | if (!string.IsNullOrEmpty(skinConfig.InitialSkinName)) |
| | | |
| | | // 使用 UniTask 进行延迟初始化,避免批量创建时卡顿 |
| | | if (isInitializing) |
| | | { |
| | | var skeleton = skeletonGraphic.Skeleton; |
| | | skeleton.SetSkin(skinConfig.InitialSkinName); |
| | | skeleton.SetSlotsToSetupPose(); |
| | | skeletonGraphic.Update(0); |
| | | // 如果正在初始化,设置标志位取消之前的任务 |
| | | isInitializing = false; |
| | | } |
| | | |
| | | skeletonGraphic.enabled = true; |
| | | SetMaterialNone(); |
| | | |
| | | spineAnimationState = skeletonGraphic.AnimationState; |
| | | spineAnimationState.Data.DefaultMix = 0f; |
| | | if (motionName == "") |
| | | motionName = GetFistSpineAnim(); |
| | | |
| | | PlayAnimation(motionName, true); |
| | | spineAnimationState.Complete -= OnAnimationComplete; |
| | | spineAnimationState.Complete += OnAnimationComplete; |
| | | |
| | | // 立绘需要立即显示,其他情况延迟初始化 |
| | | if (isLh) |
| | | { |
| | | // 立绘立即初始化 |
| | | ForceInitialize(motionName); |
| | | } |
| | | else |
| | | { |
| | | // 在加载新模型前先隐藏当前显示的模型(非立绘情况) |
| | | if (skeletonGraphic != null) |
| | | { |
| | | skeletonGraphic.enabled = false; |
| | | } |
| | | |
| | | // 使用 UniTask 进行异步初始化 |
| | | DelayedInitializeAsync(skinConfig, motionName).Forget(); |
| | | } |
| | | } |
| | | |
| | | public bool HasAnimation(string motionName) |
| | | { |
| | | return skeletonGraphic != null && skeletonGraphic.Skeleton != null && skeletonGraphic.Skeleton.ContainsMotion(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); |
| | | } |
| | | |
| | | |
| | |
| | | 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,表示是否需要设置灰度 |
| | | |
| | | /// <summary> |
| | | /// 播放 Spine 动画 |
| | | /// </summary> |
| | |
| | | /// <param name="replay">如果相同动作是否再次重播,比如跑步重播就会跳帧不顺滑</param> |
| | | public virtual TrackEntry PlayAnimation(string motionName, bool loop = false, bool replay = true) |
| | | { |
| | | if (spineAnimationState == null) return null; |
| | | // 如果正在初始化中,保存动画参数,等待初始化完成后再播放 |
| | | 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 |
| | | return spineAnimationState.SetAnimation(0, motionName.ToString(), loop); |
| | | 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) |
| | | { |
| | |
| | | { |
| | | Debug.LogError("Spine 数据中没有找到任何动画!武将皮肤:" + skinID); |
| | | } |
| | | return ""; |
| | | return "idle"; // 返回默认动画名称 |
| | | } |
| | | |
| | | /// <summary> |
| | |
| | | //越大越快 |
| | | 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 延迟初始化,避免批量创建时卡顿 - 使用 UniTask |
| | | /// </summary> |
| | | private async UniTaskVoid DelayedInitializeAsync(HeroSkinConfig skinConfig, string motionName) |
| | | { |
| | | isInitializing = true; |
| | | |
| | | try |
| | | { |
| | | // 增加当前初始化计数器 |
| | | int currentIndex; |
| | | lock (initializationLock) |
| | | { |
| | | currentIndex = activeInitializationCount++; |
| | | } |
| | | |
| | | // 根据当前初始化序号计算延迟时间 |
| | | // 第一个卡片延迟1帧,第二个延迟2帧,以此类推,最多延迟10帧 |
| | | int delayFrames = Mathf.Min(currentIndex + 2, 80); |
| | | |
| | | for (int i = 0; i < delayFrames; i++) |
| | | { |
| | | await UniTask.NextFrame(); |
| | | } |
| | | |
| | | 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能正常播放 |
| | | isInitializing = false; |
| | | PlayAnimation(pendingAnimationName, pendingAnimationLoop, pendingAnimationReplay); |
| | | // 清除所有待播放动画参数 |
| | | pendingAnimationName = null; |
| | | pendingAnimationLoop = false; |
| | | pendingAnimationReplay = true; |
| | | } |
| | | else |
| | | { |
| | | // 如果没有外部调用的动画,播放默认动画 |
| | | if (motionName == "") |
| | | motionName = GetFistSpineAnim(); |
| | | |
| | | // 临时设置isInitializing为false,以便PlayAnimation能正常播放 |
| | | isInitializing = false; |
| | | PlayAnimation(motionName, true); |
| | | } |
| | | |
| | | spineAnimationState.Complete -= OnAnimationComplete; |
| | | spineAnimationState.Complete += OnAnimationComplete; |
| | | |
| | | isInitialized = true; |
| | | isInitializing = false; |
| | | } |
| | | catch (System.OperationCanceledException) |
| | | { |
| | | // 任务被取消,正常处理 |
| | | isInitializing = false; |
| | | } |
| | | finally |
| | | { |
| | | // 减少当前初始化计数器 |
| | | lock (initializationLock) |
| | | { |
| | | activeInitializationCount--; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 强制立即初始化(用于特殊情况) |
| | | /// </summary> |
| | | public void ForceInitialize(string motionName = "idle") |
| | | { |
| | | if (isInitializing) |
| | | { |
| | | // 设置标志位取消之前的异步任务 |
| | | isInitializing = false; |
| | | } |
| | | |
| | | var skinConfig = HeroSkinConfig.Get(skinID); |
| | | if (skinConfig != null && skeletonGraphic != null) |
| | | { |
| | | 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); |
| | | } |
| | | |
| | | skeletonGraphic.enabled = true; |
| | | SetMaterialNone(); |
| | | |
| | | spineAnimationState = skeletonGraphic.AnimationState; |
| | | if (spineAnimationState != null) |
| | | { |
| | | spineAnimationState.Data.DefaultMix = 0f; |
| | | if (string.IsNullOrEmpty(motionName)) |
| | | motionName = GetFistSpineAnim(); |
| | | PlayAnimation(motionName, true); |
| | | spineAnimationState.Complete -= OnAnimationComplete; |
| | | spineAnimationState.Complete += OnAnimationComplete; |
| | | } |
| | | |
| | | isInitialized = true; |
| | | isInitializing = false; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /// <summary> |
| | | /// 检查是否已完成初始化 |
| | | /// </summary> |
| | | public bool IsInitialized() |
| | | { |
| | | return isInitialized && !isInitializing; |
| | | } |
| | | } |