| using System; | 
| using System.Collections; | 
| using System.Collections.Generic; | 
| using Spine.Unity; | 
| using UnityEngine; | 
| using Spine; | 
| using UnityEngine.UI; | 
| using Cysharp.Threading.Tasks; | 
|   | 
| // 特效播放器,预制体对象池管理,unity的预制体池使用有问题(1个特效1个池但复用又低)后续修改 | 
| // unity特效《预制体》是特效师在制作的时候生成的不同预制体,unity特效必须挂载设置播放时长脚本 EffectTime | 
| // spine特效是特效师制作的动画文件,可共同复用一个《预制体》 | 
| public class EffectPlayer : MonoBehaviour | 
| { | 
|     [SerializeField] | 
|     private int m_EffectID; | 
|     public int effectId | 
|     { | 
|         get | 
|         { | 
|             return m_EffectID; | 
|         } | 
|         set | 
|         { | 
|             if (value != m_EffectID) | 
|             { | 
|                 Stop(); | 
|                 isInit = false; | 
|                 m_EffectID = value; | 
|             } | 
|         } | 
|     } | 
|   | 
|   | 
|   | 
|     public EffectConfig effectConfig; | 
|   | 
|     public float speedRate = 1f; | 
|   | 
|     [Header("是否循环播放spine特效")] | 
|     public bool isPlaySpineLoop = false; | 
|   | 
|     [Header("是否在显示时播放")] | 
|     public bool isPlayOnEnable = false; | 
|   | 
|     [Header("延迟播放(毫秒)")] | 
|     public int playDelayTime = 0; | 
|   | 
|     public int playSpineAnimIndex = -1; //播放spine特效动画索引,代码控制 | 
|   | 
|   | 
|     [Header("播放完毕立即回收")] | 
|     public bool isReleaseImmediately = false;  //界面特效一般不需要自我销毁,跟随界面或者父对象销毁就行 | 
|   | 
|     public Action<EffectPlayer> onDestroy; | 
|   | 
|     [HideInInspector] public Canvas canvas = null; | 
|   | 
|     [HideInInspector] public GameObject effectTarget = null; | 
|   | 
|     protected EffectPenetrationBlocker blocker = null; | 
|   | 
|     protected bool isInit = false; | 
|     public bool isPlaying | 
|     { | 
|         get; | 
|         protected set; | 
|     } | 
|   | 
|     protected List<ParticleSystem> particleList = new List<ParticleSystem>(); | 
|   | 
|     protected List<Animator> animatorList = new List<Animator>(); | 
|   | 
|     protected List<Renderer> rendererList = new List<Renderer>(); | 
|   | 
|     protected SkeletonGraphic spineComp; | 
|     protected Spine.AnimationState spineAnimationState; | 
|   | 
|     public GameObjectPoolManager.GameObjectPool pool; | 
|   | 
|     public Action onComplete; | 
|   | 
|     protected virtual void OnEnable() | 
|     { | 
|          | 
|         if (isPlayOnEnable) | 
|         { | 
|             Play(false); | 
|         } | 
|         else if (spineComp != null) | 
|         { | 
|             if (!isPlaying) | 
|             { | 
|                 //隐藏,会有静态显示问题 | 
|                 spineComp.enabled = false; | 
|             } | 
|         } | 
|     } | 
|   | 
|     protected void InitComponent(bool showLog = true) | 
|     { | 
|         if (effectId <= 0) | 
|         { | 
|             effectConfig = null; | 
| #if UNITY_EDITOR | 
|             if (showLog) | 
|             { | 
|                 Debug.LogError("EffectPlayer effectId is not set"); | 
|                 UnityEditor.Selection.activeGameObject = gameObject; | 
|                 UnityEditor.EditorGUIUtility.PingObject(gameObject); | 
|             } | 
| #endif | 
|             return; | 
|         } | 
|   | 
|         effectConfig = EffectConfig.Get(effectId); | 
|   | 
|         if (null == effectConfig) | 
|         { | 
| #if UNITY_EDITOR | 
|             Debug.LogError("could not find effect config, effect id is " + effectId); | 
|             UnityEditor.Selection.activeGameObject = gameObject; | 
|             UnityEditor.EditorGUIUtility.PingObject(gameObject); | 
| #endif | 
|             return; | 
|         } | 
|   | 
|         Clear(); | 
|         return; | 
|     } | 
|   | 
|     protected virtual void Clear() | 
|     { | 
|         isInit = false; | 
|         particleList.Clear(); | 
|         animatorList.Clear(); | 
|         rendererList.Clear(); | 
|   | 
|         if (spineComp != null) | 
|         { | 
|             spineComp.skeletonDataAsset = null; | 
|             // spineComp.Initialize(false);     | 
|             spineComp.Clear(); | 
|         } | 
|   | 
|         spineComp = null; | 
|     } | 
|   | 
|     public virtual void Stop() | 
|     { | 
|         if (null != effectTarget) | 
|         { | 
|             // 如果使用了特效预制体池,则归还到池中 | 
|             if (pool != null) | 
|             { | 
|                 pool.Release(effectTarget); | 
|             } | 
|             effectTarget = null; | 
|         } | 
|         if (spineComp != null) | 
|         { | 
|             spineComp.enabled = false; | 
|         } | 
|         isPlaying = false; | 
|         playSpineAnimIndex = -1; | 
|     } | 
|   | 
|     protected void Release() | 
|     { | 
|         Stop(); | 
|   | 
|         Clear(); | 
|     } | 
|   | 
|     public virtual void Play(bool showLog = true, bool closePMA = false) | 
|     { | 
|         isPlaying = true; | 
|         if (!isInit) | 
|         { | 
|             InitComponent(showLog); | 
|             //effeid 为0也初始化成功,避免重复处理,在变更effectid时会重新初始化 | 
|             isInit = true; | 
|         } | 
|         else | 
|         { | 
|             //避免重复创建 | 
|             if (!this.gameObject.activeSelf) | 
|             { | 
|                 this.gameObject.SetActive(true); | 
|             } | 
|             //防范effeid 为0 | 
|             if (effectConfig != null && effectConfig.isSpine != 0) | 
|             { | 
|                 PlaySpineEffect(closePMA); | 
|             } | 
|             return; | 
|         } | 
|         if (effectConfig == null) | 
|             return; | 
|   | 
|         if (EffectMgr.IsNotShowBySetting(effectId)) | 
|             { | 
|                 return; | 
|             } | 
|   | 
|         if (null != effectTarget) | 
|         { | 
|             if (pool != null) | 
|                 pool.Release(effectTarget); | 
|             effectTarget = null; | 
|         } | 
|   | 
|         if (!this.gameObject.activeSelf) | 
|         { | 
|             this.gameObject.SetActive(true); | 
|         } | 
|   | 
|         // 加载spine特效资源 | 
|         if (effectConfig.isSpine != 0) | 
|         { | 
|             PlaySpineEffect(closePMA); | 
|         } | 
|         else | 
|         { | 
|             PlayerEffect(); | 
|         } | 
|      | 
|         SoundPlayer.Instance.PlayUIAudio(effectConfig.audio); | 
|   | 
|     } | 
|   | 
|   | 
|     // protected virtual void PlaySpineEffect() | 
|     // { | 
|     //     spineComp = gameObject.GetComponentInChildren<SkeletonGraphic>(true); | 
|     //     spineComp.raycastTarget = false; | 
|     //     spineComp.Initialize(true); | 
|     //     spineAnimationState = spineComp.AnimationState; | 
|     //     spineAnimationState.Complete -= OnSpineAnimationComplete; | 
|     //     spineAnimationState.Complete += OnSpineAnimationComplete; | 
|   | 
|     //     // 外层控制具体播放哪个动画 | 
|     //     spineComp.enabled = true; | 
|   | 
|     //     return; | 
|     // } | 
|   | 
|   | 
|   | 
|     protected void PlaySpineEffect(bool closePMA = false) | 
|     { | 
|   | 
|         // 从特效预制体池获取特效 | 
|         if (spineComp == null) | 
|         {  | 
|             spineComp = gameObject.AddMissingComponent<SkeletonGraphic>(); | 
|         } | 
|   | 
|         if (spineComp.skeletonDataAsset == null || spineAnimationState == null) | 
|         { | 
|             //LoadAsset 已经有缓存SkeletonDataAsset | 
|             spineComp.skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>("UIEffect/" + effectConfig.packageName, effectConfig.fxName); | 
|             //为true时会有部分特效不显示 如主界面装备特效;改成伽马后不会出现BUG故注释代码 | 
|             // spineComp.MeshGenerator.settings.pmaVertexColors = !closePMA;    | 
|             spineComp.raycastTarget = false; | 
|             spineComp.Initialize(true); | 
|             spineComp.timeScale = speedRate; | 
|             // 检查动画是否有相加模式 | 
|             // bool hasAdditiveBlend = CheckForAdditiveBlend(spineComp.Skeleton); | 
|             // if (hasAdditiveBlend) | 
|             // { | 
|             //    spineComp.material = ResManager.Instance.LoadAsset<Material>("UIEffect/" + effectConfig.packageName, effectConfig.fxName.Split('_')[0] + "_Material-Additive"); | 
|             // } | 
|             // else | 
|             // { | 
|             //     spineComp.material = null; | 
|             // } | 
|             spineComp.material = ResManager.Instance.LoadAsset<Material>("Materials", "SkeletonGraphicDefault-Straight"); | 
|   | 
|             spineAnimationState = spineComp.AnimationState; | 
|             spineAnimationState.Data.DefaultMix = 0f; | 
|             spineAnimationState.Complete -= OnSpineAnimationComplete; | 
|             spineAnimationState.Complete += OnSpineAnimationComplete; | 
|         } | 
|   | 
|         spineComp.enabled = true; | 
|         PlayerTheSpineAnim(); | 
|     } | 
|   | 
|         // 播放指定动画 | 
|     void PlayerTheSpineAnim() | 
|     { | 
|         spineComp.enabled = true; | 
|         var skeletonData = spineComp.Skeleton.Data; | 
|         if (skeletonData.Animations.Count > 0) | 
|         { | 
|             //按配置或者默认第一个 | 
|             int defaultAnimIndex = Math.Max(0, effectConfig.animIndex.Length == 0 ? 0 : effectConfig.animIndex[0]); | 
|             if (playSpineAnimIndex >= skeletonData.Animations.Count) | 
|             { | 
|                 playSpineAnimIndex = -1; | 
|                 Debug.LogError("特效:" + effectConfig.id + " 索引超出播放默认动画。 error:" + playSpineAnimIndex); | 
|             } | 
|             string defaultAnimationName = skeletonData.Animations.Items[playSpineAnimIndex == -1 ? defaultAnimIndex : playSpineAnimIndex].Name; | 
|             spineAnimationState.SetAnimation(0, defaultAnimationName, isPlaySpineLoop); | 
|         } | 
|         else | 
|         { | 
|             Debug.LogError("Spine 数据中没有找到任何动画!" + effectConfig.id); | 
|         } | 
|     } | 
|   | 
|   | 
|     private bool CheckForAdditiveBlend(Spine.Skeleton skeleton) | 
|     { | 
|         // 遍历所有插槽,检查是否有相加模式 | 
|         foreach (var slot in skeleton.Slots) | 
|         { | 
|             if (slot.Data.BlendMode == Spine.BlendMode.Additive) | 
|             { | 
|                 return true; | 
|             } | 
|         } | 
|         return false; | 
|     } | 
|   | 
|   | 
|     //unity特效 | 
|     protected virtual void PlayerEffect() | 
|     { | 
|         var effectPrefab = ResManager.Instance.LoadAsset<GameObject>("UIEffect/" + effectConfig.packageName, effectConfig.fxName); | 
|         if (effectPrefab == null) | 
|         { | 
|             Debug.LogError($"加载UI特效失败: {effectConfig.packageName}"); | 
|             return; | 
|         } | 
|   | 
|         if (effectConfig.isSpine == 0) | 
|         { | 
|             //unity特效 判断预制体是否挂载EffectTime | 
|             var timeObj = effectPrefab.GetComponent<EffectTime>(); | 
|             if (null == timeObj) | 
|             { | 
|                 Debug.LogError($"{effectPrefab.name}预制体没有挂载EffectTime"); | 
|                 return; | 
|             } | 
|         } | 
|   | 
|         // 从特效预制体池获取特效 | 
|         pool = GameObjectPoolManager.Instance.RequestPool(effectPrefab); | 
|         effectTarget = pool.Request(); | 
|         // 设置父节点和位置 | 
|         effectTarget.transform.SetParent(transform); | 
|         effectTarget.transform.localPosition = Vector3.zero; | 
|         effectTarget.transform.localScale = Vector3.one; | 
|         effectTarget.transform.localRotation = Quaternion.identity; | 
|         effectTarget.name = $"Effect_{effectConfig.fxName}"; | 
|   | 
|   | 
|         //挂载组件后 开始收集 | 
|         particleList.AddRange(gameObject.GetComponentsInChildren<ParticleSystem>(true)); | 
|         animatorList.AddRange(gameObject.GetComponentsInChildren<Animator>(true)); | 
|         rendererList.AddRange(gameObject.GetComponentsInChildren<Renderer>(true)); | 
|         OnUnityAnimationComplete(); | 
|      | 
|   | 
|         if (null == canvas) | 
|             canvas = GetComponentInParent<Canvas>(); | 
|   | 
|         // 添加特效穿透阻挡器 | 
|         blocker = effectTarget.AddMissingComponent<EffectPenetrationBlocker>(); | 
|   | 
|         //  如果没有canvas的话 正常是因为不在BattleWin下面的节点 意思就是当前没有显示 等到切回战斗的时候再通过BattleField.UpdateCanvas来更新 | 
|         if (canvas != null) | 
|         { | 
|             blocker.SetParentCanvas(canvas); | 
|         } | 
|     } | 
|   | 
|     public async UniTask PlayAsync(bool showLog = true, bool closePMA = false) | 
|     { | 
|         await UniTask.Delay(playDelayTime); | 
|         try | 
|         { | 
|             Play(showLog, closePMA); | 
|         } | 
|         catch (Exception e) | 
|         { | 
|             Debug.LogError(e); | 
|         } | 
|     } | 
|   | 
|     protected void OnDestroy() | 
|     { | 
|         if (onDestroy != null) | 
|         { | 
|             onDestroy.Invoke(this); | 
|             onDestroy = null; | 
|         } | 
|         // 停止特效并归还到池中 | 
|         Release(); | 
|   | 
|         if (spineAnimationState != null) | 
|         { | 
|             spineAnimationState.Complete -= OnSpineAnimationComplete; | 
|         } | 
|   | 
|   | 
|     } | 
|   | 
|     //单次播放完毕就会触发,即使是循环 | 
|     protected virtual void OnSpineAnimationComplete(Spine.TrackEntry trackEntry) | 
|     { | 
|         if (!isPlaySpineLoop) | 
|         { | 
|             spineComp.enabled = false; | 
|             isPlaying = false; | 
|             onComplete?.Invoke(); | 
|             if (isReleaseImmediately) | 
|             { | 
|                 Release(); | 
|             } | 
|   | 
|         } | 
|     } | 
|   | 
|   | 
|     private void OnUnityAnimationComplete() | 
|     { | 
|         var timeObj = effectTarget.GetComponent<EffectTime>(); | 
|         if (!timeObj.isLoop) | 
|         { | 
|             this.SetWait(timeObj.playTime); | 
|             this.DoWaitRestart(); | 
|             this.OnWaitCompelete(OnEffectComplete); | 
|         } | 
|     } | 
|   | 
|     private void OnEffectComplete(Component comp) | 
|     { | 
|         this.DoWaitStop(); | 
|         if (isReleaseImmediately) | 
|             Release(); | 
|     } | 
|   | 
|   | 
|     //  创建后的特效会自动隐藏 需要手动调用Play才能播放 | 
|     public static EffectPlayer Create(int effectId, Transform parent, bool createNewChild = false) | 
|     { | 
|         // 直接创建特效播放器,不使用对象池 | 
|         EffectPlayer effectPlayer = null; | 
|   | 
|         if (createNewChild) | 
|         { | 
|             GameObject newGo = new GameObject("EffectPlayer_" + effectId); | 
|             newGo.transform.SetParent(parent, false); | 
|             effectPlayer = newGo.AddComponent<EffectPlayer>(); | 
|         } | 
|         else | 
|         { | 
|             effectPlayer = parent.AddMissingComponent<EffectPlayer>(); | 
|         } | 
|   | 
|         effectPlayer.effectId = effectId; | 
|         effectPlayer.SetActive(true); | 
|         return effectPlayer; | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 设置游戏对象激活状态 | 
|     // /// </summary> | 
|     // public void SetActive(bool active) | 
|     // { | 
|     //     if (gameObject != null) | 
|     //     { | 
|     //         gameObject.SetActive(active); | 
|     //     } | 
|     // } | 
|   | 
|     public void Pause() | 
|     { | 
|         if (effectTarget == null) return; | 
|   | 
|         // Spine动画 | 
|         // var spineGraphics = effectTarget.GetComponentsInChildren<SkeletonGraphic>(true); | 
|         // foreach (var sg in spineGraphics) | 
|         if (spineComp != null) | 
|         { | 
|             spineComp.timeScale = 0f; | 
|         } | 
|   | 
|         // Animator动画 | 
|         foreach (var animator in animatorList) | 
|         { | 
|             animator.speed = 0f; | 
|         } | 
|   | 
|         // 粒子特效 | 
|         foreach (var ps in particleList) | 
|         { | 
|             ps.Pause(); | 
|         } | 
|     } | 
|   | 
|     public void Resume() | 
|     { | 
|         if (effectTarget == null) return; | 
|   | 
|         if (spineComp != null) | 
|         { | 
|             spineComp.timeScale = speedRate; | 
|         } | 
|   | 
|         // Animator动画 | 
|         foreach (var animator in animatorList) | 
|         { | 
|             animator.speed = speedRate; | 
|         } | 
|   | 
|         // 粒子特效 | 
|         foreach (var ps in particleList) | 
|         { | 
|             ps.Play(); | 
|         } | 
|     } | 
|   | 
|     public bool IsFinish() | 
|     { | 
|         if (effectTarget == null) return true; | 
|   | 
|         // Spine动画 | 
|         if (!spineComp.AnimationState.GetCurrent(0).IsComplete) | 
|         { | 
|             return false; | 
|         } | 
|   | 
|         // Animator动画 | 
|         foreach (var animator in animatorList) | 
|         { | 
|             AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0); | 
|   | 
|             //  循环动画不考虑结束的问题 | 
|             if (!stateInfo.loop && stateInfo.normalizedTime < 1f) | 
|             { | 
|                 return false; | 
|             } | 
|         } | 
|   | 
|         // 粒子特效 | 
|         foreach (var ps in particleList) | 
|         { | 
|             if (ps.IsAlive()) | 
|             { | 
|                 return false; | 
|             } | 
|         } | 
|   | 
|         return true; | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 设置遮罩(支持RectMask2D、Mask、SmoothMask等) | 
|     /// </summary> | 
|     public void SetMask(RectTransform maskArea = null) | 
|     { | 
|         if (effectTarget == null || blocker == null) | 
|             return; | 
|   | 
|         // 优先使用传入的maskArea | 
|         if (maskArea != null) | 
|         { | 
|             blocker.PerformMask(maskArea); | 
|             return; | 
|         } | 
|     } | 
|   | 
|   | 
| } |