yyl
2026-02-09 5a4e34c8a85737c0fa5b5775122da31155cbaef3
Main/Component/UI/Effect/BattleEffectPlayer.cs
@@ -30,25 +30,64 @@
        }
    }
    private Bone followedBone;
    public bool isBindBone = false;
    public bool isRedCamp = true;
    public EffectConfig effectConfig;
    public float speedRate = 1f;
    public float speedRate = 1.5f;
    private float m_Alpha = 1f;
    public float scaleFactor = 1f;//    长期为1,只在需要跟模型一起缩放的时候更改 值为teamHero.ModelScale
    public float Alpha
    {
        get
        {
            return m_Alpha;
        }
        set
        {
            if (value == m_Alpha)
                return;
            m_Alpha = value;
            OnAlphaChanged();
        }
    }
    private void OnAlphaChanged()
    {
        if (spineComp != null)
        {
            var skeleton = spineComp.Skeleton;
            skeleton.A = Alpha;
            spineComp.LateUpdate();
        }
        for (int i = 0; i < rendererList.Count; i++)
        {
            var renderer = rendererList[i];
            if (renderer != null && renderer.material != null && renderer.material.HasProperty("_Color"))
            {
                Color color = renderer.material.color;
                color.a = Alpha;
                renderer.material.color = color;
            }
        }
    }
    [Header("播放完毕立即回收")]
    public bool isReleaseImmediately = false;  //界面特效一般不需要自我销毁,跟随界面或者父对象销毁就行
    public Action<BattleEffectPlayer> onDestroy;
    [HideInInspector] public Canvas canvas = null;
    [HideInInspector] public GameObject effectTarget = null;
    protected EffectPenetrationBlocker blocker = null;
    protected RendererAdjuster blocker = null;
    protected bool isInit = false;
@@ -65,15 +104,23 @@
    public GameObjectPoolManager.GameObjectPool pool;
    public BattleField battleField;
    public Action onComplete;
    private bool isPlaying = false;
    private float timer = 0f;
    protected virtual void OnEnable()
    {
        if (spineComp != null)
        {
            //隐藏,会有静态显示问题
            spineComp.enabled = false;
        }
        // if (spineComp != null)
        // {
        //     //隐藏,会有静态显示问题
        //     spineComp.enabled = false;
        // }
        ApplySortingOrder();
    }
@@ -115,6 +162,21 @@
        }
        //  有特效可能带spine又带unity特效的情况
        spineComp = gameObject.GetComponentInChildren<SkeletonAnimation>(true);
        if (effectConfig.effectPos != null && effectConfig.effectPos.Length >= 2)
        {
            rectTrans.anchoredPosition += new Vector2((isRedCamp ? 1f : -1f) * effectConfig.effectPos[0], effectConfig.effectPos[1]);
        }
        if (effectConfig.effectScale > 0f)
        {
            rectTrans.localScale *= effectConfig.effectScale;
        }
        spineComp.loop = effectConfig.isLoop != 0;
    }
    protected virtual void Clear()
@@ -148,24 +210,78 @@
        onComplete?.Invoke();
    }
    public Func<bool> funcIsHeroFront;
    public void SetSortingOrder(Func<bool> _isHeroFrontCallback)
    {
        funcIsHeroFront = _isHeroFrontCallback;
        ApplySortingOrder();
    }
    public void ApplySortingOrder()
    {
        if (null != blocker && effectConfig != null && funcIsHeroFront != null)
        {
            bool isEffectFront = effectConfig.frontBack == 1;
            bool isHeroFront = funcIsHeroFront();
            int baseSortingOrder = isHeroFront ? BattleConst.SkillMaskOrder : BattleConst.BattleBackgroundOrder - BattleConst.BattleFrontHeroEffect - 1;
            //  区分是否在特效前面 还要再区分是否在UI前面 isBindBone
            int dynamicOffset = isBindBone
                                ? BattleConst.BattleFrontHeroBehindUIEffect : (isEffectFront ? BattleConst.BattleFrontHeroEffect : BattleConst.BattleBehindHeroEffect);
            int finalSortingOrder = baseSortingOrder + dynamicOffset;
            blocker.SetSortingOrder(finalSortingOrder);
        }
    }
    public void FollowBoneXY()
    {
        if (followedBone == null || !isBindBone)
        {
            return;
        }
        Vector2 vector2 = Vector2.zero;
        if (effectConfig.effectPos != null && effectConfig.effectPos.Length >= 2)
        {
            vector2 = new Vector2((isRedCamp ? 1f : -1f) * effectConfig.effectPos[0], effectConfig.effectPos[1]);
        }
        if (spineComp != null)
        {
            spineComp.transform.localPosition = new Vector3(followedBone.WorldX + vector2.x, followedBone.WorldY + vector2.y, 0);
        }
        if (effectTarget != null)
        {
            effectTarget.transform.localPosition = new Vector3(followedBone.WorldX + vector2.x, followedBone.WorldY + vector2.y, 0);
        }
    }
    public virtual void Play(bool showLog = true)
    {
        if (!isInit)
        {
            InitComponent(showLog);
            isInit = true;
        }
        else
        {
            //避免重复创建
            if (!this.gameObject.activeSelf)
            {
                InitComponent(showLog);
                isInit = true;
                this.gameObject.SetActive(true);
            }
            else
            {
                //避免重复创建
                if (!this.gameObject.activeSelf)
                {
                    this.gameObject.SetActive(true);
                }
                return;
            }
            return;
        }
        if (EffectMgr.IsNotShowBySetting(effectId))
        {
@@ -184,15 +300,34 @@
            this.gameObject.SetActive(true);
        }
        if (effectConfig.autoDestroy != 0)
        {
            GameObject.Destroy(gameObject, effectConfig.destroyDelay);
        }
        isPlaying = true;
        timer = 0f;
        PlayEffect();
    }
    public void Run()
    {
        if (null == effectConfig)
        {
            return;
        }
        if (!isPlaying)
        {
            return;
        }
        if (effectConfig.autoDestroy != 0)
        {
            timer += Time.deltaTime * speedRate;
            if (timer >= effectConfig.destroyDelay)
            {
                GameObject.DestroyImmediate(gameObject);
            }
        }
    }
    protected virtual void PlayEffect()
@@ -204,7 +339,7 @@
        }
        //  如果delay小于等于0 那会立刻执行
        this.DelayTime(effectConfig.delayPlay, () =>
        this.DelayTime(effectConfig.delayPlay / speedRate, () =>
        {
            PlayEffectInternal();
        });
@@ -220,48 +355,68 @@
        {
            PlayUnityEffect();
        }
        battleField?.soundManager.PlayEffectSound(effectConfig.audio);
        OnAlphaChanged();
    }
    protected void PlaySpineEffect()
    {
        //  这里是纯spine的逻辑
        if (spineComp == null)
        {
            Debug.LogError("BattleEffectPlayer spineComp is null, effect id is " + effectId);
            return;
        }
        SkeletonDataAsset skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>("UIEffect/" + effectConfig.packageName, effectConfig.fxName);
        // ===== 简化:直接从缓存获取,缓存内部会自动加载 =====
        string directory = "UIEffect/" + effectConfig.packageName;
        SkeletonDataAsset skeletonDataAsset = BattleResManager.Instance.GetSpineResource(
            directory,
            effectConfig.fxName,
            battleField?.guid
        );
        if (skeletonDataAsset == null)
        {
            Debug.LogError($"BattleEffectPlayer: Failed to load effect spine {effectConfig.fxName}");
            return;
        }
        // ================================
        spineComp.skeletonDataAsset = skeletonDataAsset;
        spineComp.Initialize(true);
        spineComp.timeScale = speedRate;
        spineAnimationState = spineComp.AnimationState;
        if (scaleX != 0f && spineComp != null && spineComp.skeleton != null)
        {
            spineComp.skeleton.ScaleX = scaleX;
        }
        spineComp.skeleton.A = Alpha;
        spineAnimationState = spineComp.state;
        spineAnimationState.Complete -= OnSpineAnimationComplete;
        spineAnimationState.Complete += OnSpineAnimationComplete;
        if (null == canvas)
            canvas = GetComponentInParent<Canvas>();
        // 添加特效穿透阻挡器
        blocker = spineComp.AddMissingComponent<EffectPenetrationBlocker>();
        blocker = spineComp.AddMissingComponent<RendererAdjuster>();
        blocker.onSortingChanged = OnSortingChanged;
        //  如果没有canvas的话 正常是因为不在BattleWin下面的节点 意思就是当前没有显示 等到切回战斗的时候再通过BattleField.UpdateCanvas来更新
        if (canvas != null)
        {
            blocker.SetParentCanvas(canvas);
        }
        ApplySortingOrder();
        spineComp.enabled = true;
        spineComp.timeScale = speedRate;
        spineComp.LateUpdate();
        Spine.Animation animation = spineAnimationState.Data.SkeletonData.Animations.First();
        spineAnimationState.SetAnimation(0, animation, effectConfig.isLoop != 0);
        SoundPlayer.Instance.PlayUIAudio(effectConfig.audio);
        // SoundPlayer.Instance.PlayUIAudio(effectConfig.audio);
    }
@@ -295,7 +450,7 @@
        }
        // 从特效预制体池获取特效
        pool = GameObjectPoolManager.Instance.RequestPool(effectPrefab);
        pool = GameObjectPoolManager.Instance.GetPool(effectPrefab);
        effectTarget = pool.Request();
        // 设置父节点和位置
        effectTarget.transform.SetParent(transform);
@@ -311,21 +466,13 @@
        OnUnityAnimationComplete();
        if (null == canvas)
            canvas = GetComponentInParent<Canvas>();
        // 添加特效穿透阻挡器
        blocker = effectTarget.AddMissingComponent<EffectPenetrationBlocker>();
        blocker = effectTarget.AddMissingComponent<RendererAdjuster>();
        blocker.onSortingChanged = OnSortingChanged;
        //  如果没有canvas的话 正常是因为不在BattleWin下面的节点 意思就是当前没有显示 等到切回战斗的时候再通过BattleField.UpdateCanvas来更新
        if (canvas != null)
        {
            blocker.SetParentCanvas(canvas);
        }
        SoundPlayer.Instance.PlayUIAudio(effectConfig.audio);
        // SoundPlayer.Instance.PlayUIAudio(effectConfig.audio);
    }
@@ -341,9 +488,13 @@
    public string sortingLayer;
    public int sortingOrder;
    public RectTransform rectTrans;
    protected void OnSortingChanged(string _sortingLayer, int _sortingOrder)
    {
        if (null == spineComp)
            return;
        sortingLayer = _sortingLayer;
        sortingOrder = _sortingOrder;
        // 处理排序变化
@@ -402,17 +553,24 @@
    //  创建后的特效会自动隐藏 需要手动调用Play才能播放
    public static BattleEffectPlayer Create(int effectId, Transform parent)
    public static BattleEffectPlayer Create(int effectId, BattleField _battleField, Transform parent, bool isRedCamp, float _scaleRatio)
    {
        // 直接创建特效播放器,不使用对象池
        BattleEffectPlayer battleEffectPlayer = null;
        GameObject newGo = new GameObject("BattleEffectPlayer_" + effectId);
        newGo.transform.SetParent(parent, false);
        newGo.AddMissingComponent<RectTransform>();
        battleEffectPlayer = newGo.AddComponent<BattleEffectPlayer>();
        battleEffectPlayer.battleField = _battleField;
        battleEffectPlayer.rectTrans = newGo.AddMissingComponent<RectTransform>();
        
        battleEffectPlayer.effectId = effectId;
        battleEffectPlayer.scaleFactor = _scaleRatio;
        // 设置阵营
        battleEffectPlayer.isRedCamp = isRedCamp;
        battleEffectPlayer.SetActive(true);
        return battleEffectPlayer;
    }
@@ -430,7 +588,7 @@
    public void Pause()
    {
        if (effectTarget == null) return;
        // if (effectTarget == null) return;
        // Spine动画
        // var spineGraphics = effectTarget.GetComponentsInChildren<SkeletonGraphic>(true);
@@ -455,7 +613,7 @@
    public void Resume()
    {
        if (effectTarget == null) return;
        // if (effectTarget == null) return;
        if (spineComp != null)
        {
@@ -509,21 +667,70 @@
        return true;
    }
    /// <summary>
    /// 设置遮罩(支持RectMask2D、Mask、SmoothMask等)
    /// </summary>
    public void SetMask(RectTransform maskArea = null)
    public void SetSpeedRatio(float ratio)
    {
        if (effectTarget == null || blocker == null)
            return;
        // 优先使用传入的maskArea
        if (maskArea != null)
        speedRate = ratio;
        if (spineComp != null)
        {
            blocker.PerformMask(maskArea);
            return;
            spineComp.timeScale = speedRate;
        }
        // Animator动画
        foreach (var animator in animatorList)
        {
            animator.speed = speedRate;
        }
    }
    public void BindBone(BattleObject battleObject, string v)
    {
        var skeletonAnim = battleObject?.GetSkeletonAnimation();
        if (skeletonAnim == null)
        {
            return;
        }
        Bone bone = skeletonAnim.skeleton.FindBone(v);
        isBindBone = false;
        followedBone = null;
        if (null == bone)
        {
             return;
        }
        rectTrans.localScale *= scaleFactor;
        isBindBone = true;
        followedBone = bone;
        ApplySortingOrder();
        BoneFollower boneFollower = gameObject.AddMissingComponent<BoneFollower>();
        boneFollower.boneName = v;
        boneFollower.skeletonRenderer = skeletonAnim;
        boneFollower.followBoneRotation = false;
        boneFollower.followXYPosition = true;
        boneFollower.followZPosition = false;
        boneFollower.followLocalScale = false;
        boneFollower.followParentWorldScale = false;
        boneFollower.followSkeletonFlip = false;
        boneFollower.Initialize();
        boneFollower.LateUpdate();
    }
    private float scaleX = 0f;
    public void SetScaleX(float v)
    {
        if (spineComp != null && spineComp.skeleton != null)
        {
            spineComp.skeleton.ScaleX = v;
        }
        else
        {
            scaleX = v;
        }
    }
}