yyl
2025-08-20 8f983d0dab26becb6b85dbbb616fde21c3ad8f02
125 【战斗】战斗系统
32个文件已修改
1307 ■■■■■ 已修改文件
Main/Component/UI/Effect/BattleEffectPlayer.cs 111 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/Configs/EffectConfig.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/Configs/HeroSkinConfig.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Config/Configs/SkillConfig.cs 103 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/DTCFile/ServerPack/HB4_FightDefine/DTCB422_tagMCTurnFightObjDead.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/ServerPack/HB4_FightDefine/HB418_tagSCObjPropertyRefreshView.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleEffectMgr.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleField/BattleField.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleField/RecordActions/DeathRecordAction.cs 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleObject/BattleObject.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleObject/BattleObjectFactory.cs 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleTweenMgr.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleUtility.cs 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Motion/MotionBase.cs 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/RecordPlayer/RecordPlayer.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Skill/DirectlyDamageSkill.cs 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Skill/DirectlyHealSkill.cs 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Skill/RebornSkill.cs 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Skill/SkillBase.cs 359 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/BulletCurve/BezierBulletCurve.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/BulletCurve/BounceBulletCurve.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/BulletCurve/BulletCurve.cs 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/BulletCurve/BulletCurveFactory.cs 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/BulletCurve/PenetrateBulletCurve.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/BulletCurve/StraightBulletCurve.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/BulletSkillEffect.cs 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/NormalSkillEffect.cs 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/SkillEffect.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/SkillEffect/SkillEffectFactory.cs 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Team/TeamBase.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Utility/EffectPenetrationBlocker.cs 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Utility/UniTaskExtension.cs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Component/UI/Effect/BattleEffectPlayer.cs
@@ -60,7 +60,7 @@
    protected GameObject spineContainer;
    protected SkeletonGraphic spineComp;
    protected SkeletonAnimation spineComp;
    protected Spine.AnimationState spineAnimationState;
    public GameObjectPoolManager.GameObjectPool pool;
@@ -107,13 +107,14 @@
        }
        //  初始化spine组件
        if (effectConfig != null && effectConfig.isSpine != 1)
        if (effectConfig != null && effectConfig.isSpine != 0)
        {
            GameObject spineContainerPrefab = ResManager.Instance.LoadAsset<GameObject>("UIComp", "SpineContainer");
            spineContainer = GameObject.Instantiate(spineContainerPrefab, transform);
            GameObject spineContainerPrefab = ResManager.Instance.LoadAsset<GameObject>("UIComp", "SpineAnimContainer");
            spineContainer = GameObject.Instantiate(spineContainerPrefab, transform, true);
            spineContainer.transform.localPosition = Vector3.zero;
        }
        //  有特效可能带spine又带unity特效的情况
        spineComp = gameObject.GetComponentInChildren<SkeletonGraphic>(true);
        spineComp = gameObject.GetComponentInChildren<SkeletonAnimation>(true);
    }
    protected virtual void Clear()
@@ -149,20 +150,22 @@
    public virtual void Play(bool showLog = true)
    {
        if (!isInit)
        {
            InitComponent(showLog);
            isInit = true;
        }
        else
        {
            //避免重复创建
            if (!this.gameObject.activeSelf)
            {
                this.gameObject.SetActive(true);
                InitComponent(showLog);
                isInit = true;
            }
            return;
        }
            else
            {
                //避免重复创建
                if (!this.gameObject.activeSelf)
                {
                    this.gameObject.SetActive(true);
                }
                return;
            }
        if (EffectMgr.IsNotShowBySetting(effectId))
        {
@@ -194,6 +197,15 @@
            return;
        }
        //  如果delay小于等于0 那会立刻执行
        this.DelayTime(effectConfig.delayPlay, () =>
        {
            PlayEffectInternal();
        });
    }
    protected void PlayEffectInternal()
    {
        if (effectConfig.isSpine != 0)
        {
            PlaySpineEffect();
@@ -202,7 +214,6 @@
        {
            PlayUnityEffect();
        }
    }
    protected void PlaySpineEffect()
@@ -215,18 +226,50 @@
            return;
        }
        SkeletonDataAsset skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>("UIEffect/BattleSpine/" + effectConfig.packageName, effectConfig.fxName);
        SkeletonDataAsset skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>("UIEffect/" + effectConfig.packageName, effectConfig.fxName);
        spineComp.skeletonDataAsset = skeletonDataAsset;
        spineComp.Initialize(true);
        spineComp.raycastTarget = false;
        spineComp.timeScale = speedRate;
        spineAnimationState = spineComp.AnimationState;
        spineAnimationState.Complete -= OnSpineAnimationComplete;
        spineAnimationState.Complete += OnSpineAnimationComplete;
        if (null == canvas)
            canvas = GetComponentInParent<Canvas>();
        // 添加特效穿透阻挡器
        blocker = spineComp.AddMissingComponent<EffectPenetrationBlocker>();
        blocker.onSortingChanged = OnSortingChanged;
        //  如果没有canvas的话 正常是因为不在BattleWin下面的节点 意思就是当前没有显示 等到切回战斗的时候再通过BattleField.UpdateCanvas来更新
        if (canvas != null)
        {
            blocker.SetParentCanvas(canvas);
        }
        spineComp.enabled = true;
        Spine.Animation animation = spineAnimationState.Data.SkeletonData.Animations.First();
        spineAnimationState.SetAnimation(0, animation, false);
        spineAnimationState.SetAnimation(0, animation, effectConfig.isLoop != 0);
        SoundPlayer.Instance.PlayUIAudio(effectConfig.audio);
    }
    private bool CheckForAdditiveBlend(Spine.Skeleton skeleton)
    {
        // 遍历所有插槽,检查是否有相加模式
        foreach (var slot in skeleton.Slots)
        {
            if (slot.Data.BlendMode == Spine.BlendMode.Additive)
            {
                return true;
            }
        }
        return false;
    }
    protected void PlayUnityEffect()
@@ -268,6 +311,8 @@
        // 添加特效穿透阻挡器
        blocker = effectTarget.AddMissingComponent<EffectPenetrationBlocker>();
        blocker.onSortingChanged = OnSortingChanged;
        //  如果没有canvas的话 正常是因为不在BattleWin下面的节点 意思就是当前没有显示 等到切回战斗的时候再通过BattleField.UpdateCanvas来更新
        if (canvas != null)
        {
@@ -278,6 +323,32 @@
    }
    void LateUpdate()
    {
        if (string.IsNullOrEmpty(sortingLayer))
        {
            return;
        }
        OnSortingChanged(sortingLayer, sortingOrder);
    }
    public string sortingLayer;
    public int sortingOrder;
    protected void OnSortingChanged(string _sortingLayer, int _sortingOrder)
    {
        sortingLayer = _sortingLayer;
        sortingOrder = _sortingOrder;
        // 处理排序变化
        var renderers = spineComp.GetComponents<Renderer>();
        foreach (var renderer in renderers)
        {
            renderer.sortingLayerName = sortingLayer;
            renderer.sortingOrder = sortingOrder;
        }
    }
    protected void OnDestroy()
    {
        if (onDestroy != null)
Main/Config/Configs/EffectConfig.cs
@@ -1,6 +1,6 @@
//--------------------------------------------------------
//    [Author]:           YYL
//    [  Date ]:           2025年8月14日
//    [  Date ]:           2025年8月19日
//--------------------------------------------------------
using System.Collections.Generic;
@@ -27,6 +27,8 @@
    public int autoDestroy;
    public float destroyDelay;
    public int isLoop;
    public int frontBack;
    public float delayPlay;
    public override int LoadKey(string _key)
    {
@@ -59,6 +61,10 @@
            float.TryParse(tables[9],out destroyDelay); 
            int.TryParse(tables[10],out isLoop); 
            int.TryParse(tables[11],out frontBack);
            float.TryParse(tables[12],out delayPlay);
        }
        catch (Exception exception)
        {
Main/Config/Configs/HeroSkinConfig.cs
@@ -1,6 +1,6 @@
//--------------------------------------------------------
//    [Author]:           YYL
//    [  Date ]:           2025年8月5日
//    [  Date ]:           2025年8月18日
//--------------------------------------------------------
using System.Collections.Generic;
Main/Config/Configs/SkillConfig.cs
@@ -1,6 +1,6 @@
//--------------------------------------------------------
//    [Author]:           YYL
//    [  Date ]:           2025年8月7日
//    [  Date ]:           Wednesday, August 20, 2025
//--------------------------------------------------------
using System.Collections.Generic;
@@ -30,9 +30,6 @@
    public int TagAffect;
    public int TagCount;
    public int HappenRate;
    public int LastTime;
    public int CoolDownTime;
    public int Priority;
    public int EffectID1;
    public int[] EffectValues1;
    public int EffectID2;
@@ -40,10 +37,11 @@
    public int EffectID3;
    public int[] EffectValues3;
    public int ConnSkill;
    public int CoolDownTime;
    public int[] EnhanceSkillList;
    public int FightPower;
    public int StartupFrames;
    public int ActiveFrames;
    public int[] ActiveFrames;
    public int RecoveryFrames;
    public int LoopCount;
    public int CastPosition;
@@ -61,6 +59,7 @@
    public int EffectPos;
    public int EffectType;
    public string IconName;
    public int ExplosionEffect2;
    public override int LoadKey(string _key)
    {
@@ -100,21 +99,15 @@
            int.TryParse(tables[13],out HappenRate); 
            int.TryParse(tables[14],out LastTime);
            int.TryParse(tables[14],out EffectID1);
            int.TryParse(tables[15],out CoolDownTime);
            int.TryParse(tables[16],out Priority);
            int.TryParse(tables[17],out EffectID1);
            if (tables[18].Contains("["))
            if (tables[15].Contains("["))
            {
                EffectValues1 = JsonMapper.ToObject<int[]>(tables[18]);
                EffectValues1 = JsonMapper.ToObject<int[]>(tables[15]);
            }
            else
            {
                string[] EffectValues1StringArray = tables[18].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
                string[] EffectValues1StringArray = tables[15].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
                EffectValues1 = new int[EffectValues1StringArray.Length];
                for (int i=0;i<EffectValues1StringArray.Length;i++)
                {
@@ -122,15 +115,15 @@
                }
            }
            int.TryParse(tables[19],out EffectID2);
            int.TryParse(tables[16],out EffectID2);
            if (tables[20].Contains("["))
            if (tables[17].Contains("["))
            {
                EffectValues2 = JsonMapper.ToObject<int[]>(tables[20]);
                EffectValues2 = JsonMapper.ToObject<int[]>(tables[17]);
            }
            else
            {
                string[] EffectValues2StringArray = tables[20].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
                string[] EffectValues2StringArray = tables[17].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
                EffectValues2 = new int[EffectValues2StringArray.Length];
                for (int i=0;i<EffectValues2StringArray.Length;i++)
                {
@@ -138,15 +131,15 @@
                }
            }
            int.TryParse(tables[21],out EffectID3);
            int.TryParse(tables[18],out EffectID3);
            if (tables[22].Contains("["))
            if (tables[19].Contains("["))
            {
                EffectValues3 = JsonMapper.ToObject<int[]>(tables[22]);
                EffectValues3 = JsonMapper.ToObject<int[]>(tables[19]);
            }
            else
            {
                string[] EffectValues3StringArray = tables[22].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
                string[] EffectValues3StringArray = tables[19].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
                EffectValues3 = new int[EffectValues3StringArray.Length];
                for (int i=0;i<EffectValues3StringArray.Length;i++)
                {
@@ -154,15 +147,17 @@
                }
            }
            int.TryParse(tables[23],out ConnSkill);
            int.TryParse(tables[20],out ConnSkill);
            if (tables[24].Contains("["))
            int.TryParse(tables[21],out CoolDownTime);
            if (tables[22].Contains("["))
            {
                EnhanceSkillList = JsonMapper.ToObject<int[]>(tables[24]);
                EnhanceSkillList = JsonMapper.ToObject<int[]>(tables[22]);
            }
            else
            {
                string[] EnhanceSkillListStringArray = tables[24].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
                string[] EnhanceSkillListStringArray = tables[22].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
                EnhanceSkillList = new int[EnhanceSkillListStringArray.Length];
                for (int i=0;i<EnhanceSkillListStringArray.Length;i++)
                {
@@ -170,45 +165,59 @@
                }
            }
            int.TryParse(tables[25],out FightPower);
            int.TryParse(tables[23],out FightPower);
            int.TryParse(tables[26],out StartupFrames);
            int.TryParse(tables[24],out StartupFrames);
            int.TryParse(tables[27],out ActiveFrames);
            if (tables[25].Contains("["))
            {
                ActiveFrames = JsonMapper.ToObject<int[]>(tables[25]);
            }
            else
            {
                string[] ActiveFramesStringArray = tables[25].Trim().Split(StringUtility.splitSeparator,StringSplitOptions.RemoveEmptyEntries);
                ActiveFrames = new int[ActiveFramesStringArray.Length];
                for (int i=0;i<ActiveFramesStringArray.Length;i++)
                {
                     int.TryParse(ActiveFramesStringArray[i],out ActiveFrames[i]);
                }
            }
            int.TryParse(tables[28],out RecoveryFrames);
            int.TryParse(tables[26],out RecoveryFrames);
            int.TryParse(tables[29],out LoopCount);
            int.TryParse(tables[27],out LoopCount);
            int.TryParse(tables[30],out CastPosition);
            int.TryParse(tables[28],out CastPosition);
            int.TryParse(tables[31],out CastIndexNum);
            int.TryParse(tables[29],out CastIndexNum);
            float.TryParse(tables[32],out CastDistance);
            float.TryParse(tables[30],out CastDistance);
            DamageDivide = JsonMapper.ToObject<int[][]>(tables[33].Replace("(", "[").Replace(")", "]"));
            DamageDivide = JsonMapper.ToObject<int[][]>(tables[31].Replace("(", "[").Replace(")", "]"));
            int.TryParse(tables[34],out BulletEffectId);
            int.TryParse(tables[32],out BulletEffectId);
            int.TryParse(tables[35],out BulletPos);
            int.TryParse(tables[33],out BulletPos);
            int.TryParse(tables[36],out BulletPath);
            int.TryParse(tables[34],out BulletPath);
            int.TryParse(tables[37],out BulletFlyTime);
            int.TryParse(tables[35],out BulletFlyTime);
            int.TryParse(tables[38],out ExplosionEffectId);
            int.TryParse(tables[36],out ExplosionEffectId);
            int.TryParse(tables[39],out ExplosionPos);
            int.TryParse(tables[37],out ExplosionPos);
            SkillMotionName = tables[40];
            SkillMotionName = tables[38];
            int.TryParse(tables[41],out EffectId);
            int.TryParse(tables[39],out EffectId);
            int.TryParse(tables[42],out EffectPos);
            int.TryParse(tables[40],out EffectPos);
            int.TryParse(tables[43],out EffectType);
            int.TryParse(tables[41],out EffectType);
            IconName = tables[44];
            IconName = tables[42];
            int.TryParse(tables[43],out ExplosionEffect2);
        }
        catch (Exception exception)
        {
Main/Core/NetworkPackage/DTCFile/ServerPack/HB4_FightDefine/DTCB422_tagMCTurnFightObjDead.cs
@@ -6,10 +6,8 @@
public class DTCB422_tagMCTurnFightObjDead : DtcBasic {
    public override void Done(GameNetPackBasic vNetPack) {
        base.Done(vNetPack);
        HB422_tagMCTurnFightObjDead vNetData = vNetPack as HB422_tagMCTurnFightObjDead;
        BattleField battleField = BattleManager.Instance.GetBattleField(vNetPack.packUID);
        battleField.OnObjDead((int)vNetData.ObjID);
        // 由技能去通知战场死亡 battleField.OnObjsDead(deadList)
    }
}
Main/Core/NetworkPackage/ServerPack/HB4_FightDefine/HB418_tagSCObjPropertyRefreshView.cs
@@ -6,6 +6,7 @@
public class HB418_tagSCObjPropertyRefreshView : GameNetPackBasic {
    public uint ObjID;
    public ushort RefreshType;    // 同0418刷新类型,如血量、怒气
    public uint AttackTypes;    // 飘字类型汇总,支持多种类型并存,如无视防御且暴击同时被格挡,二进制或运算最终值;0-失败;1-普通;2-回血;5-格挡;6-无视防御;7-暴击;9-闪避
    public uint Value;    // 更新值
    public uint ValueEx;    // 更新值,如果是大数值的此值为整除亿部分
    public byte DiffType;    // 变化类型,0-减少;1-增加
@@ -21,6 +22,7 @@
    public override void ReadFromBytes (byte[] vBytes) {
        TransBytes (out ObjID, vBytes, NetDataType.DWORD);
        TransBytes (out RefreshType, vBytes, NetDataType.WORD);
        TransBytes (out AttackTypes, vBytes, NetDataType.DWORD);
        TransBytes (out Value, vBytes, NetDataType.DWORD);
        TransBytes (out ValueEx, vBytes, NetDataType.DWORD);
        TransBytes (out DiffType, vBytes, NetDataType.BYTE);
Main/System/Battle/BattleEffectMgr.cs
@@ -43,6 +43,17 @@
    public BattleEffectPlayer PlayEffect(int ObjID, int effectId, Transform parent)
    {
        if (effectId <= 0)
        {
            return null;
        }
        var effectCfg = EffectConfig.Get(effectId);
        if (null == effectCfg)
        {
            return null;
        }
        if (!effectDict.ContainsKey(effectId))
        {
            effectDict[effectId] = new List<BattleEffectPlayer>();
Main/System/Battle/BattleField/BattleField.cs
@@ -281,9 +281,9 @@
    }
    public virtual void OnObjDead(int ObjID)
    public virtual void OnObjsDead(List<HB422_tagMCTurnFightObjDead> deadPackList)
    {
        DeathRecordAction recordAction = new DeathRecordAction(this, battleObjMgr.GetBattleObject(ObjID));
        DeathRecordAction recordAction = new DeathRecordAction(this, deadPackList);
        recordPlayer.PlayRecord(recordAction);
    }
@@ -324,7 +324,7 @@
        return GetTeamNode(battleCamp, index);
    }
    private RectTransform GetTeamNode(BattleCamp battleCamp, int index)
    public RectTransform GetTeamNode(BattleCamp battleCamp, int index)
    {
        if (index < 0 || index >= battleRootNode.redTeamNodeList.Count)
        {
Main/System/Battle/BattleField/RecordActions/DeathRecordAction.cs
@@ -3,12 +3,14 @@
public class DeathRecordAction : RecordAction
{
    protected List<HB422_tagMCTurnFightObjDead> deadPackList = new List<HB422_tagMCTurnFightObjDead>();
    public DeathRecordAction(BattleField _battleField, BattleObject _battleObj)
        : base(RecordActionType.Death, _battleField, _battleObj)
    {
        isFinish = false;
    }
    public DeathRecordAction(BattleField _battleField, List<HB422_tagMCTurnFightObjDead> _deadPackList)
        : base(RecordActionType.Death, _battleField, null)
    {
        isFinish = false;
        deadPackList = _deadPackList;
    }
    public override bool IsFinished()
    {
@@ -19,8 +21,18 @@
    public override void Run()
    {
        base.Run();
        isFinish = true;
        battleObject.OnDeath(OnDeathAnimationEnd);
        if (!isRunOnce)
        {
            isRunOnce = true;
            foreach (var deadPack in deadPackList)
            {
                BattleObject deadObj = battleField.battleObjMgr.GetBattleObject((int)deadPack.ObjID);
                deadObj.OnDeath(OnDeathAnimationEnd);
            }
            return;
        }
    }
    private void OnDeathAnimationEnd()
Main/System/Battle/BattleObject/BattleObject.cs
@@ -328,6 +328,7 @@
        //  多一个zzz的一个特效
        motionBase.PlayAnimation(MotionName.idle, true);
        heroRectTrans.anchoredPosition = Vector2.zero;
    }
    public void PushDropItems(BattleDrops _battleDrops)
Main/System/Battle/BattleObject/BattleObjectFactory.cs
@@ -47,13 +47,13 @@
        Debug.LogError("2 BattleObjectFactory.CreateBattleObject: Creating BattleObject for " + teamHero.ObjID + " at position " + teamHero.positionNum);
        float finalScaleRate = modelScaleRate * teamHero.modelScale;
        skeletonGraphic.skeletonDataAsset = skeletonDataAsset;
        skeletonGraphic.Initialize(true);
        realGO.name = battleObject.ObjID.ToString();
        realGO.transform.localScale = new Vector3(finalScaleRate, finalScaleRate, finalScaleRate);
        (realGO.transform as RectTransform).anchoredPosition = Vector2.zero;
        battleObject.Init(realGO, teamHero, _Camp);
        Debug.LogError(realGO.name +  " /3 BattleObjectFactory.CreateBattleObject: Creating BattleObject for " + teamHero.ObjID + " at position " + teamHero.positionNum);
@@ -64,6 +64,7 @@
    public static void DestroyBattleObject(int key, BattleObject battleObj)
    {
        Debug.LogError("BattleObject destroy");
        battleObj.Destroy();
        battleObj = null;
    }
Main/System/Battle/BattleTweenMgr.cs
@@ -44,12 +44,12 @@
        }
        tweenList.Add(tween);
        tween.OnComplete(() =>
        {
        tween.onComplete += () =>
        {
            tweenList.Remove(tween);
        });
        };
        tween.Play();
        // tween.Play();
    }
    public void HaveRest()
Main/System/Battle/BattleUtility.cs
@@ -12,21 +12,27 @@
    public static TweenerCore<Vector2, Vector2, DG.Tweening.Plugins.Options.VectorOptions> MoveToTarget(RectTransform transform, RectTransform target, Vector2 offset, float duration, Action onComplete = null)
    {
        Vector3 targetWorldPos = target.TransformPoint(target.anchoredPosition + offset);
        // 1. 获取目标的最终 anchoredPosition(加上 offset)
        Vector2 targetAnchoredPos = target.anchoredPosition + offset;
        Vector3 worldAnchorPos = target.TransformPoint(targetAnchoredPos);
        RectTransform parentRect = transform.parent as RectTransform;
        Vector2 targetAnchoredPos;
        // 转换 target 的 anchoredPosition 到 sourceParent 的坐标系
        Vector2 localPoint;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            parentRect,
            RectTransformUtility.WorldToScreenPoint(null, targetWorldPos),
            null,
            out targetAnchoredPos);
            transform,
            RectTransformUtility.WorldToScreenPoint(CameraManager.uiCamera, worldAnchorPos),
            CameraManager.uiCamera,
            out localPoint);
        // 3. DOTween 移动
        return transform.DOAnchorPos(targetAnchoredPos, duration)
            .SetEase(Ease.Linear)
            .OnComplete(() => onComplete?.Invoke());
        var tween = transform.DOAnchorPos(localPoint, duration).SetEase(Ease.Linear);
        tween.onComplete += () =>
        {
            onComplete?.Invoke();
        };
        return tween;
    }
    public static string DisplayDamageNum(long num, int attackType)
@@ -75,4 +81,81 @@
        return config.nums[_num - 48];
    }
    /// <summary>
    /// 保证所有分配项加起来等于totalDamage,避免因整除导致的误差
    /// </summary>
    public static List<long> DivideDamageToList(int[] damageDivide, long totalDamage)
    {
        List<long> fixedDamageList = new List<long>();
        long assigned = 0;
        int count = damageDivide.Length;
        for (int i = 0; i < count; i++)
        {
            long damage;
            if (i == count - 1)
            {
                // 最后一个分配项修正为剩余
                damage = totalDamage - assigned;
            }
            else
            {
                damage = (totalDamage * damageDivide[i] + 5000) / 10000; // 四舍五入
                assigned += damage;
            }
            fixedDamageList.Add(damage);
        }
        return fixedDamageList;
    }
    public static List<HB422_tagMCTurnFightObjDead> FindDeadPack(List<GameNetPackBasic> packList)
    {
        List<HB422_tagMCTurnFightObjDead> deadPacks = new List<HB422_tagMCTurnFightObjDead>();
        for (int i = 0; i < packList.Count; i++)
        {
            var pack = packList[i];
            //    寻找死亡包 找到死亡包之后要找掉落包 不能超过技能包
            if (pack is HB422_tagMCTurnFightObjDead)
            {
                var deadPack = pack as HB422_tagMCTurnFightObjDead;
                deadPacks.Add(deadPack);
            }
            else if (pack is CustomHB426CombinePack)
            {
                //    找死亡包不要越过技能包
                var combinePack = pack as CustomHB426CombinePack;
                if (combinePack.startTag.Tag.StartsWith("Skill_"))
                {
                    break;
                }
            }
        }
        return deadPacks;
    }
    public static List<HB423_tagMCTurnFightObjReborn> FindRebornPack(List<GameNetPackBasic> packList)
    {
        List<HB423_tagMCTurnFightObjReborn> rebornPack = new List<HB423_tagMCTurnFightObjReborn>();
        for (int i = 0; i < packList.Count; i++)
        {
            var pack = packList[i];
            //    寻找死亡包 找到死亡包之后要找掉落包 不能超过技能包
            if (pack is HB423_tagMCTurnFightObjReborn)
            {
                var deadPack = pack as HB423_tagMCTurnFightObjReborn;
                rebornPack.Add(deadPack);
            }
            else if (pack is CustomHB426CombinePack)
            {
                //    找死亡包不要越过技能包
                var combinePack = pack as CustomHB426CombinePack;
                if (combinePack.startTag.Tag.StartsWith("Skill_"))
                {
                    break;
                }
            }
        }
        return rebornPack;
    }
}
Main/System/Battle/Motion/MotionBase.cs
@@ -10,7 +10,7 @@
/// </summary>
public class MotionBase
{
    public static float MotionTimeScale = 2f;
    public static float MotionTimeScale = 1f;
    public static List<string> AttackMotionList = new List<string>
    {
@@ -133,88 +133,143 @@
        return currentTrackEntry;
    }
    public Spine.TrackEntry PlaySkillAnimation(SkillConfig skillConfig, Action onComplete = null, Action onBeginPhaseEnd = null, Action onActivePhaseEnd = null)
    public Spine.TrackEntry PlaySkillAnimation(SkillConfig skillConfig, SkillBase skillBase, Action _onComplete = null)
    {
        // 参数校验
        if (skillConfig == null)
        {
            Debug.LogError("技能配置为空,无法播放技能动画");
            return null;
        }
        if (spineAnimationState == null || skeleton == null)
        {
            Debug.LogError("SkeletonGraphic或AnimationState未初始化,无法播放技能动画");
            return null;
        }
        return PlayAnimation(skillConfig.SkillMotionName, skillConfig.StartupFrames, skillConfig.ActiveFrames, skillConfig.LoopCount,
            onComplete, onBeginPhaseEnd, onActivePhaseEnd);
    }
        // 获取动画
        Spine.Animation anim = skeleton.Data.FindAnimation(skillConfig.SkillMotionName);
        if (anim == null)
        {
            Debug.LogError($"找不到动画: {skillConfig.SkillMotionName}");
            return null;
        }
    public virtual Spine.TrackEntry PlayAnimation(
            string animationName,
            int loopBeginFrame,
            int loopEndFrame,
            int loopTimes,
            Action _onComplete = null,
            Action onBeginPhaseEnd = null,    // 前摇结束回调
            Action onActivePhaseEnd = null    // 中摇结束回调
        )
    {
        if (spineAnimationState == null || skeleton == null) return null;
        var anim = skeleton.Data.FindAnimation(animationName);
        if (anim == null) return null;
        // 关键帧参数
        float fps = BattleConst.skillMotionFps;
        float beginTime = loopBeginFrame / fps;
        float endTime = loopEndFrame / fps;
        float middleBeginTime = skillConfig.StartupFrames / fps;
        int loopCount = skillConfig.LoopCount;
        int[] activeFrames = skillConfig.ActiveFrames;
        int activeFrameCount = activeFrames.Length;
        float recoveryFrameTime = skillConfig.RecoveryFrames / fps;
        currentTrackEntry = spineAnimationState.SetAnimation(0, anim, false);
        // 播放动画
        var skillTrackEntry = spineAnimationState.SetAnimation(0, anim, false);
        currentTrackEntry = skillTrackEntry;
        // 事件状态
        int curLoop = 0;
        bool finished = false;
        bool isFinish = false;
        bool beginPhaseTriggered = false;
        bool finalFrameStarted = false;
        bool finalFrameEnded = false;
        bool middleFrameStarted = false;
        bool[] triggeredActiveFrame = new bool[activeFrameCount];
        // 技能开始
        skillBase.OnSkillStart();
        // 动画帧更新处理
        Spine.Unity.UpdateBonesDelegate updateLocalHandler = null;
        updateLocalHandler = (ISkeletonAnimation animated) =>
        {
            if (finished) return;
            var entry = currentTrackEntry;
            if (entry == null || entry.Animation != anim)
            {
                skeletonGraphic.UpdateLocal -= updateLocalHandler;
                return;
            }
            if (isFinish) return;
            float trackTime = skillTrackEntry.TrackTime;
            // 前摇结束(只触发一次)
            if (!beginPhaseTriggered && entry.TrackTime >= beginTime)
            if (!beginPhaseTriggered && trackTime >= middleBeginTime && curLoop == 0)
            {
                beginPhaseTriggered = true;
                onBeginPhaseEnd?.Invoke();
                skillBase.OnStartSkillFrameEnd();
            }
            // 中摇结束(每次到endTime都触发)
            if (entry.TrackTime >= endTime)
            // 中摇开始(每轮loop的开始,只触发一次)
            if (!middleFrameStarted && trackTime >= middleBeginTime && curLoop < loopCount)
            {
                onActivePhaseEnd?.Invoke();
                middleFrameStarted = true;
                skillBase.OnMiddleFrameStart(curLoop);
            }
                curLoop++;
                if (curLoop >= loopTimes)
            // 多段攻击帧触发
            for (int hitIndex = 0; hitIndex < activeFrameCount; hitIndex++)
            {
                float activeFrameTime = activeFrames[hitIndex] / fps;
                if (!triggeredActiveFrame[hitIndex] && trackTime >= activeFrameTime)
                {
                    finished = true;
                    skeletonGraphic.UpdateLocal -= updateLocalHandler;
                    _onComplete?.Invoke();
                    return;
                    skillBase.OnMiddleFrameEnd(curLoop, hitIndex);
                    triggeredActiveFrame[hitIndex] = true;
                }
                entry.TrackTime = beginTime;
                beginPhaseTriggered = false; // 重置,下一轮前摇可再次触发
            }
            // 判断是否所有activeFrame都已触发,准备进入下一轮loop
            bool allTriggered = true;
            for (int i = 0; i < activeFrameCount; i++)
            {
                if (!triggeredActiveFrame[i])
                {
                    allTriggered = false;
                    break;
                }
            }
            // 进入下一轮loop
            if (allTriggered && curLoop < loopCount)
            {
                curLoop++;
                Array.Clear(triggeredActiveFrame, 0, activeFrameCount);
                middleFrameStarted = false;
                if (curLoop < loopCount)
                {
                    // 重新设置到第一次的中摇时间
                    skillTrackEntry.TrackTime = middleBeginTime;
                    beginPhaseTriggered = false;
                }
                else
                {
                    finalFrameStarted = false;
                    finalFrameEnded = false;
                    // 收尾阶段由后续逻辑处理
                }
            }
            // 收尾阶段:OnFinalFrameStart 和 OnFinalFrameEnd
            if (curLoop >= loopCount)
            {
                if (!finalFrameStarted && trackTime >= recoveryFrameTime)
                {
                    finalFrameStarted = true;
                    skillBase.OnFinalFrameStart();
                }
                if (finalFrameStarted && !finalFrameEnded && trackTime >= recoveryFrameTime)
                {
                    finalFrameEnded = true;
                    skillBase.OnFinalFrameEnd();
                    skeletonGraphic.UpdateLocal -= updateLocalHandler;
                    isFinish = true;
                }
            }
        };
        skeletonGraphic.UpdateLocal += updateLocalHandler;
        if (_onComplete != null && currentTrackEntry != null)
        {
            trackEntryCompleteDict[currentTrackEntry] = _onComplete;
        }
        return currentTrackEntry;
        skeletonGraphic.UpdateLocal += updateLocalHandler;
        return skillTrackEntry;
    }
    
    /// <summary>
    /// 设置动画事件监听
Main/System/Battle/RecordPlayer/RecordPlayer.cs
@@ -7,8 +7,11 @@
    protected BattleField battleField;
    private Queue<RecordAction> recordActionQueue = new Queue<RecordAction>();
    protected RecordAction currentRecordAction;
    private bool isWaitingNextAction = false;
    private float waitTimer = 0f;
    private const float waitInterval = 1f;
    public void Init(BattleField _battleField)
    {
@@ -36,6 +39,21 @@
    public virtual void Run()
    {
        // 等待下一个action
        if (isWaitingNextAction)
        {
            waitTimer += Time.deltaTime;
            if (waitTimer >= waitInterval)
            {
                isWaitingNextAction = false;
                waitTimer = 0f;
            }
            else
            {
                return;
            }
        }
        if (currentRecordAction == null)
        {
            if (recordActionQueue.Count <= 0)
@@ -53,8 +71,10 @@
        if (currentRecordAction != null && currentRecordAction.IsFinished())
        {
            Debug.LogError("record action " + currentRecordAction.GetType() + " play finished");
            currentRecordAction = null;
            isWaitingNextAction = true;
            waitTimer = 0f;
            return;
        }
        if (currentRecordAction == null)
Main/System/Battle/Skill/DirectlyDamageSkill.cs
@@ -4,6 +4,7 @@
using System;
using System.Linq;
public class DirectlyDamageSkill : SkillBase
{
    protected SkillEffect skillEffect;
@@ -28,25 +29,8 @@
        base.Run();
    }
    //    技能动画播放完毕
    protected override void DoSkillLogic(Action _onComplete = null)
    {
        // if (skillConfig.EffectId > 0)
        // {
        //     // 播放技能特效
        //     caster.battleField.battleEffectMgr.PlayEffect(
        //         caster.ObjID,
        //         skillConfig.EffectId,
        //         caster.heroGo.transform
        //     );
        // }
        _onComplete?.Invoke();
    }
    //    前摇结束
    protected override void OnStartSkillFrame()
    //    技能开始
    public override void OnSkillStart()
    {
        skillEffect = SkillEffectFactory.CreateSkillEffect(
                caster,
@@ -59,10 +43,16 @@
        }
    }
    //    前摇结束
    public override void OnStartSkillFrameEnd()
    {
        base.OnStartSkillFrameEnd();
    }
    protected override void OnHitTargets(int _hitIndex, List<HB427_tagSCUseSkill.tagSCUseSkillHurt> hitList)
    {
        base.OnHitTargets(_hitIndex, hitList);
    }
    }
    protected override void OnHitEachTarget(BattleObject target, long totalDamage, List<long> damageList, ref HB427_tagSCUseSkill.tagSCUseSkillHurt hurt)
    {
@@ -70,14 +60,22 @@
    }
    //  中摇结束
    protected override void OnActiveSkillFrame()
    public override void OnMiddleFrameStart(int times)
    {
        base.OnMiddleFrameStart(times);
        skillEffect?.OnMiddleFrameStart(times);
    }
    //  中摇结束(命中帧)
    public override void OnMiddleFrameEnd(int times, int hitIndex)
    {
        skillEffect?.OnMiddleFrameEnd(times, hitIndex);
    }
    //    后摇结束
    protected override void OnEndSkillFrame()
    public override void OnFinalFrameEnd()
    {
    }
Main/System/Battle/Skill/DirectlyHealSkill.cs
@@ -28,25 +28,8 @@
        base.Run();
    }
    //    技能动画播放完毕
    protected override void DoSkillLogic(Action _onComplete = null)
    {
        // if (skillConfig.EffectId > 0)
        // {
        //     // 播放技能特效
        //     caster.battleField.battleEffectMgr.PlayEffect(
        //         caster.ObjID,
        //         skillConfig.EffectId,
        //         caster.heroGo.transform
        //     );
        // }
        _onComplete?.Invoke();
    }
    //    前摇结束
    protected override void OnStartSkillFrame()
    public override void OnStartSkillFrameEnd()
    {
        skillEffect = SkillEffectFactory.CreateSkillEffect(
                caster,
@@ -70,15 +53,15 @@
    }
    //  中摇结束
    protected override void OnActiveSkillFrame()
    {
    // //  中摇结束
    // protected override void OnActiveSkillFrame()
    // {
        
    }
    // }
    //    后摇结束
    protected override void OnEndSkillFrame()
    {
    // //    后摇结束
    // protected override void OnEndSkillFrame()
    // {
    }
    // }
}
Main/System/Battle/Skill/RebornSkill.cs
@@ -25,25 +25,8 @@
        base.Run();
    }
    //    技能动画播放完毕
    protected override void DoSkillLogic(Action _onComplete = null)
    {
        // if (skillConfig.EffectId > 0)
        // {
        //     // 播放技能特效
        //     caster.battleField.battleEffectMgr.PlayEffect(
        //         caster.ObjID,
        //         skillConfig.EffectId,
        //         caster.heroGo.transform
        //     );
        // }
        _onComplete?.Invoke();
    }
    //    前摇结束
    protected override void OnStartSkillFrame()
    public override void OnStartSkillFrameEnd()
    {
        skillEffect = SkillEffectFactory.CreateSkillEffect(
                caster,
@@ -66,16 +49,4 @@
        base.OnHitEachTarget(target, totalDamage, damageList, ref hurt);
    }
    //  中摇结束
    protected override void OnActiveSkillFrame()
    {
    }
    //    后摇结束
    protected override void OnEndSkillFrame()
    {
    }
}
Main/System/Battle/Skill/SkillBase.cs
@@ -9,6 +9,8 @@
public class SkillBase
{
    protected SkillEffect skillEffect;
    protected HB427_tagSCUseSkill tagUseSkillAttack;
    protected SkillConfig skillConfig;
@@ -68,7 +70,6 @@
    public void Pause()
    {
        pauseState = startCounting;
@@ -80,119 +81,231 @@
        startCounting = pauseState;
    }
    // 0·移动到距离目标n码,的距离释放(可配置,9999即原地释放,负数则是移动到人物背面,人物要转身)
    // 1·移动到距离阵容位置n码的距离(如2号位,5号位)释放(即战场中央此类)
    public virtual void Cast()
    {
        Debug.LogError(GetType().Name + " Skill Cast Start");
        //    高亮所有本次技能相关的目标
        HighLightAllTargets();
        //    距离配成负数要转身 TurnBack
        Debug.LogError(GetType().Name + " Skill CastMode : " + skillConfig.castMode);
        switch (skillConfig.castMode)
        {
            case SkillCastMode.Self:
                PlayCastAnimation(() => DoSkillLogic(OnSkillFinished));
                CastImpl();
                break;
            case SkillCastMode.Enemy:
                MoveToTarget(caster.GetEnemyCamp(), skillConfig, _onComplete: () => TurnBack(() => PlayCastAnimation(() => DoSkillLogic(() => { BackToOrigin(OnSkillFinished); }))));
                CastToEnemy();
                break;
            case SkillCastMode.Target:
                // 目标是敌方主目标
                if (tagUseSkillAttack.HurtCount <= 0)
                {
                    Debug.LogError("技能攻击包没有目标 HurtCount <= 0");
                    OnSkillFinished();
                    return;
                }
                var mainHurt = tagUseSkillAttack.HurtList[0];
                BattleObject mainTarget = battleField.battleObjMgr.GetBattleObject((int)mainHurt.ObjID);
                if (mainTarget == null)
                {
                    Debug.LogError("目标为空 mainTarget == null ObjID : " + mainHurt.ObjID);
                    OnSkillFinished();
                    return;
                }
                MoveToTarget(mainTarget.Camp, mainTarget, _onComplete: () => TurnBack(() => PlayCastAnimation(() => DoSkillLogic(() => { BackToOrigin(OnSkillFinished); }))));
                CastToTarget();
                break;
            case SkillCastMode.Allies:
                MoveToTarget(caster.Camp, skillConfig, _onComplete: () => TurnBack(() => PlayCastAnimation(() => DoSkillLogic(() => { BackToOrigin(OnSkillFinished); }))));
                CastToAllies();
                break;
            // case SkillCastMode.DashCast:
            //     DashToTarget(() => BackToOrigin(OnSkillFinished));
            //     break;
            default:
                Debug.LogError("暂时不支持其他的方式释放 有需求请联系策划" + skillConfig.SkillID);
                Debug.LogError("暂时不支持其他的方式释放 有需求请联系策划 技能id:" + skillConfig.SkillID + " cast position " + skillConfig.CastPosition);
                OnSkillFinished();
                break;
        }
    }
    //    这里其实是技能后摇结束的地方
    protected virtual void DoSkillLogic(Action _onComplete = null)
    protected void MoveToTarget(RectTransform target, Vector2 offset, float duration, Action onComplete = null)
    {
    }
    protected TrackEntry PlayCastAnimation(Action onComplete = null)
    {
        // 播放施法动作
        return caster.motionBase.PlaySkillAnimation(skillConfig, onComplete,
                OnStartSkillFrame,//攻击前摇结束
                OnActiveSkillFrame);//攻击中摇结束
    }
    public void MoveToTarget(BattleCamp camp, BattleObject target, float duration = 0.2f, Action _onComplete = null)
    {
        targetNode = battleField.GetTeamNode(camp, target);
        Vector2 offset = new Vector2(skillConfig.CastDistance, 0);
        RectTransform selfRect = caster.heroRectTrans;
        RectTransform targetRect = targetNode;
        var tweener = BattleUtility.MoveToTarget(selfRect, targetRect, offset, duration, _onComplete);
        caster.motionBase.PlayAnimation(MotionName.run, true);
        var tweener = BattleUtility.MoveToTarget(caster.heroRectTrans, target, offset, duration, () =>
        {
            caster.motionBase.PlayAnimation(MotionName.idle, true);
            onComplete?.Invoke();
        });
        battleField.battleTweenMgr.OnPlayTween(tweener);
    }
    public void MoveToTarget(BattleCamp camp, SkillConfig skillCfg, float duration = 0.2f, Action _onComplete = null)
    {
        targetNode = battleField.GetTeamNode(camp, skillCfg);
        Vector2 offset = new Vector2(skillConfig.CastDistance, 0);
        RectTransform selfRect = caster.heroRectTrans;
        RectTransform targetRect = targetNode;
        var tweener = BattleUtility.MoveToTarget(selfRect, targetRect, offset, duration, _onComplete);
        battleField.battleTweenMgr.OnPlayTween(tweener);
    }
    public void TurnBack(Action _onComplete)
    protected void TurnBack(Action _onComplete, float forward)
    {
        if (skillConfig.CastDistance < 0)
        {
            //    转身
            caster.heroGo.transform.localScale = new Vector3(-1, 1, 1);
            Vector3 scale = caster.heroGo.transform.localScale;
            scale.x = Mathf.Abs(scale.x) * forward;
            caster.heroGo.transform.localScale = scale;
        }
        _onComplete?.Invoke();
    }
    public void BackToOrigin(Action _onComplete = null)
    protected void CastToEnemy()
    {
        RectTransform selfRect = caster.heroRectTrans;
        Vector2 targetAnchoredPos = Vector2.zero;
        var tween = selfRect.DOAnchorPos(targetAnchoredPos, 0.2f)
            .SetEase(Ease.Linear)
            .OnComplete(() =>
            {
                //    转成正确方向
                caster.heroGo.transform.localScale = Vector3.one;
                _onComplete?.Invoke();
            });
        const float moveTime = 0.5f;
        battleField.battleTweenMgr.OnPlayTween(tween);
        RectTransform target = battleField.GetTeamNode(caster.GetEnemyCamp(), skillConfig);
        MoveToTarget(target, new Vector2(skillConfig.CastDistance, 0), moveTime, () =>
        {
            //    到位置转身(不一定非要转身 但是流程要写)
            TurnBack(() =>
            {
                //    到达目标位置
                CastImpl(() =>
                {
                    TurnBack(
                        () =>
                        {
                            //    回到原来的位置
                            MoveToTarget(battleField.GetTeamNode(caster.Camp, caster.teamHero.positionNum), Vector2.zero, moveTime, () =>
                            {
                                TurnBack(null, 1f);
                                caster.motionBase.PlayAnimation(MotionName.idle, true);
                            });
                        }
                    , -1f);
                });
            }, -1f);
        });
    }
    protected void CastToTarget()
    {
        // 目标是敌方主目标
        if (tagUseSkillAttack.HurtCount <= 0)
        {
            Debug.LogError("技能攻击包没有目标 HurtCount <= 0");
            OnSkillFinished();
            return;
        }
        var mainHurt = tagUseSkillAttack.HurtList[0];
        BattleObject mainTarget = battleField.battleObjMgr.GetBattleObject((int)mainHurt.ObjID);
        if (mainTarget == null)
        {
            Debug.LogError("目标为空 mainTarget == null ObjID : " + mainHurt.ObjID);
            OnSkillFinished();
            return;
        }
        // MoveToTarget(mainTarget.Camp, mainTarget, _onComplete: () => TurnBack(() => CastImpl(() => DoSkillLogic(() => { BackToOrigin(OnSkillFinished); }))));
    }
    protected void CastToAllies()
    {
        // MoveToTarget(caster.Camp, skillConfig, _onComplete: () => TurnBack(() => CastImpl(() => DoSkillLogic(() => { BackToOrigin(OnSkillFinished); }))));
    }
    protected TrackEntry CastImpl(Action onComplete = null)
    {
        // 播放施法动作
        return caster.motionBase.PlaySkillAnimation(skillConfig, this, onComplete);//攻击中摇结束
    }
    //    技能开始
    public virtual void OnSkillStart()
    {
        skillEffect = SkillEffectFactory.CreateSkillEffect(
                caster,
                skillConfig,
                tagUseSkillAttack
            );
        if (skillEffect != null)
        {
            skillEffect.Play(OnHitTargets);
        }
    }
    //    技能前摇帧结束
    public virtual void OnStartSkillFrameEnd()
    {
    }
    /// <summary>
    /// 中摇开始 times=第几次循环 从0开始
    /// </summary>
    /// <param name="times"></param>
    public virtual void OnMiddleFrameStart(int times)
    {
    }
    public virtual void OnMiddleFrameEnd(int times, int hitIndex)
    {
    }
    /// <summary>
    /// 后摇开始
    /// </summary>
    public virtual void OnFinalFrameStart()
    {
    }
    /// <summary>
    /// 后摇结束
    /// </summary>
    public virtual void OnFinalFrameEnd()
    {
    }
    // public void MoveToTarget(BattleCamp camp, BattleObject target, float duration = 0.2f, Action _onComplete = null)
    // {
    //     targetNode = battleField.GetTeamNode(camp, target);
    //     Vector2 offset = new Vector2(skillConfig.CastDistance, 0);
    //     RectTransform selfRect = caster.heroRectTrans;
    //     RectTransform targetRect = targetNode;
    //     var tweener = BattleUtility.MoveToTarget(selfRect, targetRect, offset, duration, _onComplete);
    //     battleField.battleTweenMgr.OnPlayTween(tweener);
    // }
    // public void MoveToTarget(BattleCamp camp, SkillConfig skillCfg, float duration = 0.2f, Action _onComplete = null)
    // {
    //     targetNode = battleField.GetTeamNode(camp, skillCfg);
    //     Vector2 offset = new Vector2(skillConfig.CastDistance, 0);
    //     RectTransform selfRect = caster.heroRectTrans;
    //     RectTransform targetRect = targetNode;
    //     var tweener = BattleUtility.MoveToTarget(selfRect, targetRect, offset, duration, _onComplete);
    //     battleField.battleTweenMgr.OnPlayTween(tweener);
    // }
    // public void TurnBack(Action _onComplete)
    // {
    //     if (skillConfig.CastDistance < 0)
    //     {
    //         //    转身
    //         caster.heroGo.transform.localScale = new Vector3(-1, 1, 1);
    //     }
    //     _onComplete?.Invoke();
    // }
    // public void BackToOrigin(Action _onComplete = null)
    // {
    //     RectTransform selfRect = caster.heroRectTrans;
    //     Vector2 targetAnchoredPos = Vector2.zero;
    //     var tween = selfRect.DOAnchorPos(targetAnchoredPos, 0.2f)
    //         .SetEase(Ease.Linear);
    //     tween.onComplete += () =>
    //     {
    //         //    转成正确方向
    //         caster.heroGo.transform.localScale = Vector3.one;
    //         _onComplete?.Invoke();
    //     };
    //     battleField.battleTweenMgr.OnPlayTween(tween);
    // }
    protected void HighLightAllTargets()
    {
@@ -225,7 +338,7 @@
            long totalDamage = GeneralDefine.GetFactValue(hurt.HurtHP, hurt.HurtHPEx);
            // 保证所有分配项加起来等于totalDamage,避免因整除导致的误差
            List<long> damageList = DivideDamageToList(damageDivide, totalDamage);
            List<long> damageList = BattleUtility.DivideDamageToList(damageDivide, totalDamage);
            OnHitEachTarget(target, totalDamage, damageList, ref hurt);
        }
@@ -235,7 +348,7 @@
    protected void HandleDead()
    {
        var deadPackList = FindDeadPack();
        var deadPackList = BattleUtility.FindDeadPack(packList);
        CheckAfterDeadhPack();
        // 处理掉落包 提前distribute之后 PackManager才有掉落物 所以不跟assignexp一样distribute
@@ -276,13 +389,14 @@
        }
        // 分发死亡包
        battleField.OnObjsDead(deadPackList);
        foreach (var deadPack in deadPackList)
        {
            PackageRegedit.Distribute(deadPack);
            packList.Remove(deadPack);
        }
        deadPackList.Clear();
    }
    // 分配掉落
@@ -327,32 +441,7 @@
        return expAssign;
    }
    /// <summary>
    /// 保证所有分配项加起来等于totalDamage,避免因整除导致的误差
    /// </summary>
    protected List<long> DivideDamageToList(int[] damageDivide, long totalDamage)
    {
        List<long> fixedDamageList = new List<long>();
        long assigned = 0;
        int count = damageDivide.Length;
        for (int i = 0; i < count; i++)
        {
            long damage;
            if (i == count - 1)
            {
                // 最后一个分配项修正为剩余
                damage = totalDamage - assigned;
            }
            else
            {
                damage = (totalDamage * damageDivide[i] + 5000) / 10000; // 四舍五入
                assigned += damage;
            }
            fixedDamageList.Add(damage);
        }
        return fixedDamageList;
    }
    protected virtual void OnHitEachTarget(BattleObject target, long totalDamage, List<long> damageList, ref HB427_tagSCUseSkill.tagSCUseSkillHurt hurt)
    {
@@ -368,57 +457,6 @@
                target.heroGo.transform
            );
        }
    }
    protected HB423_tagMCTurnFightObjReborn FindRebornPack(BattleObject target)
    {
        HB423_tagMCTurnFightObjReborn rebornPack = null;
        for (int i = 0; i < packList.Count; i++)
        {
            var pack = packList[i];
            if (pack is HB423_tagMCTurnFightObjReborn)
            {
                rebornPack = pack as HB423_tagMCTurnFightObjReborn;
                if (rebornPack.ObjID == target.ObjID)
                {
                    return rebornPack;
                }
            }
            else if (pack is CustomHB426CombinePack)
            {
                var combinePack = pack as CustomHB426CombinePack;
                if (combinePack.startTag.Tag.StartsWith("Skill_"))
                {
                    break; // 找到技能包就不需要再处理了
                }
            }
        }
        return null;
    }
    protected List<HB422_tagMCTurnFightObjDead> FindDeadPack()
    {
        List<HB422_tagMCTurnFightObjDead> deadPacks = new List<HB422_tagMCTurnFightObjDead>();
        for (int i = 0; i < packList.Count; i++)
        {
            var pack = packList[i];
            //    寻找死亡包 找到死亡包之后要找掉落包 不能超过技能包
            if (pack is HB422_tagMCTurnFightObjDead)
            {
                var deadPack = pack as HB422_tagMCTurnFightObjDead;
                deadPacks.Add(deadPack);
            }
            else if (pack is CustomHB426CombinePack)
            {
                //    找死亡包不要越过技能包
                var combinePack = pack as CustomHB426CombinePack;
                if (combinePack.startTag.Tag.StartsWith("Skill_"))
                {
                    break;
                }
            }
        }
        return deadPacks;
    }
    protected void CheckAfterDeadhPack()
@@ -487,7 +525,8 @@
    public void OnSkillFinished()
    {
        while (packList.Count > 0)
        Debug.LogError(GetType().Name + " Skill Finished");
        if (packList.Count > 0)
        {
            var pack = packList[0];
            packList.RemoveAt(0);
@@ -506,21 +545,5 @@
        }
        isFinished = true;
    }
    protected virtual void OnActiveSkillFrame()
    {
    }
    protected virtual void OnStartSkillFrame()
    {
    }
    protected virtual void OnEndSkillFrame()
    {
    }
}
Main/System/Battle/SkillEffect/BulletCurve/BezierBulletCurve.cs
@@ -8,8 +8,9 @@
    private Vector2 end;
    private Vector2 control;
    public BezierBulletCurve(BattleObject caster, SkillConfig skillConfig, BattleEffectPlayer effectPlayer, RectTransform target, Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> onHit)
        : base(caster, skillConfig, effectPlayer, target, onHit) { }
    public BezierBulletCurve(BattleObject caster, SkillConfig skillConfig, BattleEffectPlayer effectPlayer,
        RectTransform target, HB427_tagSCUseSkill tagUseSkillAttack, Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> onHit)
        : base(caster, skillConfig, effectPlayer, target, tagUseSkillAttack, onHit) { }
    public override void Reset()
    {
@@ -34,7 +35,7 @@
        if (t >= 1f)
        {
            finished = true;
            onHit?.Invoke(0, null);
            onHit?.Invoke(0, hurts);
        }
    }
}
Main/System/Battle/SkillEffect/BulletCurve/BounceBulletCurve.cs
@@ -16,7 +16,7 @@
    public BounceBulletCurve(BattleObject caster, SkillConfig skillConfig, BattleEffectPlayer effectPlayer,
        RectTransform target, HB427_tagSCUseSkill tagUseSkillAttack, Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> onHit)
        : base(caster, skillConfig, effectPlayer, target, onHit)
        : base(caster, skillConfig, effectPlayer, target, tagUseSkillAttack, onHit)
    {
        this.hurtList = new List<HB427_tagSCUseSkill.tagSCUseSkillHurt>(tagUseSkillAttack.HurtList);
    }
Main/System/Battle/SkillEffect/BulletCurve/BulletCurve.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class BulletCurve
@@ -15,9 +16,9 @@
    protected float duration = 0f;
    protected float elapsed = 0f;
    public BulletCurve() { }
    protected List<HB427_tagSCUseSkill.tagSCUseSkillHurt> hurts = new List<HB427_tagSCUseSkill.tagSCUseSkillHurt>();
    public BulletCurve(BattleObject caster, SkillConfig skillConfig, BattleEffectPlayer bulletEffect, RectTransform target, Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> onHit)
    public BulletCurve(BattleObject caster, SkillConfig skillConfig, BattleEffectPlayer bulletEffect, RectTransform target, HB427_tagSCUseSkill tagUseSkillAttack, Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> onHit)
    {
        this.caster = caster;
        this.skillConfig = skillConfig;
@@ -25,6 +26,7 @@
        this.target = target;
        this.onHit = onHit;
        this.bulletTrans = bulletEffect.transform as RectTransform;
        this.hurts = tagUseSkillAttack.HurtList.ToList();
    }
    public virtual void Reset()
@@ -55,7 +57,7 @@
    protected virtual void ReachTarget()
    {
        finished = true;
        onHit?.Invoke(0, null);
        onHit?.Invoke(0, hurts);
        caster.battleField.battleEffectMgr.RemoveEffect(skillConfig.BulletEffectId, bulletEffect);
    }
Main/System/Battle/SkillEffect/BulletCurve/BulletCurveFactory.cs
@@ -21,15 +21,15 @@
        switch (skillConfig.BulletPath)
        {
            case 1: // 直线消失于目标
                return new StraightBulletCurve(caster, skillConfig, bulletEffect, target, onHit);
                return new StraightBulletCurve(caster, skillConfig, bulletEffect, target, tagUseSkillAttack, onHit);
            case 2: // 直线贯穿消失在屏幕外
                return new PenetrateBulletCurve(caster, skillConfig, bulletEffect, target, onHit);
                return new PenetrateBulletCurve(caster, skillConfig, bulletEffect, target, tagUseSkillAttack, onHit);
            case 3: // 抛物线弧线
                return new BezierBulletCurve(caster, skillConfig, bulletEffect, target, onHit);
                return new BezierBulletCurve(caster, skillConfig, bulletEffect, target, tagUseSkillAttack, onHit);
            case 4: // 弹射
                return new BounceBulletCurve(caster, skillConfig, bulletEffect, target, tagUseSkillAttack,onHit);
                return new BounceBulletCurve(caster, skillConfig, bulletEffect, target, tagUseSkillAttack, onHit);
            default:
                return new BulletCurve(caster, skillConfig, bulletEffect, target, onHit);
                return new BulletCurve(caster, skillConfig, bulletEffect, target, tagUseSkillAttack, onHit);
        }
    }
}
Main/System/Battle/SkillEffect/BulletCurve/PenetrateBulletCurve.cs
@@ -9,8 +9,8 @@
    private Vector2 outPos;     // 屏幕外延长点(本地坐标)
    private bool hitTriggered = false; // 是否已触发onHit
    public PenetrateBulletCurve(BattleObject caster, SkillConfig skillConfig, BattleEffectPlayer effectPlayer, RectTransform target, Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> onHit)
        : base(caster, skillConfig, effectPlayer, target, onHit) { }
    public PenetrateBulletCurve(BattleObject caster, SkillConfig skillConfig, BattleEffectPlayer effectPlayer, RectTransform target, HB427_tagSCUseSkill tagUseSkillAttack, Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> onHit)
        : base(caster, skillConfig, effectPlayer, target, tagUseSkillAttack, onHit) { }
    /// <summary>
    /// 初始化弹道参数
@@ -51,7 +51,7 @@
            if (!hitTriggered)
            {
                hitTriggered = true;
                onHit?.Invoke(0, null);
                onHit?.Invoke(0, hurts);
            }
        }
        bulletTrans.anchoredPosition = pos;
Main/System/Battle/SkillEffect/BulletCurve/StraightBulletCurve.cs
@@ -7,8 +7,8 @@
    private Vector2 start;
    private Vector2 end;
    public StraightBulletCurve(BattleObject caster, SkillConfig skillConfig, BattleEffectPlayer bulletEffect, RectTransform target, Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> onHit)
        : base(caster, skillConfig, bulletEffect, target, onHit) { }
    public StraightBulletCurve(BattleObject caster, SkillConfig skillConfig, BattleEffectPlayer bulletEffect, RectTransform target, HB427_tagSCUseSkill tagUseSkillAttack, Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> onHit)
        : base(caster, skillConfig, bulletEffect, target, tagUseSkillAttack, onHit) { }
    public override void Reset()
    {
@@ -32,7 +32,7 @@
        if (t >= 1f)
        {
            finished = true;
            onHit?.Invoke(0, null);
            onHit?.Invoke(0, hurts);
        }
    }
}
Main/System/Battle/SkillEffect/BulletSkillEffect.cs
@@ -23,6 +23,7 @@
    public override void Play(Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> _onHit)
    {
        base.Play(_onHit);
        //  弹射 另外的做法了
        if (skillConfig.effectType == SkillEffectType.Bullet && skillConfig.BulletPath == 4)
        {
@@ -33,14 +34,14 @@
                Debug.LogError("目标为空 target == null ObjId : " + hurt.ObjID);
                return;
            }
            ShotToTarget(targetObject, _onHit);
            ShotToTarget(targetObject);
        }
        //  普通的做法
        //  普通的做法 区分打向阵营或者打向个体
        else
        {
            Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> onHitFormation = (_hitIndex, _hurtList) =>
            {
                _onHit?.Invoke(_hitIndex, tagUseSkillAttack.HurtList.ToList());
                onHit?.Invoke(_hitIndex, tagUseSkillAttack.HurtList.ToList());
            };
            switch (skillConfig.castMode)
            {
@@ -63,7 +64,7 @@
                            continue;
                        }
                        ShotToTarget(target, _onHit);
                        ShotToTarget(target);
                    }
                    break;
                case SkillCastMode.Allies:
@@ -102,30 +103,48 @@
                }
                caster.battleField.battleEffectMgr.PlayEffect(targetObj.ObjID, skillConfig.ExplosionEffectId, targetObj.heroGo.transform);
                caster.battleField.battleEffectMgr.PlayEffect(targetObj.ObjID, skillConfig.ExplosionEffect2, targetObj.heroGo.transform);
            }
            // caster.battleField.battleEffectMgr.PlayEffect(caster.ObjID, skillConfig.ExplosionEffectId, target);
        });
        bulletCurves.Add(bulletCurve);
    }
    protected void ShotToTarget(BattleObject target, Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> _onHit)
    protected void ShotToTarget(BattleObject target)
    {
        BattleEffectPlayer effectPlayer = caster.battleField.battleEffectMgr.PlayEffect(caster.ObjID, skillConfig.BulletEffectId, caster.effectNode);
        BattleEffectPlayer effectPlayer = caster.battleField.battleEffectMgr.PlayEffect(caster.ObjID, skillConfig.BulletEffectId, caster.heroRectTrans);
        RectTransform effectTrans = effectPlayer.transform as RectTransform;
        var bulletCurve = BulletCurveFactory.CreateBulletCurve(caster, skillConfig, effectPlayer, target.heroRectTrans, tagUseSkillAttack, (index, hitList) =>
        {
            //  击中就销毁子弹 子弹不需要自动销毁 配置表里autoDestroy为false
            // 表现子弹飞行到目标位置
            onHit?.Invoke(index, hitList);
            // 击中就销毁子弹
            caster.battleField.battleEffectMgr.RemoveEffect(skillConfig.BulletEffectId, effectPlayer);
            _onHit?.Invoke(index, hitList);
            // 播放子弹爆炸特效
            foreach (var hurt in hitList)
            {
                BattleObject targetObj = caster.battleField.battleObjMgr.GetBattleObject((int)hurt.ObjID);
                if (targetObj == null)
                {
                    Debug.LogError("目标为空 target == null ObjId : " + hurt.ObjID);
                    continue;
                }
                caster.battleField.battleEffectMgr.PlayEffect(targetObj.ObjID, skillConfig.ExplosionEffectId, targetObj.heroGo.transform);
                caster.battleField.battleEffectMgr.PlayEffect(targetObj.ObjID, skillConfig.ExplosionEffect2, targetObj.heroGo.transform);
            }
        });
        bulletCurves.Add(bulletCurve);
    }
    public override void Run()
    {
        foreach (var bulletCurve in bulletCurves)
Main/System/Battle/SkillEffect/NormalSkillEffect.cs
@@ -14,75 +14,48 @@
    public NormalSkillEffect(SkillConfig _skillConfig, BattleObject _caster, HB427_tagSCUseSkill _tagUseSkillAttack)
        : base(_skillConfig, _caster, _tagUseSkillAttack)
    {
    }
    //  技能动作第一帧
    //  不用理会回调
    public override void Play(Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> _onHit)
    {
        // // 特效炸开在阵容的中间的回调
        // Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> onHitLineUpCenter = (_hitIndex, _hurtList) =>
        // {
        //     _onHit?.Invoke(_hitIndex, tagUseSkillAttack.HurtList.ToList());
        // };
        // if (skillConfig.effectAnchor == SkillEffectAnchor.Caster)
        // {
        //     CastInTarget(caster.heroGo.transform as RectTransform, onHitLineUpCenter);
        // }
        // else if (skillConfig.effectAnchor == SkillEffectAnchor.Target)
        // {
        //     if (tagUseSkillAttack == null || tagUseSkillAttack.HurtList.Length <= 0)
        //     {
        //         Debug.LogError("没有目标 tagUseSkillAttack.HurtList.Length <= 0");
        //         _onHit?.Invoke(0, default);
        //         return;
        //     }
        //     foreach (var hurt in tagUseSkillAttack.HurtList)
        //     {
        //         BattleObject target = caster.battleField.battleObjMgr.GetBattleObject((int)hurt.ObjID);
        //         if (target == null)
        //         {
        //             Debug.LogError("特效目标为空 target == null ObjId : " + hurt.ObjID);
        //             continue;
        //         }
        //         CastInTarget(target.heroGo.transform as RectTransform, (index, list) => _onHit(0, new List<HB427_tagSCUseSkill.tagSCUseSkillHurt>() { hurt }));
        //     }
        // }
        // else if (skillConfig.effectAnchor == SkillEffectAnchor.AlliesCenter)
        // {
        //     CastInTarget(caster.battleField.GetTeamNode(caster.Camp), onHitLineUpCenter);
        // }
        // else if (skillConfig.effectAnchor == SkillEffectAnchor.EnemiesCenter)
        // {
        //     CastInTarget(caster.battleField.GetTeamNode(caster.Camp == BattleCamp.Blue ? BattleCamp.Red : BattleCamp.Blue), onHitLineUpCenter);
        // }
        // else
        // {
        //     Debug.LogError("未知的技能特效锚点类型: " + skillConfig.effectAnchor);
        // }
        // 播放技能特效
        BattleEffectPlayer battleEffectPlayer = caster.battleField.battleEffectMgr.PlayEffect(
            caster.ObjID,
            skillConfig.EffectId,
            caster.heroGo.transform
        );
    }
    protected void CastInTarget(RectTransform target, Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> _onHit)
    {
        // EffectPlayer effectPlayer = caster.battleField.battleEffectMgr.PlayEffect(caster.ObjID, skillConfig.EffectId, caster.effectNode);
        // RectTransform effectTrans = effectPlayer.transform as RectTransform;
        // _onHit?.Invoke(0, null);
        // //  销毁自身上的特效应该是等特效播放完毕之后
        // // caster.battleField.battleEffectMgr.RemoveEffect(skillConfig.EffectId, effectPlayer);
        // //  播放受击特效
        // caster.battleField.battleEffectMgr.PlayEffect(caster.ObjID, skillConfig.ExplotionEffectId, target);
    }
    public override void Run()
    {
        base.Run();
    }
    public override void OnMiddleFrameEnd(int times, int hitIndex)
    {
        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;
            }
            caster.battleField.battleEffectMgr.PlayEffect(target.ObjID, skillConfig.ExplosionEffectId, target.heroGo.transform);
            caster.battleField.battleEffectMgr.PlayEffect(target.ObjID, skillConfig.ExplosionEffect2, target.heroGo.transform);
        }
        onHit?.Invoke(hitIndex, tagUseSkillAttack.HurtList.ToList());
    }
    public override void OnMiddleFrameStart(int times)
    {
    }
}
Main/System/Battle/SkillEffect/SkillEffect.cs
@@ -8,6 +8,8 @@
    protected BattleObject caster;
    protected HB427_tagSCUseSkill tagUseSkillAttack;// 目标列表
    protected Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> onHit;
    public SkillEffect(SkillConfig _skillConfig, BattleObject _caster, HB427_tagSCUseSkill _tagUseSkillAttack)
    {
        skillConfig = _skillConfig;
@@ -18,11 +20,21 @@
    //  
    public virtual void Play(Action<int, List<HB427_tagSCUseSkill.tagSCUseSkillHurt>> _onHit)
    {
        onHit = _onHit;
    }
    public virtual void Run()
    {
    }
    public virtual void OnMiddleFrameEnd(int times, int hitIndex)
    {
    }
    public virtual void OnMiddleFrameStart(int times)
    {
    }
}
Main/System/Battle/SkillEffect/SkillEffectFactory.cs
@@ -6,20 +6,20 @@
{
    public static SkillEffect CreateSkillEffect(BattleObject caster, SkillConfig skillConfig, HB427_tagSCUseSkill tagUseSkillAttack)
    {
        // switch (skillConfig.effectType)
        // {
        //     case SkillEffectType.Bullet:
        //         return new BulletSkillEffect(skillConfig, caster, tagUseSkillAttack);
        //     case SkillEffectType.Direct:
        //         return new NormalSkillEffect(skillConfig, caster, tagUseSkillAttack);
        //     // case SkillEffectType.BuffEffect:
        //     //     return new BuffSkillEffect(skillConfig, caster, targets);
        //     // case SkillEffectType.StageEffect:
        //     //     return new StageSkillEffect(skillConfig, caster, targets);
        //     default:
        //         UnityEngine.Debug.LogError("Unknown Skill Effect Type");
        //         break;
        // }
        switch (skillConfig.effectType)
        {
            case SkillEffectType.Bullet:
                return new BulletSkillEffect(skillConfig, caster, tagUseSkillAttack);
            case SkillEffectType.Direct:
                return new NormalSkillEffect(skillConfig, caster, tagUseSkillAttack);
            // case SkillEffectType.BuffEffect:
            //     return new BuffSkillEffect(skillConfig, caster, targets);
            // case SkillEffectType.StageEffect:
            //     return new StageSkillEffect(skillConfig, caster, targets);
            default:
                UnityEngine.Debug.LogError("Unknown Skill Effect Type");
                break;
        }
        return null;
    }
}
Main/System/Team/TeamBase.cs
@@ -53,8 +53,8 @@
            {
                var fightObj = lineUp.ObjList[i];
                TeamHero hero = new TeamHero(fightObj, this);
                tempHeroes[fightObj.PosNum] = hero;
                serverHeroes[fightObj.PosNum] = hero;
                tempHeroes[hero.positionNum] = hero;
                serverHeroes[hero.positionNum] = hero;
            }
            else
            {
Main/Utility/EffectPenetrationBlocker.cs
@@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
using Spine.Unity;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 特效穿透阻挡器
@@ -10,26 +14,28 @@
{
    [Tooltip("是否在UI层级下自动调整排序顺序")]
    public bool autoAdjustSorting = true;
    [Tooltip("特效渲染器,如果为空则自动获取")]
    public List<Renderer> effectRenderers = new List<Renderer>();
    [Tooltip("自定义排序顺序,仅在不自动调整时有效")]
    public int customSortingOrder = 0;
    [Tooltip("自定义排序层,为空则使用默认UI层")]
    public string customSortingLayer = "UI";
    [Tooltip("特效所属的Canvas,如果为空则查找父级Canvas")]
    public Canvas parentCanvas;
    [Tooltip("特效在Canvas中的排序偏移量")]
    public int sortingOrderOffset = 1;
    public int canvasSortingOrder = 0;
    public int renderQueue = 3000;//默认值
    public Action<string, int> onSortingChanged;
    private void Awake()
    {
        transform.GetComponentsInChildren(true, effectRenderers);
@@ -56,7 +62,7 @@
            ApplySortingSettings(customSortingOrder, customSortingLayer);
        }
    }
    private void OnEnable()
    {
        if (autoAdjustSorting)
@@ -64,14 +70,14 @@
            UpdateSortingOrder();
        }
    }
    /// <summary>
    /// 更新排序顺序,确保特效显示在当前UI界面上方
    /// </summary>
    public void UpdateSortingOrder()
    {
        if (!autoAdjustSorting) return;
        // 获取父级Canvas的排序顺序
        if (parentCanvas != null)
        {
@@ -86,7 +92,7 @@
        // 将特效的排序顺序设置为Canvas排序顺序加上偏移量
        ApplySortingSettings(canvasSortingOrder + sortingOrderOffset, customSortingLayer);
    }
    /// <summary>
    /// 应用排序设置到所有渲染器和粒子系统
    /// </summary>
@@ -108,8 +114,10 @@
                }
            }
        }
        onSortingChanged?.Invoke(sortingLayer, sortingOrder);
    }
    /// <summary>
    /// 手动设置排序顺序
    /// </summary>
@@ -120,11 +128,11 @@
        {
            customSortingLayer = sortingLayer;
        }
        autoAdjustSorting = false;
        ApplySortingSettings(customSortingOrder, customSortingLayer);
    }
    /// <summary>
    /// 设置父级Canvas
    /// </summary>
@@ -160,6 +168,7 @@
        }
    }
#if UNITY_EDITOR
    [ContextMenu("Apply")]
    public void Apply()
Main/Utility/UniTaskExtension.cs
@@ -34,4 +34,25 @@
    {
        DelayFrameInternal(1, action);
    }
    public static void DelayTime(this GameObject go, float time, Action action)
    {
        DelayTimeInternal(time, action);
    }
    public static void DelayTime(this Component cmp, float time, Action action)
    {
        DelayTimeInternal(time, action);
    }
    private async static UniTask DelayTimeInternal(float time, Action action)
    {
        if (time <= 0f)
        {
            action?.Invoke();
            return;
        }
        await UniTask.Delay(TimeSpan.FromSeconds(time));
        action?.Invoke();
    }
}