| | |
| | | using UnityEngine; |
| | | using Spine; |
| | | using UnityEngine.UI; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | // 特效播放器,预制体对象池管理,unity的预制体池使用有问题(1个特效1个池但复用又低)后续修改 |
| | | // unity特效《预制体》是特效师在制作的时候生成的不同预制体,unity特效必须挂载设置播放时长脚本 EffectTime |
| | | // spine特效是特效师制作的动画文件,可共同复用一个《预制体》 |
| | | public class EffectPlayer : MonoBehaviour |
| | | { |
| | | public int effectId; |
| | | [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 Action<EffectPlayer> onDestroy; |
| | | public Action onComplete; |
| | | |
| | | public float speedRate = 1f; |
| | | |
| | | [Header("是否循环播放spine特效")] |
| | | public bool isPlaySpineLoop = false; |
| | | |
| | | [Header("是否在显示时播放")] |
| | | public bool isPlayOnEnable = false; |
| | | |
| | | [Header("是否立即播放spine特效")] |
| | | public bool isPlaySpineImmediately = false; |
| | | [Header("是否循环播放spine特效")] |
| | | public bool isPlaySpineLoop = 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; |
| | | |
| | |
| | | protected EffectPenetrationBlocker blocker = null; |
| | | |
| | | protected bool isInit = false; |
| | | public bool isPlaying |
| | | { |
| | | get; |
| | | protected set; |
| | | } |
| | | |
| | | protected List<ParticleSystem> particleList = new List<ParticleSystem>(); |
| | | |
| | |
| | | protected SkeletonGraphic spineComp; |
| | | protected Spine.AnimationState spineAnimationState; |
| | | |
| | | protected void OnEnable() |
| | | public GameObjectPoolManager.GameObjectPool pool; |
| | | |
| | | public Action onComplete; |
| | | |
| | | protected virtual void OnEnable() |
| | | { |
| | | |
| | | if (isPlayOnEnable) |
| | | { |
| | | Play(); |
| | | Play(false); |
| | | } |
| | | else if (spineComp != null) |
| | | { |
| | | if (!isPlaying) |
| | | { |
| | | //隐藏,会有静态显示问题 |
| | | spineComp.enabled = false; |
| | | } |
| | | } |
| | | } |
| | | |
| | | protected bool InitCompnent() |
| | | protected void InitComponent(bool showLog = true) |
| | | { |
| | | if (effectId <= 0) |
| | | { |
| | | effectConfig = null; |
| | | // 根据逻辑需求,动态设置特效的情况 |
| | | // Debug.LogError("EffectPlayer effectId is not set"); |
| | | // #if UNITY_EDITOR |
| | | // UnityEditor.Selection.activeGameObject = gameObject; |
| | | // UnityEditor.EditorGUIUtility.PingObject(gameObject); |
| | | // #endif |
| | | return false; |
| | | #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) |
| | | { |
| | | Debug.LogError("could not find effect config, effect id is " + effectId); |
| | | #if UNITY_EDITOR |
| | | Debug.LogError("could not find effect config, effect id is " + effectId); |
| | | UnityEditor.Selection.activeGameObject = gameObject; |
| | | UnityEditor.EditorGUIUtility.PingObject(gameObject); |
| | | #endif |
| | | return false; |
| | | } |
| | | |
| | | |
| | | particleList.Clear(); |
| | | animatorList.Clear(); |
| | | rendererList.Clear(); |
| | | spineComp = null; |
| | | |
| | | //spine是加载文件,需要提前挂载脚本,unity特效是已经做好的预制体待加载后收集组件 |
| | | if (effectConfig.isSpine != 0) |
| | | { |
| | | spineComp = gameObject.AddMissingComponent<SkeletonGraphic>(); |
| | | |
| | | } |
| | | // else |
| | | // { |
| | | // // 收集组件 |
| | | // particleList.AddRange(gameObject.GetComponentsInChildren<ParticleSystem>(true)); |
| | | // animatorList.AddRange(gameObject.GetComponentsInChildren<Animator>(true)); |
| | | // } |
| | | // rendererList.AddRange(gameObject.GetComponentsInChildren<Renderer>(true)); |
| | | |
| | | return true; |
| | | } |
| | | |
| | | public void Stop() |
| | | { |
| | | if (null != effectTarget) |
| | | { |
| | | DestroyImmediate(effectTarget); |
| | | effectTarget = null; |
| | | } |
| | | |
| | | isInit = false; |
| | | |
| | | particleList.Clear(); |
| | | animatorList.Clear(); |
| | | rendererList.Clear(); |
| | | spineComp = null; |
| | | } |
| | | |
| | | public void Play() |
| | | { |
| | | ReStart(); |
| | | } |
| | | |
| | | |
| | | protected void ReStart() |
| | | { |
| | | if (!isInit) |
| | | { |
| | | if (InitCompnent()) |
| | | { |
| | | isInit = true; |
| | | } |
| | | else |
| | | { |
| | | //根据逻辑需求,动态设置特效的情况 |
| | | return; |
| | | } |
| | | |
| | | } |
| | | |
| | | if (EffectMgr.Instance.IsNotShowBySetting(effectId)) |
| | | { |
| | | 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) |
| | | { |
| | | DestroyImmediate(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; |
| | | } |
| | | |
| | | // YYL TODO |
| | | // 在这里考虑用池的话可能走配置好一点 原本的是无论如何都走池 但是实际上有些特效并不需要 |
| | | if (!this.gameObject.activeSelf) |
| | | { |
| | | this.gameObject.SetActive(true); |
| | | } |
| | | |
| | | // 加载spine特效资源 |
| | | if (effectConfig.isSpine != 0) |
| | | { |
| | | spineComp.skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>("UIEffect/" + effectConfig.packageName, effectConfig.fxName + "_SkeletonData"); |
| | | spineComp.raycastTarget = false; |
| | | spineComp.Initialize(true); |
| | | spineAnimationState = spineComp.AnimationState; |
| | | spineAnimationState.Complete -= OnSpineAnimationComplete; |
| | | spineAnimationState.Complete += OnSpineAnimationComplete; |
| | | if (isPlaySpineImmediately) |
| | | { |
| | | //UI特效常用方式 |
| | | spineComp.enabled = true; |
| | | // 播放第一个动画(作为默认动画) |
| | | var skeletonData = spineComp.Skeleton.Data; |
| | | if (skeletonData.Animations.Count > 0) |
| | | { |
| | | string defaultAnimationName = skeletonData.Animations.Items[0].Name; |
| | | spineComp.AnimationState.SetAnimation(0, defaultAnimationName, isPlaySpineLoop); |
| | | } |
| | | else |
| | | { |
| | | Debug.LogError("Spine 数据中没有找到任何动画!" + effectConfig.id); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | spineComp.enabled = false; |
| | | } |
| | | PlaySpineEffect(closePMA); |
| | | } |
| | | else |
| | | { |
| | | PlayerEffect(); |
| | | } |
| | | |
| | | SoundPlayer.Instance.PlayUIAudio(effectConfig.audio); |
| | | |
| | | |
| | | return; |
| | | } |
| | | |
| | | |
| | | // 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>(); |
| | | } |
| | | |
| | | //加载unity特效 |
| | | 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) |
| | | { |
| | |
| | | return; |
| | | } |
| | | |
| | | // 实例化特效 |
| | | effectTarget = Instantiate(effectPrefab, transform); |
| | | 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>(); |
| | |
| | | { |
| | | blocker.SetParentCanvas(canvas); |
| | | } |
| | | |
| | | // 自动销毁 |
| | | if (effectConfig.autoDestroy != 0) |
| | | { |
| | | Destroy(effectTarget, effectConfig.destroyDelay); |
| | | } |
| | | } |
| | | |
| | | 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() |
| | | { |
| | |
| | | onDestroy.Invoke(this); |
| | | onDestroy = null; |
| | | } |
| | | // 停止特效并归还到池中 |
| | | Release(); |
| | | |
| | | if (spineAnimationState != null) |
| | | { |
| | | spineAnimationState.Complete -= OnSpineAnimationComplete; |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | | //单次播放完毕就会触发,即使是循环 |
| | | protected void OnSpineAnimationComplete(Spine.TrackEntry trackEntry) |
| | | protected virtual void OnSpineAnimationComplete(Spine.TrackEntry trackEntry) |
| | | { |
| | | if (!isPlaySpineLoop) |
| | | { |
| | | if (onComplete == null) |
| | | spineComp.enabled = false; |
| | | isPlaying = false; |
| | | onComplete?.Invoke(); |
| | | if (isReleaseImmediately) |
| | | { |
| | | if (effectConfig.isSpine != 0) |
| | | { |
| | | spineComp.enabled = false; |
| | | } |
| | | Release(); |
| | | } |
| | | |
| | | onComplete?.Invoke(); |
| | | } |
| | | } |
| | | |
| | | |
| | | 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) |
| | |
| | | else |
| | | { |
| | | effectPlayer = parent.AddMissingComponent<EffectPlayer>(); |
| | | effectPlayer.effectId = effectId; |
| | | } |
| | | |
| | | effectPlayer.effectId = effectId; |
| | | effectPlayer.SetActive(true); |
| | | return effectPlayer; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 设置游戏对象激活状态 |
| | | // /// </summary> |
| | | // public void SetActive(bool active) |
| | | // { |
| | | // if (gameObject != null) |
| | | // { |
| | | // gameObject.SetActive(active); |
| | | // } |
| | | // } |
| | | |
| | | public void Pause() |
| | | { |
| | |
| | | return; |
| | | } |
| | | } |
| | | |
| | | |
| | | } |