yyl
3 天以前 ed9bf64c03bf5fee5e115645de5a975baaa9041d
125 战斗 修改死亡表现 带动作的子技能同时触发导致的卡死
13个文件已修改
774 ■■■■■ 已修改文件
Main/Core/NetworkPackage/DTCFile/ServerPack/HB4_FightDefine/DTCB430_tagSCTurnFightReport.cs 271 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleField/BattleField.cs 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleField/RecordActions/BattleEndAction.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleField/RecordActions/BattleStartAction.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleField/RecordActions/DeathRecordAction.cs 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleField/RecordActions/RebornRecordAction.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleField/RecordActions/RoundChangeAction.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleField/RecordActions/SkillRecordAction.cs 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleObject/BattleObject.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Motion/MotionBase.cs 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/RecordPlayer/RecordAction.cs 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/RecordPlayer/RecordPlayer.cs 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Skill/SkillBase.cs 122 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Core/NetworkPackage/DTCFile/ServerPack/HB4_FightDefine/DTCB430_tagSCTurnFightReport.cs
@@ -86,6 +86,10 @@
                }
            }
#if UNITY_EDITOR
            //  解析所有vPackList里的每个字段(深度)并且输出到Application.dataPath + "/../BattleReport/PackageDetailAnalysis_时间戳.txt文件里
            string originPack = string.Empty;
            BattleField battleField = BattleManager.Instance.GetBattleField(guid);
@@ -172,7 +176,23 @@
            }
#endif
#region Start Print Pack List Detail
            if (Launch.Instance.isOpenSkillLogFile)
            {
                try
                {
                    string detailAnalysis = PrintPackageDetailAnalysis(vPackList, guid);
                    string filePath = Application.dataPath + "/../BattleReport/PackageDetailAnalysis_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".txt";
                    System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(filePath));
                    System.IO.File.WriteAllText(filePath, detailAnalysis);
                    Debug.Log("包详细分析已保存到: " + filePath);
                }
                catch (Exception e)
                {
                    Debug.LogError("保存包详细分析失败: " + e.Message);
                }
            }
#endregion
            for (int i = 0; i < vPackList.Count; i++)
            {
@@ -571,4 +591,253 @@
        }
        return returnList;
    }
#if UNITY_EDITOR
    /// <summary>
    /// 深度解析包列表中的所有字段
    /// </summary>
    private string PrintPackageDetailAnalysis(List<GameNetPackBasic> packList, string guid)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine("=====================================================");
        sb.AppendLine("战报包详细分析 - " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        sb.AppendLine("GUID: " + guid);
        sb.AppendLine("包总数: " + packList.Count);
        sb.AppendLine("=====================================================\n");
        BattleField battleField = BattleManager.Instance.GetBattleField(guid);
        for (int i = 0; i < packList.Count; i++)
        {
            sb.AppendLine($"【包 #{i}】 ==========================================");
            PrintObjectDetail(sb, packList[i], 0, battleField, new HashSet<object>());
            sb.AppendLine();
        }
        return sb.ToString();
    }
    /// <summary>
    /// 递归打印对象的所有字段和属性
    /// </summary>
    private void PrintObjectDetail(StringBuilder sb, object obj, int indent, BattleField battleField, HashSet<object> visitedObjects)
    {
        if (obj == null)
        {
            sb.AppendLine(GetIndent(indent) + "null");
            return;
        }
        Type type = obj.GetType();
        string indentStr = GetIndent(indent);
        // 防止循环引用
        if (visitedObjects.Contains(obj))
        {
            sb.AppendLine(indentStr + $"<循环引用: {type.Name}>");
            return;
        }
        // 基本类型和字符串直接输出
        if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal))
        {
            sb.AppendLine(indentStr + obj.ToString());
            return;
        }
        // 枚举类型
        if (type.IsEnum)
        {
            sb.AppendLine(indentStr + $"{obj} ({(int)obj})");
            return;
        }
        // 添加到已访问集合(仅对引用类型)
        if (!type.IsValueType)
        {
            visitedObjects.Add(obj);
        }
        // 输出类型名
        sb.AppendLine(indentStr + $"[{type.Name}]");
        // 处理数组
        if (type.IsArray)
        {
            Array array = obj as Array;
            sb.AppendLine(indentStr + $"Length: {array.Length}");
            if (array.Length > 0)
            {
                for (int i = 0; i < array.Length; i++)
                {
                    sb.AppendLine(indentStr + $"  [{i}]:");
                    PrintObjectDetail(sb, array.GetValue(i), indent + 2, battleField, visitedObjects);
                }
            }
            return;
        }
        // 处理List、HashSet等集合
        if (obj is System.Collections.IEnumerable && !(obj is string))
        {
            var enumerable = obj as System.Collections.IEnumerable;
            int count = 0;
            var enumerator = enumerable.GetEnumerator();
            while (enumerator.MoveNext()) count++;
            sb.AppendLine(indentStr + $"Count: {count}");
            if (count > 0)
            {
                int index = 0;
                foreach (var item in enumerable)
                {
                    sb.AppendLine(indentStr + $"  [{index}]:");
                    PrintObjectDetail(sb, item, indent + 2, battleField, visitedObjects);
                    index++;
                }
            }
            return;
        }
        // 获取所有公共字段
        var fields = type.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
        foreach (var field in fields)
        {
            // 跳过Unity和System库的复杂类型(但保留基本类型)
            Type fieldType = field.FieldType;
            if (ShouldSkipType(fieldType))
            {
                continue;
            }
            try
            {
                object value = field.GetValue(obj);
                // 特殊处理:显示ObjID对应的武将名字
                if (field.Name == "ObjID" && value is uint objId && battleField != null)
                {
                    var battleObj = battleField.battleObjMgr.GetBattleObject((int)objId);
                    string heroName = battleObj?.teamHero?.name ?? "Unknown";
                    sb.AppendLine(indentStr + $"  {field.Name} ({fieldType.Name}): {value} [{heroName}]");
                }
                // 特殊处理:显示SkillID对应的技能名字
                else if (field.Name == "SkillID" && value is uint skillId)
                {
                    string skillName = SkillConfig.Get((int)skillId)?.SkillName ?? "Unknown";
                    sb.AppendLine(indentStr + $"  {field.Name} ({fieldType.Name}): {value} [{skillName}]");
                }
                // 特殊处理:显示HeroID对应的英雄名字
                else if (field.Name == "HeroID" && value is uint heroId)
                {
                    string heroName = HeroConfig.Get((int)heroId)?.Name ?? "Unknown";
                    sb.AppendLine(indentStr + $"  {field.Name} ({fieldType.Name}): {value} [{heroName}]");
                }
                else if (IsSimpleType(fieldType))
                {
                    sb.AppendLine(indentStr + $"  {field.Name} ({fieldType.Name}): {value}");
                }
                else
                {
                    sb.AppendLine(indentStr + $"  {field.Name} ({fieldType.Name}):");
                    PrintObjectDetail(sb, value, indent + 2, battleField, visitedObjects);
                }
            }
            catch (Exception e)
            {
                sb.AppendLine(indentStr + $"  {field.Name}: <解析错误: {e.Message}>");
            }
        }
        // 获取所有公共属性
        var properties = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
        foreach (var prop in properties)
        {
            // 跳过索引器
            if (prop.GetIndexParameters().Length > 0)
            {
                continue;
            }
            // 跳过Unity和System库的复杂类型
            Type propType = prop.PropertyType;
            if (ShouldSkipType(propType))
            {
                continue;
            }
            // 只读取有getter的属性
            if (!prop.CanRead)
            {
                continue;
            }
            try
            {
                object value = prop.GetValue(obj, null);
                if (IsSimpleType(propType))
                {
                    sb.AppendLine(indentStr + $"  {prop.Name} ({propType.Name}): {value}");
                }
                else
                {
                    sb.AppendLine(indentStr + $"  {prop.Name} ({propType.Name}):");
                    PrintObjectDetail(sb, value, indent + 2, battleField, visitedObjects);
                }
            }
            catch (Exception e)
            {
                sb.AppendLine(indentStr + $"  {prop.Name}: <解析错误: {e.Message}>");
            }
        }
    }
    /// <summary>
    /// 判断是否为简单类型(直接输出值)
    /// </summary>
    private bool IsSimpleType(Type type)
    {
        return type.IsPrimitive ||
               type == typeof(string) ||
               type == typeof(decimal) ||
               type == typeof(DateTime) ||
               type.IsEnum;
    }
    /// <summary>
    /// 判断是否应该跳过该类型(Unity和System库的复杂类型)
    /// </summary>
    private bool ShouldSkipType(Type type)
    {
        if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal) || type.IsEnum)
        {
            return false;
        }
        string typeNamespace = type.Namespace ?? "";
        // 跳过Unity相关类型(除了基本类型)
        if (typeNamespace.StartsWith("UnityEngine") ||
            typeNamespace.StartsWith("Unity.") ||
            typeNamespace.StartsWith("System.") && !typeNamespace.StartsWith("System.Collections"))
        {
            return true;
        }
        return false;
    }
    /// <summary>
    /// 获取缩进字符串
    /// </summary>
    private string GetIndent(int indent)
    {
        return new string(' ', indent * 2);
    }
#endif
}
Main/System/Battle/BattleField/BattleField.cs
@@ -510,7 +510,7 @@
        }
    }
    public virtual void OnObjsDead(List<BattleDeadPack> deadPackList)
    public virtual void OnObjsDead(List<BattleDeadPack> deadPackList, RecordAction causingRecordAction = null)
    {
        if (deadPackList.Count > 0)
        {
@@ -545,7 +545,15 @@
            if (validDeadList.Count > 0)
            {
                DeathRecordAction recordAction = new DeathRecordAction(this, validDeadList);
                recordPlayer.ImmediatelyPlay(recordAction);
                //  如果有导致死亡的技能,将DeathRecordAction作为其子节点,并设置为WaitingPlay
                if (causingRecordAction != null)
                {
                    recordPlayer.ImmediatelyPlay(recordAction, causingRecordAction, true);
                }
                else
                {
                    recordPlayer.ImmediatelyPlay(recordAction);
                }
            }
        }
    }
Main/System/Battle/BattleField/RecordActions/BattleEndAction.cs
@@ -22,6 +22,7 @@
    {
        base.Run();
        isActionCompleted = true;
        isFinish = true;
        onComplete?.Invoke();
    }
@@ -34,6 +35,11 @@
        onComplete?.Invoke();
    }
    public override bool IsActionCompleted()
    {
        return isActionCompleted || isFinish;
    }
    public override bool IsFinished()
    {
        return isFinish;
Main/System/Battle/BattleField/RecordActions/BattleStartAction.cs
@@ -16,6 +16,11 @@
        return isFinish;
    }
    public override bool IsActionCompleted()
    {
        return isActionCompleted || isFinish;
    }
    public override void Run()
    {
@@ -55,6 +60,7 @@
    private void StartBattleCallback()
    {
        isActionCompleted = true;
        isFinish = true;
        EventBroadcast.Instance.Broadcast(EventName.DISPLAY_BATTLE_UI, battleField.guid, true);
        battleField.DistributeNextPackage();
Main/System/Battle/BattleField/RecordActions/DeathRecordAction.cs
@@ -15,10 +15,28 @@
    protected Dictionary<int, bool> dropStateDict = new Dictionary<int, bool>();
    public DeathRecordAction(BattleField _battleField, List<BattleDeadPack> _deadPackList)
    protected RecordAction causingRecordAction = null;
    protected bool hasDeathTriggerSkill = false;
    public DeathRecordAction(BattleField _battleField, List<BattleDeadPack> _deadPackList, RecordAction _causingRecordAction = null)
        : base(RecordActionType.Death, _battleField, null)
    {
        deadPackList = _deadPackList;
        causingRecordAction = _causingRecordAction;
        CheckHasDeathTriggerSkill();
    }
    protected void CheckHasDeathTriggerSkill()
    {
        foreach (var battleDeadPack in deadPackList)
        {
            if (battleDeadPack.deadTriggerSkill != null)
            {
                hasDeathTriggerSkill = true;
                break;
            }
        }
    }
@@ -42,6 +60,10 @@
                    if (null != skillAction)
                    {
                        deathActionDict.Add(battleDeadPack, skillAction);
                        //  死亡触发技能都设置WaitingPlay=true,等待父节点动作完成
                        //  如果DeathRecordAction有父节点(导致死亡的技能),则等待那个父节点
                        //  否则等待DeathRecordAction本身
                        battleField.recordPlayer.ImmediatelyPlay(skillAction, causingRecordAction == null ? this : causingRecordAction, true);
                    }
                }
                else
@@ -78,6 +100,11 @@
                deathActionDict.Remove(key);
            }
            if (0 == deathActionDict.Count)
            {
                hasDeathTriggerSkill = false;
            }
            int completeNum = 0;
            foreach (var kv in deadActionStatesDict)
@@ -90,9 +117,18 @@
            if (completeNum == deadPackList.Count)
            {
                isActionCompleted = true;
                isFinish = true;
            }
        }
    }
    //  重写IsActionCompleted,死亡动作是否完成
    public override bool IsActionCompleted()
    {
        return isActionCompleted || isFinish;
    }
    private Func<bool> CreateDeadActionState(BattleDeadPack deadPack, bool withoutAnime = false)
@@ -166,4 +202,39 @@
            deadObj.PerformDrop();
        }
    }
    protected bool HasDeathTriggerSkill()
    {
        return hasDeathTriggerSkill;
    }
    public override bool CanStartExecution()
    {
        //  如果不是WaitingPlay模式,直接可以执行
        if (!isWaitingPlay)
        {
            return true;
        }
        //  如果没有父节点,也可以执行
        if (parentAction == null)
        {
            return true;
        }
        //  可以先执行没有死亡触发技能的死亡动作
        if (!HasDeathTriggerSkill())
        {
            return true;
        }
        //  WaitingPlay模式下,需要等待直接父节点的动作完成(不管父节点是否WaitingPlay)
        if (!parentAction.IsActionCompleted())
        {
            BattleDebug.LogError($"RecordAction.CanStartExecution: {this.GetType().Name} 等待父节点 {parentAction.GetType().Name} 动作完成");
            return false;
        }
        return true;
    }
}
Main/System/Battle/BattleField/RecordActions/RebornRecordAction.cs
@@ -36,6 +36,12 @@
        }
    }
    //  重写IsActionCompleted,当动画播放结束时标记为完成
    public override bool IsActionCompleted()
    {
        return isActionCompleted || isFinish;
    }
    //  做一个从透明到恢复的渐变的同时 播放特效
    public void PlayRebornEffect()
    {
@@ -58,6 +64,7 @@
            battleObject?.AfterReborn();
            actionCallback?.Invoke();
            actionCallback = null;
            isActionCompleted = true;  // 标记动作完成
            isFinish = true;
        }));
Main/System/Battle/BattleField/RecordActions/RoundChangeAction.cs
@@ -18,6 +18,7 @@
            return;
        roundChangeCallback?.Invoke();
        isActionCompleted = true;
        isFinish = true;
    }
@@ -27,6 +28,12 @@
            return;
        base.Run();
        roundChangeCallback?.Invoke();
        isActionCompleted = true;
        isFinish = true;
    }
    public override bool IsActionCompleted()
    {
        return isActionCompleted || isFinish;
    }
}
Main/System/Battle/BattleField/RecordActions/SkillRecordAction.cs
@@ -5,6 +5,8 @@
{
    protected SkillBase skillBase;
    public HB427_tagSCUseSkill hB427_TagSCUseSkill;
    private bool isCast = false;
    public SkillBase fromSkill = null;
@@ -15,12 +17,26 @@
        // Debug.LogError("_caster == null : " + (_caster == null));
        skillBase = SkillFactory.CreateSkill(_caster, vNetData, packList, _battleField);
        hB427_TagSCUseSkill = vNetData;
        if (null == skillBase)
        {
            BattleUtility.ForceFinishBattlePackList(battleField, packList);
        }
        else
        {
            //  让SkillBase知道自己的RecordAction
            skillBase.SetParentRecordAction(this);
        }
    }
    public override bool IsNeedWaiting()
    {
        if (skillBase == null)
        {
            return false;
        }
        return !string.IsNullOrEmpty(skillBase.skillConfig.SkillMotionName);
    }
    public override bool IsFinished()
    {
@@ -29,6 +45,16 @@
            return true;
        }
        return skillBase.IsFinished();
    }
    //  检查自身动作是否完成(不包括子节点)
    public override bool IsActionCompleted()
    {
        if (null == skillBase)
        {
            return true;
        }
        return skillBase.IsFinishedForJudge();
    }
    public bool IsFinishedForJudge()
@@ -49,6 +75,23 @@
        }
        base.ForceFinish();
    }
    public override bool CanStartExecution()
    {
        if (null == skillBase)
        {
            return false;
        }
        if (!skillBase.CanStartExecution())
        {
            return false;
        }
        return base.CanStartExecution();
    }
    public override void Run()
    {
@@ -67,6 +110,8 @@
        if (isCast)
            return;
        Played();
        if (fromSkill != null)
        {
            BattleDebug.LogError("cast skill from skill : " + fromSkill.skillConfig.SkillID);
Main/System/Battle/BattleObject/BattleObject.cs
@@ -315,7 +315,7 @@
        return true;
    }
    public virtual void Hurt(BattleHurtParam battleHurtParam)
    public virtual void Hurt(BattleHurtParam battleHurtParam, RecordAction causingRecordAction = null)
    {
        bool isLastHit = battleHurtParam.hitIndex >= battleHurtParam.skillConfig.DamageDivide.Length - 1;
        bool firstHit = battleHurtParam.hitIndex == 0;
@@ -352,7 +352,7 @@
            {
                PushDropItems(battleHurtParam.battleDrops);
            }
            battleField.OnObjsDead(new List<BattleDeadPack>() { battleHurtParam.deadPack });
            battleField.OnObjsDead(new List<BattleDeadPack>() { battleHurtParam.deadPack }, causingRecordAction);
        }
        else
Main/System/Battle/Motion/MotionBase.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using UnityEngine;
using Spine.Unity;
using Cysharp.Threading.Tasks;
public class MotionBase
{
@@ -64,6 +65,7 @@
        PlayAnimation(MotionName.idle, true);
        SetupAnimationHandlers();
        skillTrack = null;
        
        if (skelAnim.gameObject != null)
            illusionShadow = skelAnim.gameObject.AddMissingComponent<SkeletonIllusionShadow>();
@@ -173,6 +175,9 @@
        return ExecuteSkillAnim(skillConfig, skillBase, onComplete, targetAnim, true, isSubSkill);
    }
    private Spine.TrackEntry skillTrack = null;
    private Spine.TrackEntry ExecuteSkillAnim(SkillConfig skillConfig, SkillBase skillBase, Action onComplete,
        Spine.Animation targetAnim, bool hasAnim, bool isSubSkill)
    {
@@ -215,10 +220,22 @@
            }
        }
        
        Spine.TrackEntry skillTrack = null;
        
        if (hasAnim)
        {
            if (null != skillTrack)
            {
                //等待上一技能动画结束 暂时处理办法
                UniTaskExtension.DelayFrames((GameObject)null, 1, () =>
                {
                    if (skillBase != null && !skillBase.IsFinished())
                    {
                        PlaySkillAnimation(skillConfig, skillBase, isSubSkill, onComplete);
                    }
                });
                return null;
            }
            skillTrack = animState.SetAnimation(trackIndex, targetAnim, false);
            if (null == skillTrack)
            {
@@ -441,6 +458,7 @@
                    RemoveAction(frameHandler);
                    onComplete?.Invoke();
                    skillBase.OnFinalFrameEnd();
                    skillTrack = null;
                }
            }
        };
@@ -599,6 +617,8 @@
            availableSubTracks.Enqueue(i);
        currentTrack = null;
        skillTrack = null;
        
        playingSkillAnim = false;
        PlayAnimation(MotionName.idle, true);
@@ -657,7 +677,7 @@
            skeleton.SetToSetupPose();
            // skeleton.UpdateWorldTransform();
        }
        skillTrack = null;
        // 10. 播放待机动画
        PlayAnimation(MotionName.idle, true);
        skeletonAnim.LateUpdate();
@@ -678,4 +698,9 @@
        }
    }
    public bool CanCastSkill()
    {
        //  上次释放的技能 释放结束了没有
        return !playingSkillAnim;
    }
}
Main/System/Battle/RecordPlayer/RecordAction.cs
@@ -14,6 +14,19 @@
    protected bool isRunOnce = false;
    //  ===== 父子关系和等待机制 =====
    //  父RecordAction引用(如果是通过其他RecordAction内部触发的)
    public RecordAction parentAction;
    //  子RecordAction列表(此RecordAction内部触发的其他RecordAction)
    protected List<RecordAction> childActionList = new List<RecordAction>();
    //  是否是WaitingPlay模式(需要等待父节点动作完成)
    public bool isWaitingPlay = false;
    //  自身动作是否完成(不包括子节点的完成状态)
    protected bool isActionCompleted = false;
    public RecordAction(RecordActionType _actionType, BattleField _battleField, BattleObject _battleObj)
    {
        actionType = _actionType;
@@ -21,11 +34,105 @@
        battleObject = _battleObj;
    }
    public virtual void Played()
    {
    }
    public RecordActionType actionType;
    //  添加子RecordAction
    public virtual void AddChildAction(RecordAction child)
    {
        if (child == null) return;
        //  防止重复添加
        if (childActionList.Contains(child)) return;
        //  防止循环依赖
        if (child == this)
        {
            BattleDebug.LogError("RecordAction.AddChildAction: 不能将自己添加为子节点");
            return;
        }
        childActionList.Add(child);
        BattleDebug.LogError($"RecordAction.AddChildAction: {this.GetType().Name} 添加子节点 {child.GetType().Name}");
    }
    //  设置父RecordAction
    public virtual void SetParentAction(RecordAction parent)
    {
        parentAction = parent;
        if (parent != null)
        {
            BattleDebug.LogError($"RecordAction.SetParentAction: {this.GetType().Name} 的父节点设置为 {parent.GetType().Name}");
        }
    }
    //  设置WaitingPlay标记
    public virtual void SetWaitingPlay(bool waiting)
    {
        isWaitingPlay = waiting;
        BattleDebug.LogError($"RecordAction.SetWaitingPlay: {this.GetType().Name} WaitingPlay={waiting}");
    }
    //  检查自身动作是否完成(不包括子节点)
    //  子类应该重写此方法来实现自己的动作完成判断
    public virtual bool IsActionCompleted()
    {
        return isActionCompleted;
    }
    //  检查是否可以开始执行(WaitingPlay条件检查)
    public virtual bool CanStartExecution()
    {
        //  如果不是WaitingPlay模式,直接可以执行
        if (!isWaitingPlay)
        {
            return true;
        }
        //  如果没有父节点,也可以执行
        if (parentAction == null)
        {
            return true;
        }
        //  WaitingPlay模式下,需要等待直接父节点的动作完成(不管父节点是否WaitingPlay)
        if (!parentAction.IsActionCompleted())
        {
            BattleDebug.LogError($"RecordAction.CanStartExecution: {this.GetType().Name} 等待父节点 {parentAction.GetType().Name} 动作完成");
            return false;
        }
        return true;
    }
    public virtual bool IsNeedWaiting()
    {
        return false;
    }
    //  检查是否完全完成(包括所有子节点)
    public virtual bool IsFinished()
    {
        return isFinish;
        //  首先自身动作必须完成
        if (!isActionCompleted && !isFinish)
        {
            return false;
        }
        //  检查所有子节点是否完成
        foreach (var child in childActionList)
        {
            if (!child.IsFinished())
            {
                return false;
            }
        }
        return true;
    }
    public virtual void Run()
@@ -36,6 +143,18 @@
    public virtual void ForceFinish()
    {
        isFinish = true;
        isActionCompleted = true;
        //  强制结束所有子节点
        for (int i = childActionList.Count - 1; i >= 0; i--)
        {
            var child = childActionList[i];
            child?.ForceFinish();
        }
        //  清理父子引用,防止内存泄漏
        childActionList.Clear();
        parentAction = null;
    }
    public virtual string GetBattleFieldGuid()
Main/System/Battle/RecordPlayer/RecordPlayer.cs
@@ -43,7 +43,7 @@
    public void PlayRecord(RecordAction recordAction)
    {
        if (recordAction == null) return;
        BattleDebug.LogError("Enqueue record action " + recordAction.GetType());
        // Debug.LogError("Enqueue record action " + recordAction.GetType() + " to queue");
        if (isForceFinish || stepForcefinish)
        {
            recordAction.ForceFinish();
@@ -69,7 +69,7 @@
            return;
        }
        BattleDebug.LogError("Insert record action " + recordAction.GetType());
        // Debug.LogError("Insert record action " + recordAction.GetType() + " at front of queue");
        if (currentRecordAction != null)
        {
            Queue<RecordAction> tempQueue = new Queue<RecordAction>();
@@ -97,13 +97,39 @@
        immediatelyActionList.Add(recordAction);
    }
    //  增强版本:支持父子关系和WaitingPlay标记
    public void ImmediatelyPlay(RecordAction recordAction, RecordAction parentAction, bool isWaitingPlay)
    {
        if (recordAction == null) return;
        if (isForceFinish || stepForcefinish)
        {
            recordAction.ForceFinish();
            return;
        }
        // Debug.LogError("insert recordAction ImmediatelyPlay: " + recordAction.GetType().Name + " parentAction: " + (parentAction != null ? parentAction.GetType().Name : "null") + " isWaitingPlay: " + isWaitingPlay);
        //  设置父子关系
        if (parentAction != null)
        {
            recordAction.SetParentAction(parentAction);
            parentAction.AddChildAction(recordAction);
        }
        //  设置WaitingPlay标记
        recordAction.SetWaitingPlay(isWaitingPlay);
        immediatelyActionList.Add(recordAction);
    }
    protected void ImmediatelyPlayRun()
    {
        if (immediatelyActionList.Count > 0)
        {
            List<int> removeIndexList = new List<int>();
            for (int i = immediatelyActionList.Count - 1; i >= 0; i--)
            //  关键修复:从前往后遍历,确保按加入顺序执行
            for (int i = 0; i < immediatelyActionList.Count; i++)
            {
                var action = immediatelyActionList[i];
                if (action == null)
@@ -111,6 +137,38 @@
                    removeIndexList.Add(i);
                    continue;
                }
                //  检查是否可以开始执行(WaitingPlay条件检查)
                if (!action.CanStartExecution())
                {
                    continue;
                }
                //  检查同级的前置兄弟节点:如果有相同父节点且索引更小的节点还在执行,则等待
                bool shouldWaitForSibling = false;
                if (action.isWaitingPlay && action.parentAction != null)
                {
                    for (int j = 0; j < i; j++)
                    {
                        var prevAction = immediatelyActionList[j];
                        if (prevAction != null && prevAction.parentAction == action.parentAction)
                        {
                            //  找到同级前置节点,如果它还在执行中,则当前节点需要等待
                            if (!prevAction.IsFinished())
                            {
                                shouldWaitForSibling = true;
                                // BattleDebug.LogError($"RecordPlayer: {action.GetType().Name} 等待同级前置节点 {prevAction.GetType().Name} 完成");
                                break;
                            }
                        }
                    }
                }
                if (shouldWaitForSibling)
                {
                    continue;
                }
                if (!action.IsFinished())
                {
                    action.Run();
@@ -120,6 +178,7 @@
                removeIndexList.Add(i);
            }
            //  从后往前删除,确保索引不会因为删除而错位
            for (int i = removeIndexList.Count - 1; i >= 0; i--)
            {
                int index = removeIndexList[i];
@@ -166,7 +225,7 @@
        if (currentRecordAction != null && currentRecordAction.IsFinished())
        {
            var guid = currentRecordAction.GetBattleFieldGuid();
            BattleDebug.LogError("record action " + currentRecordAction.GetType() + " play finished");
            // BattleDebug.LogError("record action " + currentRecordAction.GetType() + " play finished");
            currentRecordAction = null;
            isWaitingNextAction = true;
            waitTimer = 0f;
@@ -179,7 +238,7 @@
            if (recordActionQueue.Count > 0)
            {
                currentRecordAction = recordActionQueue.Dequeue();
                BattleDebug.LogError("play record action " + currentRecordAction.GetType());
                // BattleDebug.LogError("play record action " + currentRecordAction.GetType());
            }
        }
    }
Main/System/Battle/Skill/SkillBase.cs
@@ -28,11 +28,18 @@
    protected List<H0704_tagRolePackRefresh> dropPackList = new List<H0704_tagRolePackRefresh>();
    protected List<HB405_tagMCAddExp> expPackList = new List<HB405_tagMCAddExp>();
    protected List<BattleDeadPack> endDeadPackList = new List<BattleDeadPack>();
    protected List<SkillRecordAction> waitingCastSkillRecordAction = new List<SkillRecordAction>();
    protected bool moveFinished = false;
    public SkillBase fromSkill;
    public bool isPlay = false;
    //  父RecordAction(SkillRecordAction),用于子技能建立父子关系
    protected RecordAction parentRecordAction;
    //  技能动画是否播放完成(针对有动画的技能)
    protected bool isMotionCompleted = false;
    private float MoveSpeed = 750f;
@@ -59,12 +66,12 @@
        }
        SafetyCheck();
#if UNITY_EDITOR
        if (Launch.Instance.isOpenSkillLogFile)
        {
            PinrtHB427Hp();
        }
#endif
    }
    //  设置父RecordAction
    public void SetParentRecordAction(RecordAction recordAction)
    {
        parentRecordAction = recordAction;
    }
#if UNITY_EDITOR
@@ -144,6 +151,13 @@
    private void SafetyCheck()
    {
#if UNITY_EDITOR
        if (Launch.Instance.isOpenSkillLogFile)
        {
            PinrtHB427Hp();
        }
#endif
        bool safety = caster != null 
                        && skillConfig != null 
                        && tagUseSkillAttack != null 
@@ -485,24 +499,60 @@
        skillEffect = SkillEffectFactory.CreateSkillEffect(this, caster, skillConfig, tagUseSkillAttack);
        skillEffect.Play(OnHitTargets);
        ProcessSubSkill();
        HandleWaitingCastSkill();
        isPlay = true;
    }
    protected void ProcessSubSkill()
    {
        foreach (var subSkillPack in tagUseSkillAttack.subSkillList)
        {
            SkillRecordAction recordAction = CustomHB426CombinePack.CreateSkillAction(battleField.guid, new List<GameNetPackBasic>() { subSkillPack });
            recordAction.fromSkill = this;
            otherSkillActionList.Add(recordAction);
            battleField.recordPlayer.ImmediatelyPlay(recordAction);
            //  子技能设置WaitingPlay=true,等待父技能动作完成
            waitingCastSkillRecordAction.Add(recordAction);
            battleField.recordPlayer.ImmediatelyPlay(recordAction, parentRecordAction, true);
        }
        tagUseSkillAttack.subSkillList.Clear();
        foreach (var subCombinePack in tagUseSkillAttack.subSkillCombinePackList)
        {
            SkillRecordAction recordAction = CustomHB426CombinePack.CreateSkillAction(battleField.guid, subCombinePack.packList);
            recordAction.fromSkill = this;
            otherSkillActionList.Add(recordAction);
            battleField.recordPlayer.ImmediatelyPlay(recordAction);
            //  子技能设置WaitingPlay=true,等待父技能动作完成
            waitingCastSkillRecordAction.Add(recordAction);
        }
        tagUseSkillAttack.subSkillCombinePackList.Clear();
        isPlay = true;
        waitingCastSkillRecordAction.OrderBy(recordAction => recordAction.hB427_TagSCUseSkill.packUID);
    }
    protected void HandleWaitingCastSkill()
    {
        RecordAction waitingRecordAction = null;
        for (int i = 0; i < waitingCastSkillRecordAction.Count; i++)
        {
            var recordAction = waitingCastSkillRecordAction[i];
            if (waitingRecordAction != null)
            {
                //  每个都应该等前一个结束后
                battleField.recordPlayer.ImmediatelyPlay(recordAction, waitingRecordAction, true);
            }
            else
            {
                battleField.recordPlayer.ImmediatelyPlay(recordAction, parentRecordAction, true);
            }
            if (recordAction.IsNeedWaiting())
            {
                waitingRecordAction = recordAction;
            }
        }
    }
    // 技能前摇结束回调
@@ -529,6 +579,9 @@
    // 技能后摇结束回调:通知技能效果处理后摇结束
    public virtual void OnFinalFrameEnd()
    {
        //  标记动画播放完成
        isMotionCompleted = true;
        BattleDebug.LogError($"SkillBase.OnFinalFrameEnd: 技能 {skillConfig?.SkillID} 动画播放完成");
        
        skillEffect?.OnFinalFrameEnd(); // 修复:添加空值检查
    }
@@ -728,7 +781,7 @@
#endif
        // 先调用目标受伤
        target.Hurt(hurtParam);
        target.Hurt(hurtParam, parentRecordAction);
        
        // 再调用施法者吸血/反伤
        caster.OnHurtTarget(hurtParam);
@@ -1008,6 +1061,16 @@
                return false;
            }
            //  如果技能有动画(SkillMotionName不为空),需要等待动画播放完成
            if (skillConfig != null && !string.IsNullOrEmpty(skillConfig.SkillMotionName))
            {
                if (!isMotionCompleted)
                {
                    BattleDebug.LogError($"SkillBase.IsFinishedForJudge: 技能 {skillConfig.SkillID} 等待动画播放完成");
                    return false;
                }
            }
            return true;
        }
@@ -1067,6 +1130,7 @@
            {
                battleField.RemoveCastingSkill(caster.ObjID, this);
                
                //  传递parentRecordAction,让死亡技能等待当前技能完成
                battleField.OnObjsDead(new List<BattleDeadPack>(tempDeadPackList.Values));
            }
@@ -1089,7 +1153,8 @@
            battleField.RemoveCastingSkill(caster.ObjID, this);
        }
        battleField.OnObjsDead(new List<BattleDeadPack>(tempDeadPackList.Values));
        //  传递parentRecordAction,让死亡技能等待当前技能完成
        battleField.OnObjsDead(new List<BattleDeadPack>(tempDeadPackList.Values), parentRecordAction);
        // 1. 强制结束技能效果
        skillEffect?.ForceFinished();
@@ -1100,7 +1165,8 @@
            SkillRecordAction recordAction = CustomHB426CombinePack.CreateSkillAction(battleField.guid, new List<GameNetPackBasic>() { subSkillPack });
            recordAction.fromSkill = this;
            otherSkillActionList.Add(recordAction);
            battleField.recordPlayer.ImmediatelyPlay(recordAction);
            //  子技能设置WaitingPlay=true,等待父技能动作完成
            battleField.recordPlayer.ImmediatelyPlay(recordAction, parentRecordAction, true);
        }
        tagUseSkillAttack.subSkillList.Clear();
        foreach (var subCombinePack in tagUseSkillAttack.subSkillCombinePackList)
@@ -1108,7 +1174,8 @@
            SkillRecordAction recordAction = CustomHB426CombinePack.CreateSkillAction(battleField.guid, subCombinePack.packList);
            recordAction.fromSkill = this;
            otherSkillActionList.Add(recordAction);
            battleField.recordPlayer.ImmediatelyPlay(recordAction);
            //  子技能设置WaitingPlay=true,等待父技能动作完成
            battleField.recordPlayer.ImmediatelyPlay(recordAction, parentRecordAction, true);
        }
        tagUseSkillAttack.subSkillCombinePackList.Clear();
        
@@ -1173,6 +1240,9 @@
        isFinished = true;
        moveFinished = true;
        isPlay = true;
        //  强制结束时,无论是否有动画,都标记动画完成
        isMotionCompleted = true;
        // 6. 处理所有剩余包(包括 buff 包)
        // 先处理 buffPackCollections
@@ -1377,6 +1447,26 @@
        }
    }
    public virtual bool CanStartExecution()
    {
        if (null == caster)
        {
            return false;
        }
        if (null == skillConfig)
        {
            return false;
        }
        if (string.IsNullOrEmpty(skillConfig.SkillMotionName))
        {
            return true;
        }
        return !battleField.IsCastingSkill(caster.ObjID);
    }
    #endregion
}