yyl
8 天以前 b5098dca7b3454da208d60d4944d132ca660bb77
125 战斗 子技能支持
5个文件已修改
4个文件已添加
1989 ■■■■ 已修改文件
Main/Core/NetworkPackage/CustomServerPack/CustomHB426CombinePack.cs 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/CustomServerPack/CustomHB427_tagSCUseSkill.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/CustomServerPack/CustomHB427_tagSCUseSkill.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Motion/MotionBase.cs 477 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Skill/SkillBase.cs 1308 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Skill/SkillEffectType.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/NoEffect.cs 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/NoEffect.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/SkillEffectFactory.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/CustomServerPack/CustomHB426CombinePack.cs
@@ -99,6 +99,8 @@
        Dictionary<int, GameNetPackBasic> indexDict = new Dictionary<int, GameNetPackBasic>();
        Dictionary<uint, HB427_tagSCUseSkill> skillDict = new Dictionary<uint, HB427_tagSCUseSkill>();
        for (int i = 0; i < b421SeriesPackList.Count; i++)
        {
            var pack = b421SeriesPackList[i];
@@ -134,7 +136,41 @@
            }
            else
            {
                indexDict.Add(i, pack);
                if (pack is HB427_tagSCUseSkill skillPack)
                {
                    //  处理技能之间的链接关系
                    if (skillPack.RelatedSkillID > 0)
                    {
                        skillDict.TryGetValue(skillPack.RelatedSkillID, out var parentSkill);
                        if (parentSkill != null && skillPack.BattleType == 4)//4=子技能
                        {
                            parentSkill.subSkillList.Add(skillPack);
                            skillPack.parentSkill = parentSkill;
                        }
                        else
                        {
                            indexDict.Add(i, pack);
                        }
                    }
                    else
                    {
                        indexDict.Add(i, pack);
                    }
                    if (skillDict.ContainsKey(skillPack.SkillID))
                    {
                        skillDict[skillPack.SkillID] = skillPack;
                    }
                    else
                    {
                        skillDict.Add(skillPack.SkillID, skillPack);
                    }
                }
                else
                {
                    indexDict.Add(i, pack);
                }
            }
        }
@@ -203,6 +239,53 @@
    }
    public static SkillRecordAction CreateSkillAction(string guid, List<GameNetPackBasic> _packList)
    {
        BattleField battleField = BattleManager.Instance.GetBattleField(guid);
        if (null == battleField)
        {
            Debug.LogError("BattleField not found for guid: " + guid);
            return null;
        }
        while (_packList.Count > 0)
        {
            var pack = _packList[0];
            _packList.RemoveAt(0);
            if (pack is HB427_tagSCUseSkill)
            {
                _packList.Insert(0, pack);
                break;
            }
            else if (pack is CustomHB426CombinePack)
            {
                Debug.LogError("无法找到Skill包,先发现了嵌套包");
                return null;
            }
            else
            {
                // Debug.LogError("发现非Skill包,先分发掉: " + pack.GetType().Name);
                PackageRegedit.Distribute(pack);
            }
        }
        HB427_tagSCUseSkill skill = _packList[0] as HB427_tagSCUseSkill;
        _packList.RemoveAt(0);
        if (null == skill)
        {
            Debug.LogError("No HB427_tagSCUseSkill found in packList.");
            return null;
        }
        BattleObject _caster = battleField.battleObjMgr.GetBattleObject((int)skill.ObjID);
        SkillRecordAction skillAction = new SkillRecordAction(battleField, _caster, skill, _packList);
        return skillAction;
    }
    public SkillRecordAction CreateSkillAction()
    {
        BattleField battleField = BattleManager.Instance.GetBattleField(guid);
Main/Core/NetworkPackage/CustomServerPack/CustomHB427_tagSCUseSkill.cs
New file
@@ -0,0 +1,24 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public partial class HB427_tagSCUseSkill : GameNetPackBasic
{
    //-------------------------------------------//
    //  裸露在外的技能不考虑子技能 跟 parentSkill ( CustomHB426CombinePack )
    //-------------------------------------------//
    public HashSet<HB427_tagSCUseSkill> subSkillList
    {
        get;
        set;
    } = new HashSet<HB427_tagSCUseSkill>();
    public HB427_tagSCUseSkill parentSkill
    {
        get;
        set;
    } = null;
}
Main/Core/NetworkPackage/CustomServerPack/CustomHB427_tagSCUseSkill.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b83c506966c4d7645944535075a7c96f
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/Battle/Motion/MotionBase.cs
@@ -1,17 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Spine.Unity;
using Cysharp.Threading.Tasks;
/// <summary>
/// 角色动画基类,处理所有与动画相关的功能
/// </summary>
public class MotionBase
{
    public static float MotionTimeScale = 1f;
    public static List<string> AttackMotionList = new List<string>
    {
        MotionName.attack.ToString().ToLower(),
@@ -19,429 +13,332 @@
        MotionName.passiveSkill.ToString().ToLower(),
    };
    private Dictionary<Spine.TrackEntry, Action> trackEntryCompleteDict = new Dictionary<Spine.TrackEntry, Action>();
    // 动画事件
    private Dictionary<Spine.TrackEntry, Action> trackEntryCallbacks = new Dictionary<Spine.TrackEntry, Action>();
    public Action OnAttackAnimationComplete;
    public Action OnHitAnimationComplete;
    private List<Action> runActionList = new List<Action>();
    private List<Action> runningActions = new List<Action>();
    #region 组件引用
    protected SkeletonAnimation skeletonAnimation;
    protected Spine.AnimationState spineAnimationState;
    protected SkeletonAnimation skeletonAnim;
    protected Spine.AnimationState animState;
    protected Spine.Skeleton skeleton;
    #endregion
    #region 动画设置
    // 动画混合时间
    protected float defaultMixDuration = 0f;
    #endregion
    private Spine.TrackEntry currentTrackEntry;
    //  残影生成器
    private Spine.TrackEntry currentTrack;
    private SkeletonIllusionShadow illusionShadow;
    private bool playingSkillAnim = false;
    #region 初始化方法
    /// <summary>
    /// 初始化动画组件
    /// </summary>
    /// <param name="skeletonGraphic">骨骼动画组件</param>
    public virtual void Init(SkeletonAnimation _skeletonAnimation)
    public virtual void Init(SkeletonAnimation skelAnim)
    {
        this.skeletonAnimation = _skeletonAnimation;
        if (skeletonAnimation != null)
        {
            spineAnimationState = skeletonAnimation.AnimationState;
            spineAnimationState.TimeScale = MotionTimeScale;
            skeletonAnimation.timeScale = MotionTimeScale;
            skeleton = skeletonAnimation.Skeleton;
            // 设置动画混合时间
            if (spineAnimationState != null)
            {
                spineAnimationState.Data.DefaultMix = defaultMixDuration;
            }
            // 播放默认动画
            PlayAnimation(MotionName.idle, true);
            // 设置动画事件监听
            SetupAnimationHandlers();
        }
        else
        skeletonAnim = skelAnim;
        if (skeletonAnim == null)
        {
            BattleDebug.LogError("缺少SkeletonGraphic组件!");
            return;
        }
        illusionShadow = _skeletonAnimation.gameObject.AddMissingComponent<SkeletonIllusionShadow>();
        animState = skeletonAnim.AnimationState;
        animState.TimeScale = MotionTimeScale;
        skeletonAnim.timeScale = MotionTimeScale;
        skeleton = skeletonAnim.Skeleton;
        if (animState != null)
            animState.Data.DefaultMix = defaultMixDuration;
        PlayAnimation(MotionName.idle, true);
        SetupAnimationHandlers();
        if (skelAnim.gameObject != null)
            illusionShadow = skelAnim.gameObject.AddMissingComponent<SkeletonIllusionShadow>();
    }
    
    public virtual void Release()
    {
        trackEntryCompleteDict.Clear();
        if (spineAnimationState != null)
        trackEntryCallbacks.Clear();
        if (animState != null)
        {
            spineAnimationState.Complete -= OnAnimationComplete;
            spineAnimationState.ClearTracks();
            spineAnimationState = null;
            animState.Complete -= OnAnimationComplete;
            animState.ClearTracks();
            animState = null;
        }
        skeletonAnimation = null;
        skeletonAnim = null;
        skeleton = null;
        currentTrackEntry = null;
        currentTrack = null;
        playingSkillAnim = false;
    }
    #endregion
    #region 动画控制
    /// <summary>
    /// 播放指定动画
    /// </summary>
    /// <param name="motionName">动画枚举</param>
    /// <param name="loop">是否循环</param>
    /// <param name="_onComplete">动画播放完成回调</param>
    /// <returns>动画轨道条目</returns>
    public virtual Spine.TrackEntry PlayAnimation(MotionName motionName, bool loop, Action _onComplete = null)
    public virtual Spine.TrackEntry PlayAnimation(MotionName motionName, bool loop, Action onComplete = null)
    {
        if (isPlaySkillAnimation)
        if (playingSkillAnim || animState == null) return null;
        if (currentTrack != null && !currentTrack.IsComplete && trackEntryCallbacks.TryGetValue(currentTrack, out var prevCallback))
        {
            return null;
            trackEntryCallbacks.Remove(currentTrack);
            prevCallback?.Invoke();
            currentTrack = null;
        }
        if (spineAnimationState == null) return null;
        currentTrack = animState.SetAnimation(0, motionName.ToString(), loop);
        if (onComplete != null && currentTrack != null)
            trackEntryCallbacks[currentTrack] = onComplete;
        // 如果当前动画未完成
        if (currentTrackEntry != null && !currentTrackEntry.IsComplete)
        {
            if (trackEntryCompleteDict.TryGetValue(currentTrackEntry, out var __onComplete))
            {
                trackEntryCompleteDict.Remove(currentTrackEntry);
                __onComplete?.Invoke();
            }
            currentTrackEntry = null;
        }
        // 直接使用 ToString() 而不是调用 GetAnimationName
        currentTrackEntry = spineAnimationState.SetAnimation(0, motionName.ToString(), loop);
        // 绑定回调
        if (_onComplete != null && currentTrackEntry != null)
        {
            trackEntryCompleteDict[currentTrackEntry] = _onComplete;
        }
        return currentTrackEntry;
        return currentTrack;
    }
    private void RunAction(Action _action)
    {
        _action?.Invoke();
        runActionList.Add(_action);
    }
    private void AddAction(Action action) => runningActions.Add(action);
    private void RemoveAction(Action action) => runningActions.Remove(action);
    private void RemoveRunAction(Action _action)
    public Spine.TrackEntry PlaySkillAnimation(SkillConfig skillConfig, SkillBase skillBase, bool isSubSkill, Action onComplete = null)
    {
        runActionList.Remove(_action);
    }
    private bool isPlaySkillAnimation = false;
    public Spine.TrackEntry PlaySkillAnimation(SkillConfig skillConfig, SkillBase skillBase, Action _onComplete = null)
    {
        // 参数校验
        if (skillConfig == null)
        {
            Debug.LogError("技能配置为空,无法播放技能动画");
            return null;
        }
        if (spineAnimationState == null || skeleton == null)
        if (animState == null || skeleton == null)
        {
            Debug.LogError("SkeletonGraphic或AnimationState未初始化,无法播放技能动画");
            return null;
        }
        Spine.Animation anim = skeleton.Data.FindAnimation(skillConfig.SkillMotionName);
        if (null == anim)
        if (string.IsNullOrEmpty(skillConfig.SkillMotionName))
        {
            for (int i = 0; i < skeleton.Data.Animations.Count; i++)
            {
                var skeletonAnim = skeleton.Data.Animations.Items[i];
                if (skeletonAnim.Name.ToLower() == skillConfig.SkillMotionName.ToLower())
                {
                    anim = skeletonAnim;
                    // 找到动画
                    break;
                }
            }
        }
        // 获取动画
        if (anim == null)
        {
            Debug.LogError($"找不到动画: {skillConfig.SkillMotionName}");
            skillBase.ForceFinished();
            // _onComplete?.Invoke();
            PlaySkillNoAnim(skillConfig, skillBase, onComplete, isSubSkill);
            return null;
        }
        // 关键帧参数
        Spine.Animation targetAnim = FindAnim(skillConfig.SkillMotionName);
        if (targetAnim == null)
        {
            skillBase.ForceFinished();
            return null;
        }
        return ExecuteSkillAnim(skillConfig, skillBase, onComplete, targetAnim, true, isSubSkill);
    }
    private Spine.TrackEntry ExecuteSkillAnim(SkillConfig skillConfig, SkillBase skillBase, Action onComplete,
        Spine.Animation targetAnim, bool hasAnim, bool isSubSkill)
    {
        int loopCount = skillConfig.LoopCount;
        int[] activeFrames = skillConfig.ActiveFrames;
        int activeFrameCount = activeFrames.Length;
        int[] activeFrames = skillConfig.ActiveFrames ?? new int[0];
        int frameCount = activeFrames.Length;
        float recoveryFrame = skillConfig.RecoveryFrames;
        // 播放动画
        var skillTrackEntry = spineAnimationState.SetAnimation(0, anim, false);
        isPlaySkillAnimation = true;
        currentTrackEntry = skillTrackEntry;
        Spine.TrackEntry skillTrack = null;
        if (hasAnim)
        {
            skillTrack = animState.SetAnimation(0, targetAnim, false);
            currentTrack = skillTrack;
        }
        playingSkillAnim = true;
        // 事件状态
        int curLoop = 0;
        bool beginPhaseTriggered = false;
        bool finalFrameStarted = false;
        bool finalFrameEnded = false;
        bool middleFrameStarted = false;
        bool[] triggeredActiveFrame = new bool[activeFrameCount];
        int currentLoop = 0, triggerCount = 0, failCount = 0;
        bool beginTriggered = false, finalStarted = false, finalEnded = false, middleStarted = false;
        bool[] triggeredFrames = new bool[frameCount];
        float startTime = hasAnim ? 0 : Time.time;
        // 技能开始
        skillBase.OnSkillStart();
        // 动画帧更新处理
        int triggerMFEndCount = 0;
        Action updateLocalHandler = null;
        int failCallbackTimes = 0;
        updateLocalHandler = () =>
        Action frameHandler = null;
        frameHandler = () =>
        {
            if (skillBase.IsFinished())
            {
                isPlaySkillAnimation = false;
                RemoveRunAction(updateLocalHandler);
                playingSkillAnim = false;
                RemoveAction(frameHandler);
                return;
            }
            float frame = (skillTrackEntry.TrackTime * skillTrackEntry.TimeScale * (float)BattleConst.skillMotionFps);
            float currentFrame = 0f;
            if (BattleConst.skillMotionFps > 0)
                currentFrame = hasAnim ? (skillTrack.TrackTime * skillTrack.TimeScale * BattleConst.skillMotionFps) : ((Time.time - startTime) * MotionTimeScale * BattleConst.skillMotionFps);
            if (currentTrackEntry != skillTrackEntry)
            if (hasAnim)
            {
                Debug.LogError("技能动画被打断,强制结束 " + skillConfig.SkillID + " last animation : " + (currentTrackEntry != null && currentTrackEntry.Animation != null ? currentTrackEntry.Animation.Name : "null"));
                if (currentTrack != skillTrack)
                    Debug.LogError("技能动画被打断,强制结束 " + skillConfig.SkillID);
                if (skillTrack.TrackTime == 0) failCount++;
                if (failCount > 100)
                {
                    Debug.LogError("技能动画播放失败,强制结束 " + skillConfig.SkillID);
                    skillBase.ForceFinished();
                    RemoveAction(frameHandler);
                    playingSkillAnim = false;
                    return;
                }
            }
            if (skillTrackEntry.TrackTime == 0)
            if (!beginTriggered && currentFrame >= skillConfig.StartupFrames && currentLoop == 0)
            {
                failCallbackTimes++;
            }
            if (failCallbackTimes > 100)
            {
                Debug.LogError("技能动画播放失败,回调异常,强制结束 " + skillConfig.SkillID + " 导致错误的原因是技能帧配置得太久导致技能动作结束了事件还没结束");
                skillBase.ForceFinished();
                RemoveRunAction(updateLocalHandler);
                isPlaySkillAnimation = true;
                return;
            }
            // 前摇结束(只触发一次)
            if (!beginPhaseTriggered && frame >= skillConfig.StartupFrames && curLoop == 0)
            {
                beginPhaseTriggered = true;
                beginTriggered = true;
                skillBase.OnStartSkillFrameEnd();
            }
            // 中摇开始(每轮loop的开始,只触发一次)
            if (!middleFrameStarted && frame >= skillConfig.StartupFrames && curLoop <= loopCount)
            if (!middleStarted && currentFrame >= skillConfig.StartupFrames && currentLoop <= loopCount)
            {
                middleFrameStarted = true;
                skillBase.OnMiddleFrameStart(curLoop);
                middleStarted = true;
                skillBase.OnMiddleFrameStart(currentLoop);
            }
            // 多段攻击帧触发
            for (int hitIndex = 0; hitIndex < activeFrameCount; hitIndex++)
            for (int i = 0; i < frameCount && i < triggeredFrames.Length; i++)
            {
                float activeFrame = activeFrames[hitIndex];
                if (!triggeredActiveFrame[hitIndex] && frame >= activeFrame)
                if (!triggeredFrames[i] && currentFrame >= activeFrames[i])
                {
                    skillBase.OnMiddleFrameEnd(curLoop, triggerMFEndCount++);
                    triggeredActiveFrame[hitIndex] = true;
                    skillBase.OnMiddleFrameEnd(currentLoop, triggerCount++);
                    triggeredFrames[i] = true;
                }
            }
            // 判断是否所有activeFrame都已触发,准备进入下一轮loop
            bool allTriggered = true;
            for (int i = 0; i < activeFrameCount; i++)
            {
                if (!triggeredActiveFrame[i])
                {
                    allTriggered = false;
                    break;
                }
            }
            bool allTriggered = Array.TrueForAll(triggeredFrames, x => x);
            // 进入下一轮loop
            if (allTriggered && curLoop < loopCount)
            if (allTriggered && currentLoop < loopCount)
            {
                curLoop++;
                Array.Clear(triggeredActiveFrame, 0, activeFrameCount);
                middleFrameStarted = false;
                currentLoop++;
                Array.Clear(triggeredFrames, 0, frameCount);
                middleStarted = false;
                if (curLoop < loopCount)
                if (currentLoop < loopCount)
                {
                    // 重新设置到第一次的中摇时间
                    skillTrackEntry.TrackTime = skillConfig.StartupFrames / BattleConst.skillMotionFps;
                    beginPhaseTriggered = false;
                    if (BattleConst.skillMotionFps > 0)
                    {
                        if (hasAnim)
                            skillTrack.TrackTime = skillConfig.StartupFrames / BattleConst.skillMotionFps;
                        else
                            startTime = Time.time - (skillConfig.StartupFrames / BattleConst.skillMotionFps);
                    }
                    beginTriggered = false;
                }
                else
                {
                    finalFrameStarted = false;
                    finalFrameEnded = false;
                    // 收尾阶段由后续逻辑处理
                    finalStarted = false;
                    finalEnded = false;
                }
            }
            // 收尾阶段:OnFinalFrameStart 和 OnFinalFrameEnd
            if (curLoop >= loopCount)
            if (currentLoop >= loopCount)
            {
                if (!finalFrameStarted && frame >= recoveryFrame)
                if (!finalStarted && currentFrame >= recoveryFrame)
                {
                    finalFrameStarted = true;
                    finalStarted = true;
                    skillBase.OnFinalFrameStart();
                }
                if (finalFrameStarted && !finalFrameEnded && frame >= recoveryFrame)
                if (finalStarted && !finalEnded && currentFrame >= recoveryFrame)
                {
                    finalFrameEnded = true;
                    isPlaySkillAnimation = false;
                    RemoveRunAction(updateLocalHandler);
                    _onComplete?.Invoke();
                    finalEnded = true;
                    if (!isSubSkill)
                    {
                        playingSkillAnim = false;
                    }
                    RemoveAction(frameHandler);
                    onComplete?.Invoke();
                    skillBase.OnFinalFrameEnd();
                }
            }
        };
        RunAction(updateLocalHandler);
        AddAction(frameHandler);
        return skillTrack;
    }
    private Spine.Animation FindAnim(string animName)
    {
        if (string.IsNullOrEmpty(animName)) return null;
        return skillTrackEntry;
        Spine.Animation targetAnim = skeleton.Data.FindAnimation(animName);
        if (targetAnim == null && skeleton.Data.Animations != null)
        {
            for (int i = 0; i < skeleton.Data.Animations.Count; i++)
            {
                var anim = skeleton.Data.Animations.Items[i];
                if (anim?.Name != null && anim.Name.ToLower() == animName.ToLower())
                {
                    targetAnim = anim;
                    break;
                }
            }
        }
        if (targetAnim == null)
            Debug.LogError($"找不到动画: {animName}");
        return targetAnim;
    }
    /// <summary>
    /// 设置动画事件监听
    /// </summary>
    private void PlaySkillNoAnim(SkillConfig skillConfig, SkillBase skillBase, Action onComplete, bool isSubSkill) =>
        ExecuteSkillAnim(skillConfig, skillBase, onComplete, null, false, isSubSkill);
    protected virtual void SetupAnimationHandlers()
    {
        if (spineAnimationState == null) return;
        // 监听动画完成事件
        spineAnimationState.Complete += OnAnimationComplete;
        if (animState != null)
            animState.Complete += OnAnimationComplete;
    }
    /// <summary>
    /// 动画完成事件处理
    /// </summary>
    protected virtual void OnAnimationComplete(Spine.TrackEntry trackEntry)
    {
        string animation = trackEntry.Animation.Name.ToLower();
        if (trackEntry?.Animation?.Name == null) return;
        string animName = trackEntry.Animation.Name.ToLower();
        // 攻击动画完成后恢复到待机状态
        if (AttackMotionList.Contains(animation))
        if (AttackMotionList.Contains(animName))
        {
            OnAttackAnimationComplete?.Invoke();
            PlayAnimation(MotionName.idle, true);
        }
        // 受伤动画完成后恢复到待机状态 可能触发多次 因为有多段攻击的存在
        else if (animation == MotionName.hit.ToString().ToLower())
        else if (animName == MotionName.hit.ToString().ToLower())
        {
            OnHitAnimationComplete?.Invoke();
            PlayAnimation(MotionName.idle, true);
        }
        
        // 只调用本次TrackEntry的回调
        if (trackEntryCompleteDict.TryGetValue(trackEntry, out var cb))
        if (trackEntryCallbacks.TryGetValue(trackEntry, out var callback))
        {
            trackEntryCompleteDict.Remove(trackEntry);
            cb?.Invoke();
            trackEntryCallbacks.Remove(trackEntry);
            callback?.Invoke();
        }
    }
    public virtual void Run()
    {
        // #if UNITY_EDITOR
        //         List<int> removeIndex = new List<int>();
        // #endif
        for (int i = runActionList.Count - 1; i >= 0; i--)
        {
            // #if UNITY_EDITOR
            //             try
            //             {
            // #endif
            runActionList[i]?.Invoke();
            // #if UNITY_EDITOR
            //             }
            //             catch (System.Exception ex)
            //             {
            //                 removeIndex.Add(i);
            //                 BattleDebug.LogError($"执行RunAction时发生异常: {ex.Message}\n{ex.StackTrace}");
            //             }
            // #endif
        }
        for (int i = runningActions.Count - 1; i >= 0; i--)
            runningActions[i]?.Invoke();
        // #if UNITY_EDITOR
        //         // 移除失败的Action
        //         for (int i = 0; i < removeIndex.Count; i++)
        //         {
        //             runActionList.RemoveAt(removeIndex[i]);
        //         }
        // #endif
        illusionShadow.Run();
        illusionShadow?.Run();
    }
    public virtual void Pause()
    {
        spineAnimationState.TimeScale = 0f;
        skeletonAnimation.timeScale = 0f;
        if (animState != null) animState.TimeScale = 0f;
        if (skeletonAnim != null) skeletonAnim.timeScale = 0f;
    }
    public virtual void Resume()
    {
        spineAnimationState.TimeScale = MotionTimeScale;
        skeletonAnimation.timeScale = MotionTimeScale;
        if (animState != null) animState.TimeScale = MotionTimeScale;
        if (skeletonAnim != null) skeletonAnim.timeScale = MotionTimeScale;
    }
    public void HaveRest()
    {
        trackEntryCompleteDict.Clear();
        runActionList.Clear();
        trackEntryCallbacks.Clear();
        runningActions.Clear();
        playingSkillAnim = false;
        PlayAnimation(MotionName.idle, true);
    }
    public void SetSpeedRatio(float ratio)
    {
        MotionTimeScale = ratio;
        spineAnimationState.TimeScale = ratio;
        skeletonAnimation.timeScale = ratio;
        if (animState != null) animState.TimeScale = ratio;
        if (skeletonAnim != null) skeletonAnim.timeScale = ratio;
    }
    public void ShowIllusionShadow(bool v)
    public void ShowIllusionShadow(bool isVisible)
    {
        illusionShadow.SetSkeletonAnimation(skeletonAnimation);
        illusionShadow.Show(v);
        if (illusionShadow != null)
        {
            illusionShadow.SetSkeletonAnimation(skeletonAnim);
            illusionShadow.Show(isVisible);
        }
    }
    #endregion
}
Main/System/Battle/Skill/SkillBase.cs
@@ -5,779 +5,699 @@
using System.Linq;
using System;
public class SkillBase
{
    const float moveTime = 0.5f;
    const float moveTime = 0.5f;
    protected SkillEffect skillEffect;
    protected HB427_tagSCUseSkill tagUseSkillAttack;
    public SkillConfig skillConfig;
    protected bool isFinished = false;
    protected BattleField battleField = null; // 战场
    protected RectTransform targetNode = null; // 目标节点
    protected BattleObject caster = null; // 施法者
    protected List<GameNetPackBasic> packList;
    protected SkillRecordAction otherSkillAction;
    protected List<H0704_tagRolePackRefresh> dropPackList = new List<H0704_tagRolePackRefresh>();
    protected List<HB405_tagMCAddExp> expPackList = new List<HB405_tagMCAddExp>();
    protected bool moveFinished = false;
    protected SkillEffect skillEffect;
    protected HB427_tagSCUseSkill tagUseSkillAttack;
    public SkillConfig skillConfig;
    protected bool isFinished = false;
    protected BattleField battleField = null; // 战场
    protected RectTransform targetNode = null; // 目标节点
    protected BattleObject caster = null; // 施法者
    protected List<GameNetPackBasic> packList;
    protected List<SkillRecordAction> otherSkillActionList = new List<SkillRecordAction>();
    protected List<H0704_tagRolePackRefresh> dropPackList = new List<H0704_tagRolePackRefresh>();
    protected List<HB405_tagMCAddExp> expPackList = new List<HB405_tagMCAddExp>();
    protected bool moveFinished = false;
    public int fromSkillId;
    public bool isPlay = false;
    public bool isPlay = false;
    private Dictionary<int, BattleDrops> tempDropList = new Dictionary<int, BattleDrops>();
    private Dictionary<int, HB422_tagMCTurnFightObjDead> tempDeadPackList = new Dictionary<int, HB422_tagMCTurnFightObjDead>();
    // 构造函数:初始化技能基础数据
    public SkillBase(BattleObject _caster, SkillConfig _skillCfg, HB427_tagSCUseSkill vNetData, List<GameNetPackBasic> _packList, BattleField _battleField = null)
    {
        caster = _caster;
        if (null == caster)
        {
            throw new Exception("SkillBase caster is null ");
        }
        skillConfig = _skillCfg;
        tagUseSkillAttack = vNetData;
        battleField = _battleField;
        packList = _packList;
    {
        caster = _caster;
        if (null == caster)
        {
            throw new Exception("SkillBase caster is null ");
        }
        skillConfig = _skillCfg;
        tagUseSkillAttack = vNetData;
        battleField = _battleField;
        packList = _packList;
    }
        // Debug.LogError("start a skill id " + skillConfig.SkillID + " caster " + caster.teamHero.heroId + " pos " + caster.teamHero.positionNum + " camp " + caster.Camp);
    }
    // 技能运行主逻辑:处理技能效果和其他技能动作
    public virtual void Run()
    {
        if (skillEffect != null)
        {
            if (skillEffect.IsFinished())
            {
                skillEffect = null;
                OnSkillFinished();
            }
            else
            {
                skillEffect.Run();
            }
            return;
        }
    public virtual void Run()
    {
        if (null != skillEffect)
        {
            if (skillEffect.IsFinished())
            {
                skillEffect = null;
                OnSkillFinished();
            }
            else
            {
                skillEffect.Run();
            }
            return;
        }
        if (otherSkillActionList.Count > 0)
        {
            for (int i = otherSkillActionList.Count - 1; i >= 0; i--)
            {
                var action = otherSkillActionList[i];
                if (action.IsFinished())
                {
                    otherSkillActionList.RemoveAt(i);
                    OnSkillFinished();
                }
                else if (moveFinished)
                {
                    action.Run();
                }
            }
        }
    }
        if (otherSkillAction != null)
        {
            if (otherSkillAction.IsFinished())
            {
                otherSkillAction = null;
                OnSkillFinished();
            }
            else
            {
                if (moveFinished)
                {
                    otherSkillAction.Run();
                }
            }
            return;
        }
    }
    // 技能释放主逻辑:广播事件、高亮目标、执行释放
    public virtual void Cast()
    {
        // 广播技能释放事件
        string guid = battleField.guid;
        TeamHero teamHero = caster.teamHero;
        EventBroadcast.Instance.Broadcast<string, SkillConfig, TeamHero>(EventName.BATTLE_CAST_SKILL, guid, skillConfig, teamHero);
    // 0·移动到距离目标n码,的距离释放(可配置,9999即原地释放,负数则是移动到人物背面,人物要转身)
    // 1·移动到距离阵容位置n码的距离(如2号位,5号位)释放(即战场中央此类)
    public virtual void Cast()
    {
        // Debug.LogError("Cast skill " + skillConfig.SkillID + " cast position " + skillConfig.CastPosition + " cast mode " + skillConfig.castMode);
        string guid = battleField.guid;
        TeamHero teamHero = caster.teamHero;
        EventBroadcast.Instance.Broadcast<string, SkillConfig, TeamHero>(EventName.BATTLE_CAST_SKILL, guid, skillConfig, teamHero);
        // 高亮所有本次技能相关的目标
        HighLightAllTargets();
        //    高亮所有本次技能相关的目标
        HighLightAllTargets();
        // 根据释放模式执行相应逻辑
        switch (skillConfig.castMode)
        {
            case SkillCastMode.Self:
                CastImpl(OnAttackFinish);
                break;
            case SkillCastMode.Enemy:
                CastToEnemy();
                break;
            case SkillCastMode.Target:
                CastToTarget();
                break;
            case SkillCastMode.Allies:
                CastToAllies();
                break;
            case SkillCastMode.DashCast:
                DashCast(OnAttackFinish);
                break;
            default:
                Debug.LogError("强制结束技能 暂时不支持其他的方式释放 有需求please联系策划 技能id:" + skillConfig.SkillID + " cast position " + skillConfig.CastPosition);
                ForceFinished();
                break;
        }
    }
        //    距离配成负数要转身 TurnBack
        switch (skillConfig.castMode)
        {
            case SkillCastMode.Self:
                CastImpl(OnAttackFinish);
                break;
            case SkillCastMode.Enemy:
                CastToEnemy();
                break;
            case SkillCastMode.Target:
                CastToTarget();
                break;
            case SkillCastMode.Allies:
                CastToAllies();
                break;
            case SkillCastMode.DashCast:
                DashCast(OnAttackFinish);
                break;
            default:
                Debug.LogError("强制结束技能 暂时不支持其他的方式释放 有需求请联系策划 技能id:" + skillConfig.SkillID + " cast position " + skillConfig.CastPosition);
                ForceFinished();
                break;
        }
    // 冲撞攻击模式(待实现)
    protected void DashCast(Action _onComplete)
    {
        Debug.LogError("DashCast 还没实现");
        ForceFinished();
    }
    }
    // 对敌方释放技能:移动到敌方区域进行攻击
    protected void CastToEnemy()
    {
        RectTransform target = battleField.GetTeamNode(caster.GetEnemyCamp(), skillConfig);
        ExecuteMoveAndCastSequence(target, () =>
        {
            MoveToTarget(battleField.GetTeamNode(caster.Camp, caster.teamHero.positionNum), Vector2.zero, OnAttackFinish, 750F);
        });
    }
    //    冲撞攻击
    protected void DashCast(Action _onComplete)
    {
        Debug.LogError("DashCast 还没实现");
        ForceFinished();
        //    YYL TODO
    // 对指定目标释放技能:移动到主要目标位置进行攻击
    protected void CastToTarget()
    {
        if (tagUseSkillAttack.HurtCount <= 0)
        {
            Debug.LogError("技能攻击包没有目标 HurtCount <= 0");
            OnSkillFinished();
            return;
        }
        // var entry = caster.motionBase.PlayAnimation(skillConfig.GetMotionName(), false);
        // float animationTime = entry.AnimationTime;
        int mainTargetPosNum = BattleUtility.GetMainTargetPositionNum(caster, tagUseSkillAttack.HurtList.ToList(), skillConfig);
        BattleCamp battleCamp = skillConfig.TagFriendly != 0 ? caster.Camp : caster.GetEnemyCamp();
        RectTransform targetTrans = battleField.GetTeamNode(battleCamp, mainTargetPosNum);
        // int mainTargetPosNum = BattleUtility.GetMainTargetPositionNum(caster, tagUseSkillAttack.HurtList.ToList(), skillConfig);
        ExecuteMoveAndCastSequence(targetTrans, () =>
        {
            RectTransform rectTransform = battleField.GetTeamNode(caster.Camp, caster.teamHero.positionNum);
            MoveToTarget(rectTransform, Vector2.zero, OnAttackFinish, 750F);
        });
    }
        // BattleCamp battleCamp = skillConfig.TagFriendly != 0 ? caster.Camp : caster.GetEnemyCamp();
    // 对友方释放技能:移动到友方区域进行治疗或增益
    protected void CastToAllies()
    {
        RectTransform target = battleField.GetTeamNode(caster.Camp, skillConfig);
        ExecuteMoveAndCastSequence(target, () =>
        {
            MoveToTarget(battleField.GetTeamNode(caster.Camp, caster.teamHero.positionNum), Vector2.zero, OnAttackFinish, 750F);
        });
    }
        // RectTransform targetTrans = battleField.GetTeamNode(battleCamp, mainTargetPosNum);
    // 执行移动-施法-返回序列:通用的移动攻击流程
    private void ExecuteMoveAndCastSequence(RectTransform target, Action onReturnComplete)
    {
        MoveToTarget(target, new Vector2(skillConfig.CastDistance, 0), () =>
        {
            TurnBack(() =>
            {
                CastImpl(() =>
                {
                    TurnBack(() =>
                    {
                        try
                        {
                            onReturnComplete?.Invoke(); // 添加异常处理防止回调异常导致状态不完整
                        }
                        catch (Exception ex)
                        {
                            Debug.LogError($"ExecuteMoveAndCastSequence回调异常: {ex.Message}");
                            throw;
                        }
                    }, -1f);
                });
            }, -1f);
        });
    }
        // var tweener = BattleUtility.MoveToTarget(caster.heroRectTrans, targetTrans, new Vector2(skillConfig.CastDistance, 0), animationTime * 0.9f, () =>
        // {
        //     caster.motionBase.PlayAnimation(MotionName.idle, true);
        //     _onComplete?.Invoke();
        // });
        // battleField.battleTweenMgr.OnPlayTween(tweener);
    }
    // 移动到目标位置:处理角色的移动动画和逻辑
    protected void MoveToTarget(RectTransform target, Vector2 offset, Action _onComplete = null, float speed = 500f)
    {
        if (skillConfig.CastDistance >= 9999)
        {
            _onComplete?.Invoke();
            return;
        }
    protected void MoveToTarget(RectTransform target, Vector2 offset, Action _onComplete = null, float speed = 500f)
    {
        //    原地释放
        if (skillConfig.CastDistance >= 9999)
        {
            _onComplete?.Invoke();
            return;
        }
        caster.motionBase.PlayAnimation(MotionName.run, true);
        var tweener = BattleUtility.MoveToTarget(caster.heroRectTrans, target, offset, () =>
        {
            caster.motionBase.PlayAnimation(MotionName.idle, true);
            _onComplete?.Invoke();
        }, speed);
        battleField.battleTweenMgr.OnPlayTween(tweener);
    }
        caster.motionBase.PlayAnimation(MotionName.run, true);
        var tweener = BattleUtility.MoveToTarget(caster.heroRectTrans, target, offset, () =>
        {
            caster.motionBase.PlayAnimation(MotionName.idle, true);
            _onComplete?.Invoke();
        }, speed);
        battleField.battleTweenMgr.OnPlayTween(tweener);
    // 转身逻辑:根据技能配置处理角色转向
    protected void TurnBack(Action _onComplete, float forward)
    {
        if (skillConfig.CastDistance < 0)
        {
            Vector3 scale = caster.heroGo.transform.localScale;
            scale.x = Mathf.Abs(scale.x) * forward;
            caster.heroGo.transform.localScale = scale;
        }
        _onComplete?.Invoke();
    }
        // Debug.LogError("move to tarrget " + target.name + " offset " + offset + " speed " + speed + " time " + tweener.Duration());
    }
    // 攻击完成后的处理:转身、恢复状态、播放待机动画
    protected void OnAttackFinish()
    {
        TurnBack(null, 1f);
        OnAllAttackMoveFinished();
        caster.motionBase.PlayAnimation(MotionName.idle, true);
    }
    protected void TurnBack(Action _onComplete, float forward)
    {
        if (skillConfig.CastDistance < 0)
        {
            //    转身
            Vector3 scale = caster.heroGo.transform.localScale;
            scale.x = Mathf.Abs(scale.x) * forward;
            caster.heroGo.transform.localScale = scale;
        }
        _onComplete?.Invoke();
    }
    // 所有攻击移动完成后的处理:恢复UI显示状态
    protected virtual void OnAllAttackMoveFinished()
    {
        moveFinished = true;
        List<BattleObject> allList = battleField.battleObjMgr.allBattleObjDict.Values.ToList<BattleObject>();
        foreach (BattleObject bo in allList)
        {
            bo.layerMgr.SetFront();
            bo.heroInfoBar.SetActive(true);
        }
        battleField.battleRootNode.skillMaskNode.SetActive(false);
    }
    protected void CastToEnemy()
    {
    // 执行技能释放动画和逻辑:播放施法动作并提供回调
    protected TrackEntry CastImpl(Action onComplete = null)
    {
        return caster.motionBase.PlaySkillAnimation(skillConfig, this, tagUseSkillAttack.BattleType == 4, onComplete);
    }
        RectTransform target = battleField.GetTeamNode(caster.GetEnemyCamp(), skillConfig);
    // 技能开始回调:处理死亡、子技能、技能效果初始化
    public void OnSkillStart()
    {
        HandleDead();
        skillEffect = SkillEffectFactory.CreateSkillEffect(caster, skillConfig, tagUseSkillAttack);
        skillEffect.Play(OnHitTargets);
        foreach (var subSkillPack in tagUseSkillAttack.subSkillList)
        {
            RecordAction recordAction = CustomHB426CombinePack.CreateSkillAction(battleField.guid, new List<GameNetPackBasic>() { subSkillPack });
            battleField.recordPlayer.ImmediatelyPlay(recordAction);
        }
        isPlay = true;
    }
        MoveToTarget(target, new Vector2(skillConfig.CastDistance, 0), () =>
        {
            //    到位置转身(不一定非要转身 但是流程要写)
            TurnBack(() =>
            {
                //    到达目标位置
                CastImpl(() =>
                {
                    TurnBack(
                        () =>
                        {
                            //    回到原来的位置
                            MoveToTarget(battleField.GetTeamNode(caster.Camp, caster.teamHero.positionNum), Vector2.zero,
                                OnAttackFinish, 750F);
                        }
                    , -1f);
                });
            }, -1f);
        });
    }
    // 技能前摇结束回调
    public virtual void OnStartSkillFrameEnd() { }
    // 技能中摇开始回调:通知技能效果处理中摇开始
    public virtual void OnMiddleFrameStart(int times)
    {
        skillEffect?.OnMiddleFrameStart(times); // 修复:添加空值检查
    }
    // 技能中摇结束回调:通知技能效果处理中摇结束
    public virtual void OnMiddleFrameEnd(int times, int hitIndex)
    {
        skillEffect?.OnMiddleFrameEnd(times, hitIndex); // 修复:添加空值检查
    }
    protected void CastToTarget()
    {
        // 目标是敌方主目标
        if (tagUseSkillAttack.HurtCount <= 0)
        {
            Debug.LogError("技能攻击包没有目标 HurtCount <= 0");
            OnSkillFinished();
            return;
        }
    // 技能后摇开始回调:通知技能效果处理后摇开始
    public virtual void OnFinalFrameStart()
    {
        skillEffect?.OnFinalFrameStart(); // 修复:添加空值检查
    }
        int mainTargetPosNum = BattleUtility.GetMainTargetPositionNum(caster, tagUseSkillAttack.HurtList.ToList(), skillConfig);
    // 技能后摇结束回调:通知技能效果处理后摇结束
    public virtual void OnFinalFrameEnd()
    {
        skillEffect?.OnFinalFrameEnd(); // 修复:添加空值检查
    }
        BattleCamp battleCamp = skillConfig.TagFriendly != 0 ? caster.Camp : caster.GetEnemyCamp();
    // 高亮所有相关目标:设置施法者和目标的显示层级
    protected void HighLightAllTargets()
    {
        caster.layerMgr.SetSortingOrder(BattleConst.ActiveHeroActionSortingOrder);
        RectTransform targetTrans = battleField.GetTeamNode(battleCamp, mainTargetPosNum);
        if (skillConfig.FuncType != 2)
            return;
        MoveToTarget(targetTrans, new Vector2(skillConfig.CastDistance, 0), () =>
        {
            //    到位置转身(不一定非要转身 但是流程要写)
            TurnBack(() =>
            {
                //    到达目标位置
                CastImpl(() =>
                {
                    TurnBack(
                        () =>
                        {
                            RectTransform rectTransform = battleField.GetTeamNode(caster.Camp, caster.teamHero.positionNum);
                            //    回到原来的位置
                            MoveToTarget(rectTransform, Vector2.zero, OnAttackFinish, 750F);
                        }
                    , -1f);
                });
            }, -1f);
        });
    }
        List<BattleObject> targetList = battleField.battleObjMgr.GetBattleObjList(tagUseSkillAttack);
        List<BattleObject> highlightList = new List<BattleObject>(targetList) { caster };
        List<BattleObject> allList = battleField.battleObjMgr.allBattleObjDict.Values.ToList<BattleObject>();
        // 修复:使用HashSet优化性能,避免重复设置
        var targetSet = new HashSet<BattleObject>(targetList);
        var highlightSet = new HashSet<BattleObject>(highlightList);
        caster.heroInfoBar.SetActive(false);
    protected virtual void OnAllAttackMoveFinished()
    {
        moveFinished = true;
        List<BattleObject> allList = battleField.battleObjMgr.allBattleObjDict.Values.ToList<BattleObject>();
        for (int i = 0; i < allList.Count; i++)
        {
            BattleObject bo = allList[i];
            bo.layerMgr.SetFront();
            // bo.heroRectTrans.SetParent(battleField.GetTeamNode(bo.Camp, bo.teamHero.positionNum), true);
            bo.heroInfoBar.SetActive(true);
        }
        battleField.battleRootNode.skillMaskNode.SetActive(false);
        // Debug.LogError("OnAllAttackMoveFinished skill " + skillConfig.SkillID + " cast position " + skillConfig.CastPosition + " cast mode " + skillConfig.castMode);
    }
        foreach (BattleObject bo in allList)
        {
            bool isHighlight = highlightSet.Contains(bo);
            bool isTarget = targetSet.Contains(bo);
            if (isHighlight)
            {
                bo.layerMgr.SetFront();
            }
            else
            {
                bo.layerMgr.SetBack();
            }
    protected void CastToAllies()
    {
        RectTransform target = battleField.GetTeamNode(caster.Camp, skillConfig);
            bo.heroInfoBar.SetActive(isTarget);
        }
        MoveToTarget(target, new Vector2(skillConfig.CastDistance, 0), () =>
        {
            //    到位置转身(不一定非要转身 但是流程要写)
            TurnBack(() =>
            {
                //    到达目标位置
                CastImpl(() =>
                {
                    TurnBack(
                        () =>
                        {
                            //    回到原来的位置
                            MoveToTarget(battleField.GetTeamNode(caster.Camp, caster.teamHero.positionNum),
                                Vector2.zero, OnAttackFinish, 750F);
                        }
                    , -1f);
                });
            }, -1f);
        });
    }
        battleField.battleRootNode.skillMaskNode.SetActive(true);
        battleField.battleRootNode.SetSortingOrder();
    }
    protected void OnAttackFinish()
    {
        TurnBack(null, 1f);
        OnAllAttackMoveFinished();
        caster.motionBase.PlayAnimation(MotionName.idle, true);
    }
    // 命中目标回调:处理所有被命中的目标
    protected virtual void OnHitTargets(int _hitIndex, List<HB427_tagSCUseSkill.tagSCUseSkillHurt> hitList)
    {
        foreach (var hurt in hitList)
        {
            BattleObject target = caster.battleField.battleObjMgr.GetBattleObject((int)hurt.ObjID);
            if (target == null)
            {
                Debug.LogError("目标为空 target == null ObjId : " + hurt.ObjID);
                continue;
            }
            OnHitEachTarget(_hitIndex, target, hurt);
        }
    }
    //    承载技能大部分的逻辑
    protected TrackEntry CastImpl(Action onComplete = null)
    {
        // 播放施法动作
        //    onComplete是指施法动作播放完的回调 不代表是技能结束
        //    具体技能结束的时间应该看技能对应的逻辑
        //    这里只提供6个动作相关的函数
        // OnSkillStart 动作第一帧
        // OnStartSkillFrameEnd 前摇结束
        // OnMiddleFrameStart 中摇开始
        // OnMiddleFrameEnd 中摇结束
        // OnFinalFrameStart 后摇开始
        // OnFinalFrameEnd 后摇结束
    // 处理单个目标被命中:应用伤害和施法者效果
    protected virtual void OnHitEachTarget(int _hitIndex, BattleObject target, HB427_tagSCUseSkill.tagSCUseSkillHurt hurt)
    {
        List<int> damageDivide = new List<int>();
        if (_hitIndex == 0 && skillConfig.DamageDivide.Length <= 0)
        {
            damageDivide.Add(10000);
        }
        else
        {
            if (skillConfig.DamageDivide.Length <= _hitIndex)
            {
                Debug.LogError("技能伤害分布配置错误 skillId: " + skillConfig.SkillID + " hitIndex: " + _hitIndex);
                damageDivide.Add(10000);
            }
            else
            {
                damageDivide = skillConfig.DamageDivide[_hitIndex].ToList();
            }
        }
        return caster.motionBase.PlaySkillAnimation(skillConfig, this, onComplete);
    }
        // 伤害分布计算和应用
        long totalDamage = GeneralDefine.GetFactValue(hurt.HurtHP, hurt.HurtHPEx);
        List<long> damageList = BattleUtility.DivideDamageToList(damageDivide.ToArray(), totalDamage);
    //    技能开始
    public void OnSkillStart()
    {
        HandleDead();
        skillEffect = SkillEffectFactory.CreateSkillEffect(
                caster,
                skillConfig,
                tagUseSkillAttack
            );
        // 获取临时数据并应用伤害
        int objID = (int)target.ObjID;
        tempDropList.TryGetValue(objID, out BattleDrops battleDrops);
        tempDeadPackList.TryGetValue(objID, out HB422_tagMCTurnFightObjDead deadPack);
        target.Hurt(damageList, totalDamage, hurt, skillConfig, _hitIndex, battleDrops, deadPack);
        skillEffect.Play(OnHitTargets);
        isPlay = true;
        // 处理施法者相关效果
        caster.SuckHp(hurt.SuckHP, skillConfig);
        caster.HurtByReflect(hurt.BounceHP, skillConfig);
    }
    }
    // 处理死亡相关逻辑:分配掉落和经验
    protected void HandleDead()
    {
        var deadPackList = BattleUtility.FindDeadPack(packList);
        if (deadPackList.Count <= 0) return;
    //    技能前摇帧结束
    public virtual void OnStartSkillFrameEnd()
    {
    }
        CheckAfterDeadhPack();
    /// <summary>
    /// 中摇开始 times=第几次循环 从0开始
    /// </summary>
    /// <param name="times"></param>
    public virtual void OnMiddleFrameStart(int times)
    {
        skillEffect.OnMiddleFrameStart(times);
    }
        // 修复:先收集要删除的包,避免在foreach中修改集合
        var dropPacksToRemove = new List<H0704_tagRolePackRefresh>(dropPackList);
        foreach (var _dropPack in dropPacksToRemove)
        {
            PackageRegedit.Distribute(_dropPack);
            packList.Remove(_dropPack);
        }
    public virtual void OnMiddleFrameEnd(int times, int hitIndex)
    {
        skillEffect.OnMiddleFrameEnd(times, hitIndex);
    }
        // 获取并分配掉落物品和经验
        var dropPack = PackManager.Instance.GetSinglePack(PackType.DropItem);
        var itemDict = dropPack.GetAllItems();
        List<ItemModel> itemList = new List<ItemModel>(itemDict.Values.Where(item => item != null && item.isAuction));
    /// <summary>
    /// 后摇开始
    /// </summary>
    public virtual void OnFinalFrameStart()
    {
        skillEffect.OnFinalFrameStart();
    }
        var dropAssign = AssignDrops(itemList, deadPackList.Count);
        var expAssign = AssignExp(expPackList, deadPackList.Count);
    /// <summary>
    /// 后摇结束
    /// </summary>
    public virtual void OnFinalFrameEnd()
    {
        skillEffect.OnFinalFrameEnd();
        // 构造BattleDrops并缓存
        for (int i = 0; i < deadPackList.Count; i++)
        {
            int objID = (int)deadPackList[i].ObjID;
            BattleObject deadTarget = battleField.battleObjMgr.GetBattleObject(objID);
            // 修复:添加空值检查
            if (deadTarget == null)
            {
                Debug.LogError($"找不到死亡目标,ObjID: {objID}");
                continue;
            }
            List<int> itemIndexList = dropAssign[i].Select(item => item.gridIndex).ToList();
            BattleDrops battleDrops = new BattleDrops()
            {
                rectTransform = deadTarget.heroRectTrans,
                dropItemPackIndex = itemIndexList,
                expDrops = expAssign[i]
            };
        //    转移到死亡包 battleobject.hurt 最后一击的时候播放
    }
    protected void HighLightAllTargets()
    {
        caster.layerMgr.SetSortingOrder(BattleConst.ActiveHeroActionSortingOrder);
        if (skillConfig.FuncType != 2)
            return;
        // 高亮所有目标
        List<BattleObject> targetList = battleField.battleObjMgr.GetBattleObjList(tagUseSkillAttack);
        List<BattleObject> highlightList = new List<BattleObject>(targetList);
        highlightList.Add(caster);
        List<BattleObject> allList = battleField.battleObjMgr.allBattleObjDict.Values.ToList<BattleObject>();
        caster.heroInfoBar.SetActive(false);
        for (int i = 0; i < allList.Count; i++)
        {
            BattleObject bo = allList[i];
            if (highlightList.Contains(bo))
            {
                bo.layerMgr.SetFront();
                bo.heroInfoBar.SetActive(true);
                // bo.heroRectTrans.SetParent(battleField.battleRootNode.skillFrontNode, true);
            }
            else
            {
                bo.layerMgr.SetBack();
                // bo.heroRectTrans.SetParent(battleField.battleRootNode.skillBackNode, true);
            }
            if (targetList.Contains(bo))
            {
                bo.heroInfoBar.SetActive(true);
            }
            else
            {
                bo.heroInfoBar.SetActive(false);
            }
        }
        battleField.battleRootNode.skillMaskNode.SetActive(true);
        battleField.battleRootNode.SetSortingOrder();
        // caster.battleField.skillMask
        //    把这些BO全高亮 或者说把除了这些的都放在遮罩后面
        //    YYL TODO
    }
    //    命中目标后的回调 正常是以各技能的方式来处理的
    protected virtual void OnHitTargets(int _hitIndex, List<HB427_tagSCUseSkill.tagSCUseSkillHurt> hitList)
    {
        for (int i = 0; i < hitList.Count; i++)
        {
            HB427_tagSCUseSkill.tagSCUseSkillHurt hurt = hitList[i];
            BattleObject target = caster.battleField.battleObjMgr.GetBattleObject((int)hurt.ObjID);
            if (target == null)
            {
                Debug.LogError("目标为空 target == null ObjId : " + hurt.ObjID);
                continue;
            }
            OnHitEachTarget(_hitIndex, target, hurt);
        }
    }
    protected virtual void OnHitEachTarget(int _hitIndex, BattleObject target, HB427_tagSCUseSkill.tagSCUseSkillHurt hurt)
    {
        // 伤害分布 (万分比)
        // Debug.LogError("skillConfig.DamageDivide.Count " + skillConfig.DamageDivide.Length + " _hitIndex " + _hitIndex);
        int[] damageDivide = skillConfig.DamageDivide[_hitIndex];
        long totalDamage = GeneralDefine.GetFactValue(hurt.HurtHP, hurt.HurtHPEx);
        // 保证所有分配项加起来等于totalDamage,避免因整除导致的误差
        List<long> damageList = BattleUtility.DivideDamageToList(damageDivide, totalDamage);
        // public uint ObjID;
        // public uint AttackTypes;        // 飘血类型汇总,支持多种类型并存,如无视防御且暴击同时被格挡,二进制或运算最终值;0-失败;1-普通;2-回血;5-格挡;6-无视防御;7-暴击;9-闪避
        // public uint HurtHP;        // 飘血值,求余亿部分
        // public uint HurtHPEx;        // 飘血值,整除亿部分
        // public uint CurHP;        // 更新剩余血量,求余亿部分
        // public uint CurHPEx;        // 更新剩余血量,整除亿部分
        // public uint SuckHP;        // 本次伤害转化的吸血量
        // public uint BounceHP;        // 本次伤害反弹的伤害量
        //    TODO YYL AttackTypes  要表现成什么样呢? 支持多种类型并存,如无视防御且暴击同时被格挡,二进制或运算最终值;0-失败;1-普通;2-回血;5-格挡;6-无视防御;7-暴击;9-闪避
        int objID = (int)target.ObjID;
        tempDropList.TryGetValue(objID, out BattleDrops battleDrops);
        tempDeadPackList.TryGetValue(objID, out HB422_tagMCTurnFightObjDead deadPack);
        target.Hurt(damageList, totalDamage, hurt, skillConfig, _hitIndex, battleDrops, deadPack);
        //    TODO YYL 这里是要做统一计算后再hurt跟suckhp还是怎样
        caster.SuckHp(hurt.SuckHP, skillConfig);//    吸血
        caster.HurtByReflect(hurt.BounceHP, skillConfig);// 反弹伤害
    }
    private Dictionary<int, BattleDrops> tempDropList = new Dictionary<int, BattleDrops>();
    private Dictionary<int, HB422_tagMCTurnFightObjDead> tempDeadPackList = new Dictionary<int, HB422_tagMCTurnFightObjDead>();
    protected void HandleDead()
    {
        var deadPackList = BattleUtility.FindDeadPack(packList);
        int deadCount = deadPackList.Count;
        if (deadCount <= 0)
        {
            //    如果没死亡就不用管
            return;
        }
        CheckAfterDeadhPack();
        // 处理掉落包 提前distribute之后 PackManager才有掉落物 所以不跟assignexp一样distribute
        foreach (var _dropPack in dropPackList)
        {
            PackageRegedit.Distribute(_dropPack);
            packList.Remove(_dropPack);
        }
        // 获取掉落物品
        var dropPack = PackManager.Instance.GetSinglePack(PackType.DropItem);
        var itemDict = dropPack.GetAllItems();
        List<ItemModel> itemList = new List<ItemModel>(
            from item in itemDict.Values
            where item != null && item.isAuction
            select item);
            // 修复:避免字典键冲突,使用安全的添加方式
            if (!tempDropList.ContainsKey(objID))
            {
                tempDropList.Add(objID, battleDrops);
            }
            else
            {
                Debug.LogWarning($"tempDropList中已存在ObjID={objID}的记录,将覆盖原值");
                tempDropList[objID] = battleDrops; // 覆盖现有值
            }
        // 分配掉落和经验
        var dropAssign = AssignDrops(itemList, deadCount);
        var expAssign = AssignExp(expPackList, deadCount);
            if (!tempDeadPackList.ContainsKey(objID))
            {
                tempDeadPackList.Add(objID, deadPackList[i]);
            }
            else
            {
                Debug.LogWarning($"tempDeadPackList中已存在ObjID={objID}的记录,将覆盖原值");
                tempDeadPackList[objID] = deadPackList[i]; // 覆盖现有值
            }
        }
        // 构造 BattleDrops 并分配
        for (int i = 0; i < deadCount; i++)
        {
            BattleObject deadTarget = battleField.battleObjMgr.GetBattleObject((int)deadPackList[i].ObjID);
            List<ItemModel> itemModelDrops = dropAssign[i];
            List<int> itemModelDropsIndexList = new List<int>(
                from item in itemModelDrops select item.gridIndex);
            BattleDrops battleDrops = new BattleDrops()
            {
                rectTransform = deadTarget.heroRectTrans,
                dropItemPackIndex = itemModelDropsIndexList,
                expDrops = expAssign[i]
            };
        // 修复:避免在遍历时修改集合,先收集后删除
        var deadPacksToRemove = new List<GameNetPackBasic>(deadPackList.Cast<GameNetPackBasic>());
        foreach (var deadPack in deadPacksToRemove)
        {
            packList.Remove(deadPack);
        }
    }
            tempDropList.Add((int)deadPackList[i].ObjID, battleDrops);
            // deadTarget.PushDropItems(battleDrops);
        }
    // 分配掉落物品:将掉落物品平均分配给死亡对象
    protected List<List<ItemModel>> AssignDrops(List<ItemModel> itemList, int deadCount)
    {
        var dropAssign = new List<List<ItemModel>>();
        for (int i = 0; i < deadCount; i++)
            dropAssign.Add(new List<ItemModel>());
        for (int i = 0; i < itemList.Count; i++)
            dropAssign[i % deadCount].Add(itemList[i]);
        return dropAssign;
    }
        // battleField.OnObjsDead(new List<HB422_tagMCTurnFightObjDead>(deadPackList));
        foreach (var deadPack in deadPackList)
        {
            tempDeadPackList.Add((int)deadPack.ObjID, deadPack);
            packList.Remove(deadPack);
        }
    }
    // 分配经验值:将经验包平均分配给每个死亡对象
    protected List<List<HB405_tagMCAddExp>> AssignExp(List<HB405_tagMCAddExp> expList, int deadCount)
    {
        var expAssign = new List<List<HB405_tagMCAddExp>>();
        for (int i = 0; i < deadCount; i++)
            expAssign.Add(new List<HB405_tagMCAddExp>());
        // 修复:检查除零风险
        if (deadCount == 0)
        {
            Debug.LogWarning("AssignExp: deadCount为0,无法分配经验");
            return expAssign;
        }
        // 修复:先收集要删除的包,避免在foreach中修改packList
        var expPacksToRemove = new List<HB405_tagMCAddExp>();
    // 分配掉落
    protected List<List<ItemModel>> AssignDrops(List<ItemModel> itemList, int deadCount)
    {
        var dropAssign = new List<List<ItemModel>>(deadCount);
        for (int i = 0; i < deadCount; i++)
            dropAssign.Add(new List<ItemModel>());
        for (int i = 0; i < itemList.Count; i++)
            dropAssign[i % deadCount].Add(itemList[i]);
        return dropAssign;
    }
        foreach (var expPack in expList)
        {
            long totalExp = GeneralDefine.GetFactValue(expPack.Exp, expPack.ExpPoint);
            long avgExp = totalExp / deadCount;
            long remain = totalExp % deadCount;
    // 分配经验:每个原始包都平均分配到每个死亡对象
    protected List<List<HB405_tagMCAddExp>> AssignExp(List<HB405_tagMCAddExp> expList, int deadCount)
    {
        var expAssign = new List<List<HB405_tagMCAddExp>>(deadCount);
        for (int i = 0; i < deadCount; i++)
            expAssign.Add(new List<HB405_tagMCAddExp>());
            for (int i = 0; i < deadCount; i++)
            {
                long assignExp = avgExp + (i < remain ? 1 : 0);
                var newPack = new HB405_tagMCAddExp
                {
                    Exp = (uint)(assignExp % 100000000),
                    ExpPoint = (uint)(assignExp / 100000000),
                    Source = expPack.Source
                };
                expAssign[i].Add(newPack);
            }
            expPacksToRemove.Add(expPack);
        }
        // 统一删除收集的包
        foreach (var pack in expPacksToRemove)
        {
            packList.Remove(pack);
        }
        foreach (var expPack in expList)
        {
            long totalExp = GeneralDefine.GetFactValue(expPack.Exp, expPack.ExpPoint);
            long avgExp = totalExp / deadCount;
            long remain = totalExp % deadCount;
        return expAssign;
    }
            for (int i = 0; i < deadCount; i++)
            {
                long assignExp = avgExp + (i < remain ? 1 : 0);
                long expPoint = assignExp / 100000000;
                long exp = assignExp % 100000000;
                var newPack = new HB405_tagMCAddExp
                {
                    Exp = (uint)exp,
                    ExpPoint = (uint)expPoint,
                    Source = expPack.Source // 保持原包来源
                };
                expAssign[i].Add(newPack);
            }
            packList.Remove(expPack);
        }
        return expAssign;
    }
    // 检查死亡后的包处理:处理技能包、掉落包、经验包
    protected void CheckAfterDeadhPack()
    {
        List<int> removeIndexList = new List<int>();
        for (int i = 0; i < packList.Count; i++)
        {
            var pack = packList[i];
            // 复活基本都靠技能包
            if (pack is CustomHB426CombinePack combinePack && combinePack.startTag.Tag.StartsWith("Skill_"))
                break;
            if (pack is H0704_tagRolePackRefresh h0704Pack && h0704Pack.PackType == (byte)PackType.DropItem && h0704Pack.IsBind == 1)
            {
                dropPackList.Add(h0704Pack);
                removeIndexList.Add(i);
            }
            if (pack is HB405_tagMCAddExp h405Pack && h405Pack.Source == 2)
            {
                expPackList.Add(h405Pack);
                removeIndexList.Add(i);
            }
        }
        for (int i = removeIndexList.Count - 1; i >= 0; i--)
            packList.RemoveAt(removeIndexList[i]);
    }
    protected void CheckAfterDeadhPack()
    {
        List<int> removeIndexList = new List<int>();
        for (int i = 0; i < packList.Count; i++)
        {
            var pack = packList[i];
    // 检查技能是否完成:综合检查所有完成条件
    public virtual bool IsFinished()
    {
        if (!isPlay) return false;
            //     复活基本都靠技能包
            if (pack is CustomHB426CombinePack)
            {
                var combinePack = pack as CustomHB426CombinePack;
                if (combinePack.startTag.Tag.StartsWith("Skill_"))
                {
                    break; // 找到技能包就不需要再处理了
                }
            }
            else if (pack is H0704_tagRolePackRefresh)
            {
                var h0704Pack = pack as H0704_tagRolePackRefresh;
                if (h0704Pack.PackType == (byte)PackType.DropItem)
                {
                    //    掉落的
                    if (h0704Pack.IsBind == 1)
                    {
                        //    掉落的物品
                        dropPackList.Add(h0704Pack);
                        removeIndexList.Add(i);
                    }
                    else if (h0704Pack.IsBind == 0)
                    {
                        //    替换的
                    }
                }
            }
            else if (pack is HB405_tagMCAddExp)
            {
                var h405Pack = pack as HB405_tagMCAddExp;
        // 检查技能效果是否完成
        if (skillEffect != null)
        {
            if (!skillEffect.IsFinished()) return false;
            skillEffect = null;
            OnSkillFinished();
            return false;
        }
                //B4 05 获得经验 #tagMCAddExp 通知获得的经验,
                //可用于做经验获得表现 Source = 2 时为主线击杀怪物获得经验
                if (h405Pack.Source == 2)
                {
                    expPackList.Add(h405Pack);
                    removeIndexList.Add(i);
                }
            }
        // 检查其他技能动作是否完成
        if (otherSkillActionList.Count > 0)
        {
            for (int i = otherSkillActionList.Count - 1; i >= 0; i--)
            {
                var action = otherSkillActionList[i];
                if (action.IsFinished())
                {
                    otherSkillActionList.RemoveAt(i);
                    OnSkillFinished();
                }
            }
            if (otherSkillActionList.Count > 0) return false;
        }
        }
        // 检查最终完成状态
        if (isFinished && moveFinished)
        {
            if (packList.Count > 0)
            {
                OnSkillFinished();
                return false;
            }
        for (int i = removeIndexList.Count - 1; i >= 0; i--)
        {
            packList.RemoveAt(removeIndexList[i]);
        }
    }
    public virtual bool IsFinished()
    {
        if (!isPlay)
        {
            return false;
        }
            return true;
        }
        if (skillEffect != null)
        {
            if (!skillEffect.IsFinished())
            {
                return false;
            }
            else
            {
                skillEffect = null;
                OnSkillFinished();
                return false;
            }
        }
        return false;
    }
        if (otherSkillAction != null)
        {
            if (!otherSkillAction.IsFinished())
            {
                return false;
            }
            else
            {
                otherSkillAction = null;
                OnSkillFinished();
                return false;
            }
        }
    // 强制结束技能:立即结束所有技能相关的处理
    public virtual void ForceFinished()
    {
        skillEffect?.ForceFinished();
        otherSkillActionList.ForEach(action => action.ForceFinish());
        otherSkillActionList.Clear();
        if (isFinished && moveFinished)
        {
            if (packList.Count > 0)
            {
                OnSkillFinished();
                return false;
            }
        isFinished = true;
        moveFinished = true;
        isPlay = true;
            return true;
        }
        else
        {
            return false;
        }
    }
        // 处理所有剩余包
        while (packList.Count > 0)
        {
            var pack = packList[0];
            packList.RemoveAt(0);
    public virtual void ForceFinished()
    {
        skillEffect?.ForceFinished();
        if (otherSkillAction != null)
        {
            otherSkillAction.ForceFinish();
            otherSkillAction = null;
        }
        isFinished = true;
        moveFinished = true;
        isPlay = true;
            if (pack is CustomHB426CombinePack combinePack && combinePack.startTag.Tag.StartsWith("Skill_"))
            {
                var otherSkillAction = combinePack.CreateSkillAction();
                otherSkillAction.fromSkillId = skillConfig.SkillID;
                otherSkillAction.ForceFinish();
            }
            else
            {
                if (pack is CustomB421ActionPack actionPack)
                    actionPack.Distribute();
                PackageRegedit.Distribute(pack);
            }
        }
    }
        while (packList.Count > 0)
        {
            var pack = packList[0];
            packList.RemoveAt(0);
    // 技能完成处理:正常完成时的清理工作
    public void OnSkillFinished()
    {
        // 修复:使用循环代替递归,避免栈溢出风险
        try
        {
            while (true)
            {
                // 验证技能效果是否完成
                if (skillEffect != null && !skillEffect.IsFinished())
                    return;
                if (skillEffect != null)
                {
                    skillEffect = null;
                    continue; // 使用continue代替递归调用
                }
            if (pack is CustomHB426CombinePack combinePack)
            {
                if (combinePack.startTag.Tag.StartsWith("Skill_"))
                {
                    otherSkillAction = combinePack.CreateSkillAction();
                    otherSkillAction.fromSkillId = skillConfig.SkillID;
                    //    强制结束其他技能
                    otherSkillAction.ForceFinish();
                    continue;
                }
            }
            else if (pack is CustomB421ActionPack actionPack)
            {
                actionPack.Distribute();
            }
            PackageRegedit.Distribute(pack);
        }
    }
                // 验证其他技能动作是否完成
                if (otherSkillActionList.Count > 0)
                {
                    bool hasFinishedAction = false;
                    for (int i = otherSkillActionList.Count - 1; i >= 0; i--)
                    {
                        var action = otherSkillActionList[i];
                        if (action.IsFinished())
                        {
                            otherSkillActionList.RemoveAt(i);
                            hasFinishedAction = true;
                        }
                    }
                    if (hasFinishedAction)
                    {
                        continue; // 使用continue代替递归调用
                    }
                    return;
                }
    public void OnSkillFinished()
    {
        if (skillEffect != null)
        {
            if (!skillEffect.IsFinished())
            {
                return;
            }
            else
            {
                skillEffect = null;
                OnSkillFinished();
            }
        }
                break; // 没有更多需要处理的,退出循环
            }
        if (otherSkillAction != null)
        {
            if (!otherSkillAction.IsFinished())
            {
                return;
            }
            else
            {
                otherSkillAction = null;
                OnSkillFinished();
            }
        }
            // 处理剩余包
            while (packList.Count > 0)
            {
                var pack = packList[0];
                packList.RemoveAt(0);
        while (packList.Count > 0)
        {
            var pack = packList[0];
            packList.RemoveAt(0);
                if (pack is CustomHB426CombinePack combinePack && combinePack.startTag.Tag.StartsWith("Skill_"))
                {
                    BattleDebug.LogError("other skill casting " + combinePack.startTag.Tag);
                    var otherSkillAction = combinePack.CreateSkillAction();
                    otherSkillAction.fromSkillId = skillConfig.SkillID;
                    return;
                }
            if (pack is CustomHB426CombinePack)
            {
                var combinePack = pack as CustomHB426CombinePack;
                if (combinePack.startTag.Tag.StartsWith("Skill_"))
                {
                    BattleDebug.LogError("other skill casting " + combinePack.startTag.Tag);
                    otherSkillAction = combinePack.CreateSkillAction();
                    otherSkillAction.fromSkillId = skillConfig.SkillID;
                    return;
                }
            }
            else if (pack is CustomB421ActionPack actionPack)
            {
                actionPack.Distribute();
            }
                if (pack is CustomB421ActionPack actionPack)
                    actionPack.Distribute();
                PackageRegedit.Distribute(pack);
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"OnSkillFinished异常: {ex.Message},技能ID={skillConfig.SkillID}");
            // 确保状态一致性,即使出现异常也要标记完成
            isFinished = true;
            throw; // 重新抛出异常供上层处理
        }
            // Debug.LogError("Distribute pack " + pack.GetType().ToString());
            PackageRegedit.Distribute(pack);
        }
        isFinished = true;
    }
        isFinished = true;
    }
    // 添加清理方法:防止内存泄漏
    public virtual void Cleanup()
    {
        tempDropList?.Clear();
        tempDeadPackList?.Clear();
        otherSkillActionList?.Clear();
        dropPackList?.Clear();
        expPackList?.Clear();
        skillEffect = null;
        packList = null;
    }
}
Main/System/Battle/Skill/SkillEffectType.cs
@@ -7,4 +7,6 @@
    Direct,
    BuffEffect,
    StageEffect,
    NoEffect,
}
Main/System/Battle/SkillEffect/NoEffect.cs
New file
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using UnityEngine;
//
public class NoEffect : SkillEffect
{
    // protected SkillConfig skillConfig;
    // protected BattleObject caster;
    // protected List<BattleObject> targets; // 目标列表
    public NoEffect(SkillConfig _skillConfig, BattleObject _caster, HB427_tagSCUseSkill _tagUseSkillAttack)
        : base(_skillConfig, _caster, _tagUseSkillAttack)
    {
    }
    public override void OnMiddleFrameEnd(int times, int hitIndex)
    {
        int mainTargetIndex = BattleUtility.GetMainTargetPositionNum(caster, tagUseSkillAttack.HurtList.ToList(), skillConfig);
        BattleCamp battleCamp = skillConfig.TagFriendly == 1 ? caster.Camp : caster.GetEnemyCamp();
        RectTransform targetTransform = caster.battleField.GetTeamNode(battleCamp, mainTargetIndex);
        if (skillConfig.ExplosionEffectId > 0)
        {
            caster.battleField.battleEffectMgr.PlayEffect(caster, skillConfig.ExplosionEffectId, targetTransform, caster.Camp);
        }
        if (skillConfig.ExplosionEffect2 > 0)
        {
            caster.battleField.battleEffectMgr.PlayEffect(caster, skillConfig.ExplosionEffect2, targetTransform, caster.Camp);
        }
        for (int i = 0; i < tagUseSkillAttack.HurtList.Length; i++)
        {
            var hurt = tagUseSkillAttack.HurtList[i];
            BattleObject target = caster.battleField.battleObjMgr.GetBattleObject((int)hurt.ObjID);
            if (target == null)
            {
                Debug.LogError("特效目标为空 target == null ObjId : " + hurt.ObjID);
                continue;
            }
            if (skillConfig.ExplosionEffect3 > 0)
            {
                caster.battleField.battleEffectMgr.PlayEffect(target, skillConfig.ExplosionEffect3, target.heroGo.transform, caster.Camp);
            }
        }
        onHit?.Invoke(hitIndex, tagUseSkillAttack.HurtList.ToList());
    }
    /// <summary>
    /// 后摇结束
    /// </summary>
    public override void OnFinalFrameEnd()
    {
        base.OnFinalFrameEnd();
        isFinish = true;
    }
}
Main/System/Battle/SkillEffect/NoEffect.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b2652b2c82a4d7d49a6e2cb75cea978b
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/Battle/SkillEffect/SkillEffectFactory.cs
@@ -6,6 +6,7 @@
{
    public static SkillEffect CreateSkillEffect(BattleObject caster, SkillConfig skillConfig, HB427_tagSCUseSkill tagUseSkillAttack)
    {
        switch (skillConfig.effectType)
        {
            case SkillEffectType.Bullet:
@@ -16,6 +17,8 @@
            //     return new BuffSkillEffect(skillConfig, caster, targets);
            // case SkillEffectType.StageEffect:
            //     return new StageSkillEffect(skillConfig, caster, targets);
            case SkillEffectType.NoEffect:
                return new NoEffect(skillConfig, caster, tagUseSkillAttack);
            default:
                BattleDebug.LogError("Unknown Skill Effect Type");
                break;