yyl
21 小时以前 7f9ec6d10ebb5d741b10e2b4168b11ad0ebb22cd
125 战斗 飘血 护盾 满怒气 吸血反伤拆分
13个文件已修改
1399 ■■■■ 已修改文件
Main/System/Battle/BattleConst.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleHUDWin.cs 360 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleManager.cs 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleObject/BattleObject.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleObject/BattleObjectFactory.cs 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Buff/BattleObjectBuffMgr.cs 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Define/BattleDmgInfo.cs 363 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Define/DamageType.cs 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/Skill/SkillBase.cs 128 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/UIComp/BattleFloatingUIController.cs 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/UIComp/BattleHeroInfoBar.cs 313 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/UIComp/BattleTips.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/UIComp/DamageContent.cs 135 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleConst.cs
@@ -202,4 +202,5 @@
        PassiveSkillLimitGroup,
    };
    public const int ShieldBuffAttackType = 1003;//护盾吸收伤害类型ID
}
Main/System/Battle/BattleHUDWin.cs
@@ -10,6 +10,10 @@
//  这个界面是 persistent的界面
public class BattleHUDWin : UIBase
{
    private const int CASTER_DAMAGE_HEIGHT_OFFSET = 100; // 施法者伤害高度间隔
    private const int CASTER_DAMAGE_FLOAT_HEIGHT = 150;  // 飘字向上移动高度
    private const int TARGET_DAMAGE_FLOAT_HEIGHT = 150;  // 目标伤害飘字向上移动高度
    private GameObjectPoolManager.GameObjectPool damagePrefabPool;
    private GameObjectPoolManager.GameObjectPool buffIconPrefabPool;
    private GameObjectPoolManager.GameObjectPool buffLabelPrefabPool;
@@ -29,35 +33,14 @@
    protected override void OnPreOpen()
    {
        base.OnPreOpen();
        EventBroadcast.Instance.AddListener<BattleDmgInfo>(EventName.BATTLE_DAMAGE_TAKEN, OnDamageTaken);
        EventBroadcast.Instance.AddListener<string, JsonData>(EventName.BATTLE_END, OnBattleEnd);
        damagePrefabPool = GameObjectPoolManager.Instance.RequestPool(UILoader.LoadPrefab("DamageContent"));
    }
    private void OnBattleEnd(string guid, JsonData data)
    {
        ClearContent(guid);
    }
    private void ClearContent(string guid, bool force = false)
    {
        if ((battleField != null && battleField.guid == guid) || force)
        {
            for (int i = damageContentList.Count - 1; i >= 0; i--)
            {
                var content = damageContentList[i];
                content.Stop();
                RemoveDamageContent(content);
            }
            damageContentList.Clear();
        }
        RegisterEvents();
        InitializePools();
    }
    protected override void OnPreClose()
    {
        base.OnPreClose();
        EventBroadcast.Instance.RemoveListener<BattleDmgInfo>(EventName.BATTLE_DAMAGE_TAKEN, OnDamageTaken);
        EventBroadcast.Instance.RemoveListener<string, JsonData>(EventName.BATTLE_END, OnBattleEnd);
        UnregisterEvents();
    }
    protected override void OnOpen()
@@ -68,13 +51,7 @@
    protected override void OnClose()
    {
        base.OnClose();
        if (battleField != null)
        {
            battleField.OnBattlePause -= OnBattlePause;
            battleField.OnBattleRun -= OnBattleRun;
            battleField.OnSpeedRatioChange -= OnSpeedRatioChange;
            battleField = null;
        }
        CleanupBattleField();
    }
    protected override void NextFrameAfterOpen()
@@ -87,121 +64,65 @@
        base.CompleteClose();
    }
    private void RemoveDamageContent(DamageContent content)
    private void RegisterEvents()
    {
        damageContentList.Remove(content);
        damagePrefabPool.Release(content.gameObject);
        EventBroadcast.Instance.AddListener<BattleDmgInfo>(EventName.BATTLE_DAMAGE_TAKEN, OnDamageTaken);
        EventBroadcast.Instance.AddListener<string, JsonData>(EventName.BATTLE_END, OnBattleEnd);
    }
    private void UnregisterEvents()
    {
        EventBroadcast.Instance.RemoveListener<BattleDmgInfo>(EventName.BATTLE_DAMAGE_TAKEN, OnDamageTaken);
        EventBroadcast.Instance.RemoveListener<string, JsonData>(EventName.BATTLE_END, OnBattleEnd);
    }
    private void InitializePools()
    {
        damagePrefabPool = GameObjectPoolManager.Instance.RequestPool(UILoader.LoadPrefab("DamageContent"));
    }
    public void SetBattleField(BattleField _battleField)
    {
        CleanupBattleField();
        ClearContent(string.Empty, true);
        battleField = _battleField;
        RegisterBattleFieldEvents();
    }
    private void RegisterBattleFieldEvents()
    {
        if (battleField == null) return;
        battleField.OnBattlePause += OnBattlePause;
        battleField.OnBattleRun += OnBattleRun;
        battleField.OnSpeedRatioChange += OnSpeedRatioChange;
    }
    private void UnregisterBattleFieldEvents()
    {
        if (battleField == null) return;
        battleField.OnBattlePause -= OnBattlePause;
        battleField.OnBattleRun -= OnBattleRun;
        battleField.OnSpeedRatioChange -= OnSpeedRatioChange;
    }
    private void CleanupBattleField()
    {
        UnregisterBattleFieldEvents();
        battleField = null;
    }
    private void OnBattleEnd(string guid, JsonData data)
    {
        ClearContent(guid);
    }
    private void OnDamageTaken(BattleDmgInfo damageInfo)
    {
        SetTargetDamage(damageInfo);
        SetSelfDamage(damageInfo);
    }
    private void SetSelfDamage(BattleDmgInfo damageInfo)
    {
        if (damageInfo.casterDamageList.Count > 0)
        {
            GameObject damageContent = damagePrefabPool.Request();
            DamageContent content = damageContent.GetComponent<DamageContent>();
            damageContent.transform.SetParent(damageNode, false);
            var heroRect = damageInfo.casterObj.heroRectTrans;
            if (heroRect == null)
            {
                damagePrefabPool.Release(damageContent);
                return;
            }
            var contentRect = content.GetComponent<RectTransform>();
            var contentParentRect = contentRect.parent as RectTransform;
            // 获取 heroRect 的世界坐标(锚点为中心)
            Vector3 worldTargetPos = heroRect.transform.TransformPoint(heroRect.rect.center);
            // 转换到 content 父节点下的 anchoredPosition
            Vector2 anchoredPos;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                contentParentRect,
                RectTransformUtility.WorldToScreenPoint(null, worldTargetPos),
                null,
                out anchoredPos);
            // 设置动态位置(会覆盖配置中的位置)
            Vector2 beginPos = anchoredPos;
            Vector2 endPos = anchoredPos + new Vector2(0, 150);
            content.SetPosition(beginPos, endPos);
            // 设置速度比例
            if (battleField != null)
            {
                content.SetRatio(battleField.speedRatio, 1f);
            }
            content.SetDamage(damageInfo, damageInfo.casterDamageList, () => RemoveDamageContent(content));
            damageContentList.Add(content);
        }
    }
    private void SetTargetDamage(BattleDmgInfo damageInfo)
    {
        if (damageInfo.targetDamageList.Count > 0)
        {
            GameObject damageContent = damagePrefabPool.Request();
            DamageContent content = damageContent.GetComponent<DamageContent>();
            damageContent.transform.SetParent(damageNode, false);
            var heroRect = damageInfo.hurtObj.heroRectTrans;
            if (heroRect == null)
            {
                damagePrefabPool.Release(damageContent);
                return;
            }
            var contentRect = content.GetComponent<RectTransform>();
            var contentParentRect = contentRect.parent as RectTransform;
            // 获取 heroRect 的世界坐标(锚点为中心)
            Vector3 worldTargetPos = heroRect.transform.TransformPoint(heroRect.rect.center);
            // 转换到 content 父节点下的 anchoredPosition
            Vector2 anchoredPos;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                contentParentRect,
                RectTransformUtility.WorldToScreenPoint(null, worldTargetPos),
                null,
                out anchoredPos);
            // 设置动态位置(会覆盖配置中的位置)
            Vector2 beginPos = anchoredPos;
            Vector2 endPos = anchoredPos + new Vector2(0, 150);
            content.SetPosition(beginPos, endPos);
            // 设置速度比例
            if (battleField != null)
            {
                content.SetRatio(battleField.speedRatio, 1f);
            }
            content.SetDamage(damageInfo, damageInfo.targetDamageList, () => RemoveDamageContent(content));
            damageContentList.Add(content);
        }
    }
    public void SetBattleField(BattleField _battleField)
    {
        if (battleField != null)
        {
            battleField.OnBattlePause -= OnBattlePause;
            battleField.OnBattleRun -= OnBattleRun;
            battleField.OnSpeedRatioChange -= OnSpeedRatioChange;
        }
        ClearContent(string.Empty, true);
        battleField = _battleField;
        battleField.OnBattlePause += OnBattlePause;
        battleField.OnBattleRun += OnBattleRun;
        battleField.OnSpeedRatioChange += OnSpeedRatioChange;
    }
    private void OnSpeedRatioChange(float newSpeedRatio)
@@ -216,22 +137,167 @@
    {
        if (isPause)
        {
            foreach (var content in damageContentList)
            {
                content.Stop();
            }
            PauseAllDamageContent();
        }
        else
        {
            foreach (var content in damageContentList)
            {
                content.Resume();
            }
            ResumeAllDamageContent();
        }
    }
    private void OnBattleRun()
    {
        RunAllDamageContent();
    }
    /// <summary>
    /// 设置目标受到的伤害显示
    /// </summary>
    private void SetTargetDamage(BattleDmgInfo damageInfo)
    {
        if (damageInfo.targetDamageList.Count == 0) return;
        RectTransform heroRect = damageInfo.hurtObj.heroRectTrans;
        if (heroRect == null) return;
        DamageContent content = CreateDamageContent();
        if (content == null) return;
        Vector2 anchoredPos = CalculateWorldToLocalPosition(heroRect, content);
        SetupTargetDamagePosition(content, anchoredPos);
        SetupDamageContent(content, damageInfo.targetDamageList, damageInfo);
    }
    /// <summary>
    /// 设置目标伤害的起始和结束位置
    /// </summary>
    private void SetupTargetDamagePosition(DamageContent content, Vector2 anchoredPos)
    {
        // 保持英雄的 X 坐标,只在 Y 轴向上飘
        Vector2 beginPos = anchoredPos;
        Vector2 endPos = anchoredPos + new Vector2(0, TARGET_DAMAGE_FLOAT_HEIGHT);
        content.SetPosition(beginPos, endPos);
    }
    /// <summary>
    /// 设置施法者受到的伤害显示(反伤、吸血等)
    /// </summary>
    private void SetSelfDamage(BattleDmgInfo damageInfo)
    {
        if (damageInfo.casterDamageList.Count == 0) return;
        RectTransform heroRect = damageInfo.casterObj.heroRectTrans;
        if (heroRect == null) return;
        // 创建单个DamageContent显示所有施法者伤害
        DamageContent content = CreateDamageContent();
        if (content == null) return;
        Vector2 anchoredPos = CalculateWorldToLocalPosition(heroRect, content);
        SetupCasterDamagePosition(content, anchoredPos);
        SetupDamageContent(content, damageInfo.casterDamageList, damageInfo);
    }
    /// <summary>
    /// 设置施法者伤害的起始和结束位置
    /// </summary>
    private void SetupCasterDamagePosition(DamageContent content, Vector2 anchoredPos)
    {
        // 保持英雄的 X 坐标,只在 Y 轴向上飘
        Vector2 beginPos = anchoredPos;
        Vector2 endPos = anchoredPos + new Vector2(0, CASTER_DAMAGE_FLOAT_HEIGHT);
        content.SetPosition(beginPos, endPos);
    }
    /// <summary>
    /// 创建一个DamageContent对象
    /// </summary>
    private DamageContent CreateDamageContent()
    {
        GameObject damageContentObj = damagePrefabPool.Request();
        DamageContent content = damageContentObj.GetComponent<DamageContent>();
        damageContentObj.transform.SetParent(damageNode, false);
        return content;
    }
    /// <summary>
    /// 计算英雄世界坐标到本地坐标的转换
    /// </summary>
    private Vector2 CalculateWorldToLocalPosition(RectTransform heroRect, DamageContent content)
    {
        RectTransform contentRect = content.GetComponent<RectTransform>();
        RectTransform contentParentRect = contentRect.parent as RectTransform;
        Vector3 worldTargetPos = heroRect.transform.TransformPoint(heroRect.rect.center);
        Vector2 anchoredPos;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            contentParentRect,
            RectTransformUtility.WorldToScreenPoint(null, worldTargetPos),
            null,
            out anchoredPos);
        return anchoredPos;
    }
    /// <summary>
    /// 配置DamageContent的速度、伤害数据和回调
    /// </summary>
    private void SetupDamageContent(DamageContent content, List<BattleDmg> damageList, BattleDmgInfo damageInfo)
    {
        if (battleField != null)
        {
            content.SetRatio(battleField.speedRatio, 1f);
        }
        content.SetDamage(damageInfo, damageList, () => RemoveDamageContent(content));
        damageContentList.Add(content);
    }
    /// <summary>
    /// 移除DamageContent对象
    /// </summary>
    private void RemoveDamageContent(DamageContent content)
    {
        damageContentList.Remove(content);
        damagePrefabPool.Release(content.gameObject);
    }
    /// <summary>
    /// 清除所有伤害显示内容
    /// </summary>
    private void ClearContent(string guid, bool force = false)
    {
        if ((battleField != null && battleField.guid == guid) || force)
        {
            for (int i = damageContentList.Count - 1; i >= 0; i--)
            {
                var content = damageContentList[i];
                content.Stop();
                RemoveDamageContent(content);
            }
            damageContentList.Clear();
        }
    }
    private void PauseAllDamageContent()
    {
        foreach (var content in damageContentList)
        {
            content.Stop();
        }
    }
    private void ResumeAllDamageContent()
    {
        foreach (var content in damageContentList)
        {
            content.Resume();
        }
    }
    private void RunAllDamageContent()
    {
        for (int i = damageContentList.Count - 1; i >= 0; i--)
        {
            if (i < damageContentList.Count)
Main/System/Battle/BattleManager.cs
@@ -491,12 +491,19 @@
                isCreate = false;
            }
            else
            {
            {
                BattleDebug.LogError("战场已存在 先进行销毁");
                battleField.Destroy();
            }
        }
        var bf = GetBattleFieldByMapID(MapID);
        if (bf != null && !string.IsNullOrEmpty(guid))
        {
            BattleDebug.LogError("相同地图ID的战场已存在 先进行销毁");
            bf.Destroy();
        }
        if (isCreate)
        {
            battleField = BattleFieldFactory.CreateBattleField(guid, MapID, FuncLineID, extendData, redTeamList, blueTeamList);
Main/System/Battle/BattleObject/BattleObject.cs
@@ -170,12 +170,12 @@
        {
            case PlayerDataType.HP:
                long toHp = GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx);
                heroInfoBar.UpdateHP(teamHero.curHp, toHp, teamHero.maxHp);
                heroInfoBar.UpdateHP(teamHero.curHp, toHp, teamHero.maxHp, false);
                teamHero.curHp = GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx);
                break;
            case PlayerDataType.MaxHP:
                teamHero.maxHp = GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx);
                heroInfoBar.UpdateHP(teamHero.curHp, teamHero.curHp, teamHero.maxHp);
                heroInfoBar.UpdateHP(teamHero.curHp, teamHero.curHp, teamHero.maxHp, false);
                break;
            case PlayerDataType.XP:
                long toXp = GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx);
@@ -198,12 +198,12 @@
        {
            case PlayerDataType.HP:
                long toHp = GeneralDefine.GetFactValue(vNetData.Value, vNetData.ValueEx);
                heroInfoBar.UpdateHP(teamHero.curHp, toHp, teamHero.maxHp);
                heroInfoBar.UpdateHP(teamHero.curHp, toHp, teamHero.maxHp, false);
                teamHero.curHp = GeneralDefine.GetFactValue(vNetData.Value, vNetData.ValueEx);
                break;
            case PlayerDataType.MaxHP:
                teamHero.maxHp = GeneralDefine.GetFactValue(vNetData.Value, vNetData.ValueEx);
                heroInfoBar.UpdateHP(teamHero.curHp, teamHero.curHp, teamHero.maxHp);
                heroInfoBar.UpdateHP(teamHero.curHp, teamHero.curHp, teamHero.maxHp, false);
                break;
            case PlayerDataType.XP:
                long toXp = GeneralDefine.GetFactValue(vNetData.Value, vNetData.ValueEx);
@@ -365,6 +365,12 @@
        motionBase.ShowIllusionShadow(true);
        DamageNumConfig damageNumConfig = DamageNumConfig.Get((int)DamageType.Dodge);
        string dodgeStr = ((char)damageNumConfig.prefix).ToString();
        heroInfoBar.ShowTips(dodgeStr, true, false);
        tween.onComplete += () =>
        {
            motionBase.ShowIllusionShadow(false);
@@ -428,8 +434,7 @@
        }
        else
        {
            // 使用传入的 fromHp 和 toHp 更新血条显示
            heroInfoBar.UpdateHP(battleHurtParam.fromHp, battleHurtParam.toHp, teamHero.maxHp);
            heroInfoBar.UpdateDamage(battleDmgInfo);
            // YYL TODO 是否需要挂在在自身的follow点上
            EventBroadcast.Instance.Broadcast(EventName.BATTLE_DAMAGE_TAKEN, battleDmgInfo);
Main/System/Battle/BattleObject/BattleObjectFactory.cs
@@ -34,8 +34,6 @@
        GameObject battleGO = ResManager.Instance.LoadAsset<GameObject>("Hero/SpineRes", "Hero_001"/*skinCfg.SpineRes*/);
        GameObject goParent = posNodeList[teamHero.positionNum];
        BattleObject battleObject = new BattleObject(_battleField);
        battleObject.ObjID = teamHero.ObjID;
@@ -50,7 +48,6 @@
            return null;
        }
        float finalScaleRate = modelScaleRate * teamHero.modelScale;
        skeletonAnimation.skeletonDataAsset = skeletonDataAsset;
@@ -61,6 +58,14 @@
        rectTrans.anchoredPosition = Vector2.zero;
        battleObject.Init(realGO, teamHero, _Camp);
#if UNITY_EDITOR
        BattleDebug.LogError(
            "初始化 未行动" +
            (battleObject.Camp == BattleCamp.Red ? "【红方】" : "【蓝方】 ") +
            $"武将: {battleObject.teamHero.name}\n" +
            $"当前血量: {battleObject.teamHero.curHp} -> 最大血量{battleObject.teamHero.maxHp}\n"
        );
#endif
        return battleObject;
    }
Main/System/Battle/Buff/BattleObjectBuffMgr.cs
@@ -363,12 +363,37 @@
        return false;
    }
    public List<HB428_tagSCBuffRefresh> GetBuffList()
    {
        return buffDataDict.Values.ToList();
    }
    public long GetShieldValue()
    {
        // 承伤盾判断,当释放方式为1003时可以视为承伤盾
        // 前端目前应该是承伤盾会用到
        // Value1     当前剩余盾值求余亿部分
        // Value2     当前剩余盾值整除亿部分
        return GetBuffValue(BattleConst.ShieldBuffAttackType);
    }
    public long GetBuffValue(int buffAtkType)
    {
        long values = 0;
        foreach (var kv in buffDataDict)
        {
            HB428_tagSCBuffRefresh hB428_TagSCBuffRefresh = kv.Value;
            SkillConfig skillConfig = SkillConfig.Get((int)hB428_TagSCBuffRefresh.SkillID);
            if (null != skillConfig && skillConfig.AtkType == buffAtkType)
            {
                values += GeneralDefine.GetFactValue(hB428_TagSCBuffRefresh.Value1, hB428_TagSCBuffRefresh.Value2);
            }
        }
        return values;
    }
    public void InsertBuff(HB428_tagSCBuffRefresh vNetData)
    {
        RefreshBuff(vNetData, true);
Main/System/Battle/Define/BattleDmgInfo.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using UnityEngine;
public class BattleDmg
{
@@ -11,42 +12,43 @@
    }
}
public class BattleDmgInfo
{
    public string battleFieldGuid { get; private set; }
    public BattleHurtParam battleHurtParam { get; private set; }
    public List<long> damageList { get { return battleHurtParam.damageList; } }
    public BattleObject hurtObj { get { return battleHurtParam.hurtObj; } }
    public BattleObject casterObj { get { return battleHurtParam.casterObj; } }
    public HB427_tagSCUseSkill.tagSCUseSkillHurt hurt { get { return battleHurtParam.hurt; } }
    public SkillConfig skillConfig { get { return battleHurtParam.skillConfig; } }
    //  是否被格挡了
    public bool isBlocked = false;
    public bool isLastHit = false;
    public List<BattleDmg> targetDamageList = new List<BattleDmg>();
    public List<BattleDmg> casterDamageList = new List<BattleDmg>();
    #region Initialization
    public BattleDmgInfo(string battleFieldGuid, BattleHurtParam battleHurtParam)
    {
        this.battleFieldGuid = battleFieldGuid;
        this.battleHurtParam = battleHurtParam;
        this.isLastHit = battleHurtParam.hitIndex >= battleHurtParam.skillConfig.DamageDivide.Length - 1;
        HandleDamageType();
        HandleAttackTypeAndDamage();
    }
    #endregion
    #region Damage Type Processing
    private void HandleDamageType()
    {
        if (hurt == null) return;
        int attackTypes = 0;
        foreach (ServerDamageType serverDamageType in System.Enum.GetValues(typeof(ServerDamageType)))
        {
@@ -59,111 +61,178 @@
        hurt.AttackTypes = (uint)attackTypes;
    }
    #endregion
    #region Damage List Generation
    private void HandleAttackTypeAndDamage()
    {
        isBlocked = HaveBlockDamage();
        int rawAttackType = (int)hurt.AttackTypes;
        float blockRatio = GeneralDefine.blockRatio; // 格挡减伤率
        int rawAttackType = hurt == null ? 0 : (int)hurt.AttackTypes;
        int maxCount = CalculateMaxDamageSegmentCount();
        // 处理每一段伤害及其对应的反伤和吸血
        for (int i = 0; i < damageList.Count; i++)
        for (int i = 0; i < maxCount; i++)
        {
            long actualDamage = damageList[i];
            // ============ 1. 先处理当前段对应的反伤 ============
            if (battleHurtParam.reflectHpList != null && i < battleHurtParam.reflectHpList.Count)
            {
                long reflectHp = battleHurtParam.reflectHpList[i];
                if (reflectHp > 0)
                {
                    casterDamageList.Add(new BattleDmg
                    {
                        damage = reflectHp,
                        attackType = (int)DamageType.Reflect
                    });
                }
            }
            // ============ 2. 然后处理当前段对应的吸血 ============
            if (battleHurtParam.suckHpList != null && i < battleHurtParam.suckHpList.Count)
            {
                long suckHp = battleHurtParam.suckHpList[i];
                if (suckHp > 0)
                {
                    casterDamageList.Add(new BattleDmg
                    {
                        damage = suckHp,
                        attackType = (int)DamageType.SuckHP
                    });
                }
            }
            // ============ 3. 最后处理主要伤害 ============
            // 格挡处理
            if (isBlocked)
            {
                // 去掉格挡类型
                int attackType = rawAttackType & (~(int)DamageType.Block);
                // 计算格挡伤害
                long totalDamage = (long)(actualDamage / (1 - blockRatio));
                long blockDmg = totalDamage - actualDamage;
                targetDamageList.Add(new BattleDmg { damage = blockDmg, attackType = (int)DamageType.Block });
                // 真实伤害特殊处理
                if (IsRealdamage())
                {
                    int showAttackType = (int)DamageType.Realdamage + (IsCrit() ? (int)DamageType.Crit : 0);
                    targetDamageList.Add(new BattleDmg { damage = actualDamage, attackType = showAttackType });
                }
                else
                {
                    // 普通伤害/治疗处理
                    if (DamageNumConfig.Get(attackType) == null)
                    {
                        UnityEngine.Debug.LogError($"服务器给的伤害类型不对,强制转换为普通伤害/治疗, attackType: {attackType}");
                        if ((attackType & (int)DamageType.Damage) != 0)
                            attackType = (int)DamageType.Damage;
                        else if ((attackType & (int)DamageType.Recovery) != 0)
                            attackType = (int)DamageType.Recovery;
                        else
                            UnityEngine.Debug.LogError($"强制转换失败,该类型不是治疗也不是伤害 {attackType}");
                    }
                    targetDamageList.Add(new BattleDmg { damage = actualDamage, attackType = attackType });
                }
            }
            else
            {
                int attackType = rawAttackType;
                // 真实伤害特殊处理
                if (IsRealdamage())
                {
                    int showAttackType = (int)DamageType.Realdamage + (IsCrit() ? (int)DamageType.Crit : 0);
                    targetDamageList.Add(new BattleDmg { damage = actualDamage, attackType = showAttackType });
                }
                else
                {
                    // 普通伤害/治疗处理
                    if (DamageNumConfig.Get(attackType) == null)
                    {
                        UnityEngine.Debug.LogError($"服务器给的伤害类型不对,强制转换为普通伤害/治疗, attackType: {attackType}");
                        if ((attackType & (int)DamageType.Damage) != 0)
                            attackType = (int)DamageType.Damage;
                        else if ((attackType & (int)DamageType.Recovery) != 0)
                            attackType = (int)DamageType.Recovery;
                        else
                            UnityEngine.Debug.LogError($"强制转换失败,该类型不是治疗也不是伤害 {attackType}");
                    }
                    targetDamageList.Add(new BattleDmg { damage = actualDamage, attackType = attackType });
                }
            }
            ProcessReflectDamage(i);
            ProcessSuckHpDamage(i);
            ProcessMainDamage(i, rawAttackType);
        }
    }
    /// <summary>
    /// 计算最大伤害段数
    /// </summary>
    private int CalculateMaxDamageSegmentCount()
    {
        int maxCount = damageList != null ? damageList.Count : 0;
        maxCount = Mathf.Max(maxCount, battleHurtParam.suckHpList != null ? battleHurtParam.suckHpList.Count : 0);
        maxCount = Mathf.Max(maxCount, battleHurtParam.reflectHpList != null ? battleHurtParam.reflectHpList.Count : 0);
        return maxCount;
    }
    /// <summary>
    /// 处理反伤伤害
    /// </summary>
    private void ProcessReflectDamage(int segmentIndex)
    {
        if (battleHurtParam.reflectHpList == null || segmentIndex >= battleHurtParam.reflectHpList.Count)
            return;
        long reflectHp = battleHurtParam.reflectHpList[segmentIndex];
        if (reflectHp > 0)
        {
            casterDamageList.Add(new BattleDmg
            {
                damage = reflectHp,
                attackType = (int)DamageType.Reflect
            });
        }
    }
    /// <summary>
    /// 处理吸血伤害
    /// </summary>
    private void ProcessSuckHpDamage(int segmentIndex)
    {
        if (battleHurtParam.suckHpList == null || segmentIndex >= battleHurtParam.suckHpList.Count)
            return;
        long suckHp = battleHurtParam.suckHpList[segmentIndex];
        if (suckHp > 0)
        {
            casterDamageList.Add(new BattleDmg
            {
                damage = suckHp,
                attackType = (int)DamageType.SuckHP
            });
        }
    }
    /// <summary>
    /// 处理主要伤害
    /// </summary>
    private void ProcessMainDamage(int segmentIndex, int rawAttackType)
    {
        if (damageList == null || segmentIndex >= damageList.Count)
            return;
        long actualDamage = damageList[segmentIndex];
        if (isBlocked)
        {
            ProcessBlockedDamage(actualDamage, rawAttackType);
        }
        else
        {
            ProcessNormalDamage(actualDamage, rawAttackType);
        }
    }
    /// <summary>
    /// 处理被格挡的伤害
    /// </summary>
    private void ProcessBlockedDamage(long actualDamage, int rawAttackType)
    {
        float blockRatio = GeneralDefine.blockRatio;
        int attackType = rawAttackType & (~(int)DamageType.Block);
        // 添加格挡伤害显示
        long totalDamage = (long)(actualDamage / (1 - blockRatio));
        long blockDmg = totalDamage - actualDamage;
        targetDamageList.Add(new BattleDmg { damage = blockDmg, attackType = (int)DamageType.Block });
        // 添加实际伤害显示
        if (IsRealdamage())
        {
            AddRealdamageToList(actualDamage);
        }
        else
        {
            AddNormalDamageToList(actualDamage, attackType);
        }
    }
    /// <summary>
    /// 处理正常伤害(未被格挡)
    /// </summary>
    private void ProcessNormalDamage(long actualDamage, int rawAttackType)
    {
        if (IsRealdamage())
        {
            AddRealdamageToList(actualDamage);
        }
        else
        {
            AddNormalDamageToList(actualDamage, rawAttackType);
        }
    }
    /// <summary>
    /// 添加真实伤害到列表
    /// </summary>
    private void AddRealdamageToList(long damage)
    {
        int showAttackType = (int)DamageType.Realdamage + (IsCrit() ? (int)DamageType.Crit : 0);
        targetDamageList.Add(new BattleDmg { damage = damage, attackType = showAttackType });
    }
    /// <summary>
    /// 添加普通伤害/治疗到列表
    /// </summary>
    private void AddNormalDamageToList(long damage, int attackType)
    {
        attackType = ValidateAndFixAttackType(attackType);
        targetDamageList.Add(new BattleDmg { damage = damage, attackType = attackType });
    }
    /// <summary>
    /// 验证并修复攻击类型
    /// </summary>
    private int ValidateAndFixAttackType(int attackType)
    {
        if (DamageNumConfig.Get(attackType) != null)
            return attackType;
        UnityEngine.Debug.LogError($"服务器给的伤害类型不对,强制转换为普通伤害/治疗, attackType: {attackType}");
        if ((attackType & (int)DamageType.Damage) != 0)
            return (int)DamageType.Damage;
        if ((attackType & (int)DamageType.Recovery) != 0)
            return (int)DamageType.Recovery;
        UnityEngine.Debug.LogError($"强制转换失败,该类型不是治疗也不是伤害 {attackType}");
        return attackType;
    }
    #endregion
    #region Type Checking
    public bool IsType(DamageType damageType)
    {
        return (hurt.AttackTypes & (int)damageType) == (int)damageType;
        return hurt != null && (hurt.AttackTypes & (int)damageType) == (int)damageType;
    }
    public bool IsCrit()
@@ -181,6 +250,12 @@
        return skillConfig.HurtType / 10 == 1;
    }
    public bool IsDamage()
    {
        return IsType(DamageType.Damage) || IsRealdamage() || IsType((DamageType)11) || IsType((DamageType)12);
    }
    #endregion
}
public class BattleHurtParam
@@ -191,17 +266,87 @@
    public List<long> suckHpList;
    public List<long> reflectHpList;
    public long fromShieldValue;
    public long toShieldValue;
    public long fromHp;
    public long toHp;
    public BattleDrops battleDrops;
    public HB427_tagSCUseSkill.tagSCUseSkillHurt hurt;
    public int hitIndex;
    public HB422_tagMCTurnFightObjDead deadPack;
    public SkillConfig skillConfig;
    public long maxHp;
    #region Shield Value Calculations
    public long MaxSheildValue
    {
        get
        {
            return hurtObj == null ? 0 : hurtObj.teamHero.maxHp;
        }
    }
    public long phase1FromShieldValue
    {
        get
        {
            if (fromShieldValue > 0)
            {
                return Mathf.Min((int)fromShieldValue, (int)MaxSheildValue);
            }
            else
            {
                return 0;
            }
        }
    }
    public long phase1ToShieldValue
    {
        get
        {
            if (toShieldValue > 0)
            {
                return Mathf.Min((int)toShieldValue, (int)MaxSheildValue);
            }
            else
            {
                return 0;
            }
        }
    }
    public long phase2FromShieldValue
    {
        get
        {
            if (fromShieldValue > MaxSheildValue)
            {
                return fromShieldValue - MaxSheildValue;
            }
            else
            {
                return 0;
            }
        }
    }
    public long phase2ToShieldValue
    {
        get
        {
            if (toShieldValue > MaxSheildValue)
            {
                return toShieldValue - MaxSheildValue;
            }
            else
            {
                return 0;
            }
        }
    }
    #endregion
}
Main/System/Battle/Define/DamageType.cs
@@ -12,11 +12,23 @@
//    服务器拥有的DamageType
public enum ServerDamageType
{
    Damage = 2,
    Recovery = 4,
    Block = 32,
    Crit = 128,
    Dodge = 512
    Damage = 2,//普通伤害
    Recovery = 4,//治疗
    Immune = 16,//免疫
    Block = 32, //格挡
    Realdamage = 64, //真伤
    Crit = 128, //暴击
    Dodge = 256, //闪避
    DamageReverse = 512,//伤害反转为治疗
    SuckHpReverse = 1024,//吸血反转为伤害
    SelfHarm = 2048,//自残
}
public enum DamageType
Main/System/Battle/Skill/SkillBase.cs
@@ -390,18 +390,18 @@
        List<BattleObject> targetList = battleField.battleObjMgr.GetBattleObjList(tagUseSkillAttack);
        List<BattleObject> highlightList = new List<BattleObject>(targetList) { caster };
        List<BattleObject> allList = battleField.battleObjMgr.allBattleObjDict.Values.ToList<BattleObject>();
        // 修复:使用HashSet优化性能,避免重复设置
        var targetSet = new HashSet<BattleObject>(targetList);
        var highlightSet = new HashSet<BattleObject>(highlightList);
        caster.heroInfoBar.SetActive(false);
        foreach (BattleObject bo in allList)
        {
            bool isHighlight = highlightSet.Contains(bo);
            bool isTarget = targetSet.Contains(bo);
            if (isHighlight)
            {
                bo.layerMgr.SetFront();
@@ -418,14 +418,23 @@
        // battleField.battleRootNode.SetSortingOrder();
    }
    protected long suckHp = 0;
    // 命中目标回调:处理所有被命中的目标
    protected virtual void OnHitTargets(int _hitIndex, List<HB427_tagSCUseSkill.tagSCUseSkillHurt> hitList)
    {
        //  造成伤害前先处理血量刷新包
        HandleRefreshHP();
        suckHp = 0;
        foreach (var hurt in hitList)
        {
            suckHp += hurt.SuckHP;
        }
        foreach (var hurt in hitList)
        {
            BattleObject target = caster.battleField.battleObjMgr.GetBattleObject((int)hurt.ObjID);
            if (target == null)
            {
@@ -436,6 +445,46 @@
            OnHitEachTarget(_hitIndex, target, hurt);
        }
    }
    // protected void RecoveryHp(long suckHp, int _hitIndex)
    // {
    //     // long suckHp = hurt.SuckHP;
    //     if (suckHp <= 0)
    //     {
    //         return;
    //     }
    //     List<long> suckHpList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, suckHp);
    //     long currentHitSuckHp = 0;
    //     foreach (long suck in suckHpList)
    //     {
    //         currentHitSuckHp += suck;
    //     }
    //     long fromHp = caster.teamHero.curHp;
    //     long toHp = caster.teamHero.curHp + currentHitSuckHp;
    //             //  参数打包
    //     BattleHurtParam hurtParam = new BattleHurtParam()
    //     {
    //         casterObj = caster,
    //         hurtObj = null,
    //         damageList = new List<long>(),
    //         suckHpList = suckHpList,
    //         reflectHpList = new List<long>(),
    //         fromHp = fromHp,
    //         toHp = toHp,
    //         battleDrops = null,
    //         hurt = null,
    //         hitIndex = _hitIndex,
    //         deadPack = null,
    //         skillConfig = skillConfig
    //     };
    //     caster.Hurt(hurtParam);
    // }
    // 处理单个目标被命中:应用伤害和施法者效果
    protected virtual void OnHitEachTarget(int _hitIndex, BattleObject target, HB427_tagSCUseSkill.tagSCUseSkillHurt hurt)
@@ -463,17 +512,20 @@
        long totalDamage = GeneralDefine.GetFactValue(hurt.HurtHP, hurt.HurtHPEx);
        List<long> damageList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, totalDamage);
        long totalSuckHp = suckHp;
        List<long> suckHpList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, totalSuckHp);
        // ============ 第二步:刷新实际血量 ============
        long fromHp = target.teamHero.curHp;
        
        // ============处理吸血跟反伤 ===============
        // ============处理反伤 ===============
        //  也要按每一击平均算 最后要补齐伤害
        long suckHp = hurt.SuckHP;
        long reflectHp = hurt.BounceHP;
        List<long> suckHpList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, suckHp);
        List<long> reflectHpList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, reflectHp);
        // long currentSuckHp = suckHp / tagUseSkillAttack.HurtCount;
@@ -485,11 +537,7 @@
            currentHitDamage += dmg;
        }
        long currentHitSuckHp = 0;
        foreach (long suck in suckHpList)
        {
            currentHitSuckHp += suck;
        }
        long currentHitReflectHp = 0;
        foreach (long reflect in reflectHpList)
@@ -497,7 +545,50 @@
            currentHitReflectHp += reflect;
        }
        long toHp = Math.Max(0, fromHp - currentHitDamage);
        long shieldValue = target.buffMgr.GetShieldValue();
        long remainSheild = shieldValue - currentHitDamage;
        long toShieldValue = Math.Max(0, shieldValue - currentHitDamage);
        long shieldRecieveDamage = shieldValue - toShieldValue;
        // if (shieldValue > 0 && remainSheild >= 0)
        // {
        //     currentHitDamage = 0;
        //     target.buffMgr.ReduceShieldValue(currentHitDamage);
        // }
        // else if (shieldValue > 0 && remainSheild < 0)
        // {
        //     currentHitDamage = Math.Abs(remainSheild);
        //     target.buffMgr.ClearShield();
        // }
        long toHp = fromHp; //Math.Max(0, fromHp - currentHitDamage);
        // 判断是治疗还是伤害
        bool isHealing = ((hurt.AttackTypes & (int)ServerDamageType.Recovery) != 0 ||
                          (hurt.AttackTypes & (int)ServerDamageType.DamageReverse) != 0) &&
                         (hurt.AttackTypes & (int)ServerDamageType.Damage) == 0 &&
                         (hurt.AttackTypes & (int)ServerDamageType.Realdamage) == 0 &&
                         (hurt.AttackTypes & (int)ServerDamageType.SuckHpReverse) == 0 &&
                         (hurt.AttackTypes & (int)ServerDamageType.SelfHarm) == 0;
        if (isHealing)
        {
            // 治疗逻辑:直接加血,不考虑护盾
            toHp = Math.Min(target.teamHero.maxHp, fromHp + currentHitDamage);
        }
        else
        {
            // 伤害逻辑:先扣护盾,再扣血
            if (remainSheild < 0)
            {
                // 盾被打破
                toHp = Math.Max(0, fromHp + remainSheild);
            }
        }
        // 更新目标血量
@@ -505,12 +596,14 @@
#if UNITY_EDITOR
        BattleDebug.LogError(
            (caster.Camp == BattleCamp.Red ? "【红方行动】" : "【蓝方行动】") + "\n" +
            (caster.Camp == BattleCamp.Red ? "【红方行动】" : "【蓝方行动】 ") +
            $"攻击者: {caster.teamHero.name}\n" +
            $"目标: {target.teamHero.name}\n" +
            $"技能: {skillConfig.SkillName} (第{_hitIndex}击)\n" +
            $"伤害: {currentHitDamage} (总伤害: {totalDamage})\n" +
            $"吸血: {currentHitSuckHp}\n" +
            $"伤害: {currentHitDamage} 实际受到伤害: {fromHp-toHp} (总伤害: {totalDamage})\n" +
            $"承伤前护盾值: {shieldValue}\n" +
            $"承伤后护盾值: {toShieldValue}\n" +
            $"护盾承受伤害: {shieldRecieveDamage}\n" +
            $"反伤: {currentHitReflectHp}\n" +
            $"血量变化: {fromHp} -> {toHp}\n" +
            $"技能包里的血量是: {GeneralDefine.GetFactValue(hurt.CurHP, hurt.CurHPEx)}\n"
@@ -532,10 +625,13 @@
            casterObj = caster,
            hurtObj = target,
            damageList = damageList,
            suckHpList = suckHpList,
            suckHpList = suckHpList, //suckHpList,
            reflectHpList = reflectHpList,
            fromHp = fromHp,
            toHp = toHp,
            maxHp = target.teamHero.maxHp,
            fromShieldValue = shieldValue,
            toShieldValue = toShieldValue,
            battleDrops = battleDrops,
            hurt = hurt,
            hitIndex = _hitIndex,
Main/System/Battle/UIComp/BattleFloatingUIController.cs
@@ -288,7 +288,7 @@
            applyColorCallback?.Invoke(GetEndColor());
        }
    }
    /// <summary>
    /// 动画完成回调
    /// </summary>
@@ -299,5 +299,10 @@
        onFinishCallback = null;
    }
    
    public bool IsValid()
    {
        return ValidateConfig() && rectTransform != null && gameObject != null;
    }
    #endregion
}
Main/System/Battle/UIComp/BattleHeroInfoBar.cs
@@ -10,8 +10,6 @@
/// </summary>
public class BattleHeroInfoBar : MonoBehaviour
{
    #region 内部类
    /// <summary>
    /// 飘字信息配置
    /// </summary>
@@ -26,13 +24,23 @@
        public bool isDebuff = false;      // 是否是负向 Buff(决定用哪个颜色)
    }
    
    #endregion
    #region Inspector字段
    /// <summary>
    /// 血条更新请求
    /// </summary>
    private class HpUpdateRequest
    {
        public long fromHp;
        public long toHp;
        public long maxHp;
        public bool tween;
    }
    
    [Header("UI Components")]
    public Slider sliderHp;
    public Slider sliderXp;
    public GameObject maxXpGO;
    public Slider sliderShield1;
    public Slider sliderShield2;
    public BasicHeroInfoContainer heroInfoContainer;
    public BattleTips textTips;
    
@@ -46,15 +54,7 @@
    [Tooltip("不跟随角色的飘字配置(固定在战场节点)")]
    public FloatingConfig noFollowFloatingConfig;
    
    [Header("Settings")]
    public float PopUpInterval = 0.2f;
    #endregion
    #region 私有字段
    protected BattleObject battleObject;
    protected float timer = 0f;
    
    protected List<TipsInfo> messages = new List<TipsInfo>();
    protected List<BattleTips> tipsList = new List<BattleTips>();
@@ -62,19 +62,21 @@
    
    protected Tween hpTween;
    protected Tween xpTween;
    protected Tween shieldTween1;
    protected Tween shieldTween2;
    protected Sequence damageSequence;
    
    #endregion
    #region Unity生命周期
    private Queue<HpUpdateRequest> hpUpdateQueue = new Queue<HpUpdateRequest>();
    private Queue<BattleDmgInfo> damageUpdateQueue = new Queue<BattleDmgInfo>();
    // 飘字GCD相关
    private float tipsGCDTimer = 0f;
    private const int TIPS_GCD_FRAMES = 5;
    
    protected void OnDisable()
    {
        CleanupTips();
    }
    #endregion
    #region 公共方法 - 初始化
    
    public void SetBattleObject(BattleObject _battleObject)
    {
@@ -83,16 +85,19 @@
        RefreshBuff(battleObject.buffMgr.GetBuffList());
        UpdateHP(battleObject.teamHero.curHp, battleObject.teamHero.curHp, battleObject.teamHero.maxHp, false);
        UpdateXP(battleObject.teamHero.rage, battleObject.teamHero.rage, 100, false);
        long shieldValue = battleObject.buffMgr.GetShieldValue();
        long maxHp = battleObject.teamHero.maxHp;
        //  第一条护盾的值最大值是当前的MaxHp 第二条护盾的最大值其实也是MaxHp 多余的不做显示
        sliderShield1.value = maxHp > 0 ? Mathf.Min((float)shieldValue, (float)maxHp) / (float)maxHp : 0;
        sliderShield2.value = maxHp > 0 ? Mathf.Max((float)(shieldValue - maxHp), 0f) / (float)maxHp : 0;
    }
    
    public void SetActive(bool active)
    {
        gameObject.SetActive(active);
    }
    #endregion
    #region 公共方法 - Buff管理
    
    public void RefreshBuff(List<HB428_tagSCBuffRefresh> datas)
    {
@@ -111,11 +116,14 @@
                buffCells[i].SetActive(false);
            }
        }
    }
    #endregion
    #region 公共方法 - 飘字管理
        //  check shield buff
        long shieldValue = battleObject.buffMgr.GetShieldValue();
        long maxHp = battleObject.teamHero.maxHp;
        //  第一条护盾的值最大值是当前的MaxHp 第二条护盾的最大值其实也是MaxHp 多余的不做显示
        sliderShield1.value = maxHp > 0 ? Mathf.Min((float)shieldValue, (float)maxHp) / (float)maxHp : 0;
        sliderShield2.value = maxHp > 0 ? Mathf.Max((float)(shieldValue - maxHp), 0f) / (float)maxHp : 0;
    }
    
    /// <summary>
    /// 添加飘字到队列
@@ -139,24 +147,34 @@
        messages.Add(tipsInfo);
    }
    
    #endregion
    #region 公共方法 - 数值更新
    /// <summary>
    /// 更新血量显示
    /// </summary>
    public void UpdateHP(long fromHp, long toHp, long maxHp, bool tween = true)
    {
        // 加入队列
        hpUpdateQueue.Enqueue(new HpUpdateRequest
        {
            fromHp = fromHp,
            toHp = toHp,
            maxHp = maxHp,
            tween = tween
        });
    }
    /// <summary>
    /// 实际执行血量更新
    /// </summary>
    private void ExecuteHpUpdate(HpUpdateRequest request)
    {
        KillTween(ref hpTween);
        
        float fromValue = (float)fromHp / (float)maxHp;
        float targetValue = (float)toHp / (float)maxHp;
        float fromValue = (float)request.fromHp / (float)request.maxHp;
        float targetValue = (float)request.toHp / (float)request.maxHp;
        
        if (tween)
        if (request.tween)
        {
            // 关键修复:先设置起始值,再播放动画到目标值
            sliderHp.value = fromValue;  // ← 这行是关键!
            sliderHp.value = fromValue;
            hpTween = sliderHp.DOValue(targetValue, 0.3f).SetAutoKill(false);
            battleObject.battleField.battleTweenMgr.OnPlayTween(hpTween);
        }
@@ -181,37 +199,116 @@
    public void UpdateXP(long fromXp, long toXp, long maxXp, bool tween = true)
    {
        KillTween(ref xpTween);
        float fromValue = (float)fromXp / (float)maxXp;
        float targetValue = (float)toXp / (float)maxXp;
        if (tween)
        {
            // 同样的修复
            sliderXp.value = fromValue;
            xpTween = sliderXp.DOValue(targetValue, 0.2f).SetAutoKill(false);
            xpTween.OnComplete(() =>
            {
                if (toXp >= maxXp)
                {
                    maxXpGO.SetActive(true);
                }
                else
                {
                    maxXpGO.SetActive(false);
                }
            });
            battleObject.battleField.battleTweenMgr.OnPlayTween(xpTween);
        }
        else
        {
            if (toXp >= maxXp)
            {
                maxXpGO.SetActive(true);
            }
            else
            {
                maxXpGO.SetActive(false);
            }
            sliderXp.value = targetValue;
        }
    }
    #endregion
    #region 公共方法 - 运行时更新
    /// <summary>
    /// 播放血条 护盾的变化
    /// </summary>
    public void UpdateDamage(BattleDmgInfo dmgInfo)
    {
        // 加入队列
        damageUpdateQueue.Enqueue(dmgInfo);
    }
    /// <summary>
    /// 实际执行伤害更新
    /// </summary>
    private void ExecuteDamageUpdate(BattleDmgInfo dmgInfo)
    {
        KillTween(ref damageSequence);
        long maxHp = dmgInfo.battleHurtParam.maxHp;
        long fromShield = dmgInfo.battleHurtParam.fromShieldValue;
        long toShield = dmgInfo.battleHurtParam.toShieldValue;
        if (maxHp <= 0)
        {
            sliderShield1.value = 0;
            sliderShield2.value = 0;
            return;
        }
        //  第一条护盾的值最大值是当前的MaxHp 第二条护盾的最大值其实也是MaxHp 多余的不做显示
        float fromValue1 = (float)dmgInfo.battleHurtParam.phase1FromShieldValue / (float)maxHp;
        float targetValue1 = (float)dmgInfo.battleHurtParam.phase1ToShieldValue / (float)maxHp;
        float fromValue2 = (float)dmgInfo.battleHurtParam.phase2FromShieldValue / (float)maxHp;
        float targetValue2 = (float)dmgInfo.battleHurtParam.phase2ToShieldValue / (float)maxHp;
        damageSequence = DOTween.Sequence();
        sliderShield2.value = fromValue2;
        if (fromValue2 > 0  && fromValue2 != targetValue2)
        {
            damageSequence.Append(sliderShield2.DOValue(targetValue2, 0.2f));
        }
        sliderShield1.value = fromValue1;
        if (fromValue1 > 0 && fromValue1 != targetValue1)
        {
            damageSequence.Append(sliderShield1.DOValue(targetValue1, 0.2f));
        }
        if (dmgInfo.battleHurtParam.fromHp != dmgInfo.battleHurtParam.toHp)
        {
            float fromHpValue = (float)dmgInfo.battleHurtParam.fromHp / (float)maxHp;
            float toHpValue = (float)dmgInfo.battleHurtParam.toHp / (float)maxHp;
            sliderHp.value = fromHpValue;
            damageSequence.Append(sliderHp.DOValue(toHpValue, 0.2f));
        }
        damageSequence.Play();
        battleObject.battleField.battleTweenMgr.OnPlayTween(damageSequence);
    }
    
    /// <summary>
    /// 每帧更新
    /// </summary>
    public void Run()
    {
        // 处理血条和伤害队列
        UpdateHpAndDamageQueue();
        // 更新飘字GCD并处理队列
        UpdateTipsGCDAndQueue();
        // 更新所有飘字
        UpdateActiveTips();
        // 处理飘字队列
        ProcessTipsQueue();
    }
    /// <summary>
@@ -225,9 +322,81 @@
        }
    }
    
    #endregion
    /// <summary>
    /// 处理血条和伤害更新队列
    /// </summary>
    private void UpdateHpAndDamageQueue()
    {
        // 优先处理UpdateDamage
        if (damageUpdateQueue.Count > 0)
        {
            BattleDmgInfo dmgInfo = damageUpdateQueue.Dequeue();
            ExecuteDamageUpdate(dmgInfo);
            return;
        }
        // 其次处理UpdateHP
        else if (hpUpdateQueue.Count > 0)
        {
            HpUpdateRequest request = hpUpdateQueue.Dequeue();
            ExecuteHpUpdate(request);
            return;
        }
    }
    /// <summary>
    /// 更新飘字GCD并处理队列
    /// </summary>
    private void UpdateTipsGCDAndQueue()
    {
        // 更新GCD计时器
        if (tipsGCDTimer > 0f)
        {
            float speedRatio = GetCurrentSpeedRatio();
            float deltaTime = 1f / (float)BattleConst.skillMotionFps * speedRatio;
            tipsGCDTimer -= deltaTime;
            if (tipsGCDTimer < 0f)
            {
                tipsGCDTimer = 0f;
            }
        }
        // 如果GCD结束且有待处理的飘字,弹出一个
        if (tipsGCDTimer <= 0f && messages.Count > 0)
        {
            TipsInfo tipsInfo = messages[0];
            messages.RemoveAt(0);
            PopUpTipsDirectly(tipsInfo);
            // 重置GCD
            ResetTipsGCD();
        }
    }
    #region 私有方法 - 飘字处理
    /// <summary>
    /// 重置飘字GCD计时器
    /// </summary>
    private void ResetTipsGCD()
    {
        float speedRatio = GetCurrentSpeedRatio();
        float frameTime = 1f / (float)BattleConst.skillMotionFps;
        tipsGCDTimer = frameTime * TIPS_GCD_FRAMES / speedRatio;
    }
    /// <summary>
    /// 获取当前速度倍率
    /// </summary>
    private float GetCurrentSpeedRatio()
    {
        // 回退到战场速度
        if (battleObject != null && battleObject.battleField != null)
        {
            return battleObject.battleField.speedRatio;
        }
        return 1f;
    }
    
    /// <summary>
    /// 立即弹出飘字
@@ -236,24 +405,23 @@
    {
        // 创建飘字实例
        BattleTips tips = CreateTipsInstance(tipsInfo);
        // 配置飘字
        ConfigureTips(tips, tipsInfo);
        // 设置位置(如果不跟随)
        if (!tipsInfo.followCharacter)
        {
            SetNonFollowPosition(tips);
        }
        // 设置参数并显示
        tips.SetRatio(battleObject.battleField.speedRatio, tipsInfo.scaleRatio);
        tips.SetText(tipsInfo.message, tipsInfo.useArtText, false); // 移除 textColor 参数
        tips.ShowBackground(tipsInfo.showBackground);
        // 注册完成回调
        tips.OnFinish = () => RemoveTips(tips);
        tips.SetRatio(battleObject.battleField.speedRatio, tipsInfo.scaleRatio);
        tips.ShowBackground(tipsInfo.showBackground);
        tips.SetText(tipsInfo.message, tipsInfo.useArtText, false);
        // 添加到列表
        tipsList.Add(tips);
    }
@@ -327,6 +495,7 @@
    private void RemoveTips(BattleTips tips)
    {
        tipsList.Remove(tips);
        tips.controller = null;
        GameObject.DestroyImmediate(tips.gameObject);
    }
    
@@ -337,25 +506,13 @@
    {
        for (int i = tipsList.Count - 1; i >= 0; i--)
        {
            if (tipsList[i].gameObject == null)
            {
                var instanceid = tipsList[i].gameObject.GetInstanceID();
                tipsList.RemoveAt(i);
                continue;
            }
            tipsList[i].Run();
        }
    }
    /// <summary>
    /// 处理飘字队列
    /// </summary>
    private void ProcessTipsQueue()
    {
        timer += GetDeltaTime();
        if (messages.Count > 0 && timer >= PopUpInterval)
        {
            TipsInfo tipsInfo = messages[0];
            messages.RemoveAt(0);
            PopUpTipsDirectly(tipsInfo);
            timer = 0f;
        }
    }
    
@@ -375,14 +532,10 @@
        tipsList.Clear();
    }
    
    #endregion
    #region 私有方法 - 辅助方法
    /// <summary>
    /// 停止并清理Tween
    /// </summary>
    private void KillTween(ref Tween tween)
    private void KillTween<T>(ref T tween) where T : Tween
    {
        if (tween != null && battleObject != null)
        {
@@ -406,6 +559,4 @@
    {
        // TODO: 显示buff描述/当前身上所有buff
    }
    #endregion
}
Main/System/Battle/UIComp/BattleTips.cs
@@ -35,7 +35,7 @@
    #region 私有字段
    
    // 移除 [SerializeField],controller 不应该被序列化
    private BattleFloatingUIController controller;
    public BattleFloatingUIController controller;
    
    #endregion
@@ -165,8 +165,6 @@
    /// </summary>
    private void InitController()
    {
        if (controller != null) return;
        if (floatingConfig == null)
        {
            Debug.LogError($"[BattleTips] FloatingConfig 未配置! GameObject: {gameObject.name}");
@@ -186,7 +184,7 @@
    /// </summary>
    private void EnsureControllerInitialized()
    {
        if (controller == null)
        if (controller == null || !controller.IsValid())
            InitController();
    }
    
Main/System/Battle/UIComp/DamageContent.cs
@@ -3,6 +3,7 @@
using UnityEngine;
using System;
using Cysharp.Threading.Tasks;
using DG.Tweening;
public class DamageContent : MonoBehaviour, IBattleFloatingUI
{
@@ -17,10 +18,16 @@
    private BattleDmgInfo battleDmgInfo;
    private BattleFloatingUIController controller;
    #region Unity Lifecycle
    void Awake()
    {
        line.SetActive(false);
    }
    #endregion
    #region Controller Management
    private void InitController()
    {
@@ -42,6 +49,19 @@
        controller?.SetRatio(speed, scale);
    }
    public void SetFloatingConfig(FloatingConfig config)
    {
        floatingConfig = config;
        if (controller != null)
        {
            controller.SetConfig(config);
        }
    }
    #endregion
    #region Position Management
    /// <summary>
    /// 设置飘字的起点和终点位置(运行时动态设置)
    /// </summary>
@@ -51,30 +71,95 @@
        controller?.SetRuntimePosition(beginPos, endPos);
    }
    public async void SetDamage(BattleDmgInfo _battleDmgInfo, List<BattleDmg> damages, Action _onComplete)
    #endregion
    #region Damage Display
    public void SetDamage(BattleDmgInfo _battleDmgInfo, List<BattleDmg> damages, Action _onComplete)
    {
        battleDmgInfo = _battleDmgInfo;
        for (int i = damages.Count; i < damageLineList.Count; i++)
        EnsureDamageLineCapacity(damages.Count);
        DisplayDamageLines(damages);
        HideExcessDamageLines(damages.Count);
        bool isCrit = battleDmgInfo.IsCrit();
        Play(isCrit, _onComplete);
    }
    /// <summary>
    /// 确保有足够的DamageLine对象
    /// </summary>
    private void EnsureDamageLineCapacity(int requiredCount)
    {
        RectTransform lineTemplate = line.GetComponent<RectTransform>();
        Vector2 templateAnchorMin = lineTemplate.anchorMin;
        Vector2 templateAnchorMax = lineTemplate.anchorMax;
        Vector2 templatePivot = lineTemplate.pivot;
        for (int i = damageLineList.Count; i < requiredCount; i++)
        {
            GameObject newLine = GameObject.Instantiate(line, parent);
            DamageLine damageLine = newLine.GetComponent<DamageLine>();
            RectTransform newLineRect = newLine.GetComponent<RectTransform>();
            if (newLineRect != null)
            {
                newLineRect.anchorMin = templateAnchorMin;
                newLineRect.anchorMax = templateAnchorMax;
                newLineRect.pivot = templatePivot;
                newLineRect.anchoredPosition = Vector2.zero;
                newLineRect.localScale = Vector3.one;
            }
            damageLineList.Add(damageLine);
        }
    }
    /// <summary>
    /// 显示伤害行并设置位置
    /// </summary>
    private void DisplayDamageLines(List<BattleDmg> damages)
    {
        for (int i = 0; i < damages.Count; i++)
        {
            DamageLine damageLine = damageLineList[i];
            SetDamageLinePosition(damageLine, i);
            damageLine.SetActive(true);
            damageLine.SetDamage(damages[i]);
        }
    }
    /// <summary>
    /// 设置单个伤害行的位置(使用Y轴偏移)
    /// </summary>
    private void SetDamageLinePosition(DamageLine damageLine, int index)
    {
        RectTransform lineRect = damageLine.GetComponent<RectTransform>();
        if (lineRect == null) return;
        RectTransform lineTemplate = line.GetComponent<RectTransform>();
        Vector2 basePos = lineTemplate.anchoredPosition;
        Vector2 pos = basePos;
        pos.y += 60f * index;
        lineRect.anchoredPosition = pos;
    }
    /// <summary>
    /// 隐藏多余的伤害行
    /// </summary>
    private void HideExcessDamageLines(int displayCount)
    {
        for (int i = displayCount; i < damageLineList.Count; i++)
        {
            damageLineList[i].SetActive(false);
        }
        // 使用控制器的Play方法
        bool isCrit = battleDmgInfo.IsCrit();
        Play(isCrit, _onComplete);
        for (int i = 0; i < damages.Count; i++)
        {
            if (i >= damageLineList.Count)
            {
                GameObject newLine = GameObject.Instantiate(line, parent);
                damageLineList.Add(newLine.GetComponent<DamageLine>());
            }
            damageLineList[i].SetActive(true);
            damageLineList[i].SetDamage(damages[i]);
            await UniTask.Delay(100);
        }
    }
    #endregion
    #region Animation Control
    public void Play(bool isCrit, Action onComplete = null)
    {
@@ -100,6 +185,10 @@
        controller.Resume();
    }
    #endregion
    #region Visual Effects
    private void ApplyColor(Color color)
    {
        for (int i = 0; i < damageLineList.Count; i++)
@@ -111,13 +200,5 @@
        }
    }
    // 运行时更新配置
    public void SetFloatingConfig(FloatingConfig config)
    {
        floatingConfig = config;
        if (controller != null)
        {
            controller.SetConfig(config);
        }
    }
    #endregion
}