Main/System/Battle/UIComp/BattleHeroInfoBar.cs
@@ -10,8 +10,6 @@
/// </summary>
public class BattleHeroInfoBar : MonoBehaviour
{
    #region 内部类
    /// <summary>
    /// 飘字信息配置
    /// </summary>
@@ -24,15 +22,27 @@
        public bool showBackground = false;
        public bool useBuffColor = false;  // 是否使用 Buff 颜色(从 FloatingConfig 读取)
        public bool isDebuff = false;      // 是否是负向 Buff(决定用哪个颜色)
        public bool isRage = false;
    }
    
    #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;
    
@@ -45,16 +55,10 @@
    public FloatingConfig followFloatingConfig;
    [Tooltip("不跟随角色的飘字配置(固定在战场节点)")]
    public FloatingConfig noFollowFloatingConfig;
    [Header("Settings")]
    public float PopUpInterval = 0.2f;
    #endregion
    #region 私有字段
    public FloatingConfig rageFloatingConfig;
    
    protected BattleObject battleObject;
    protected float timer = 0f;
    
    protected List<TipsInfo> messages = new List<TipsInfo>();
    protected List<BattleTips> tipsList = new List<BattleTips>();
@@ -62,19 +66,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 +89,24 @@
        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 curHp = battleObject.teamHero.curHp;
        long maxHp = battleObject.teamHero.maxHp;
        // 护盾1的值 = min(当前血量 + 护盾值, maxHp) / maxHp
        float shield1Value = maxHp > 0 ? Mathf.Min((float)(curHp + shieldValue), (float)maxHp) / (float)maxHp : 0;
        // 护盾2的值 = max(当前血量 + 护盾值 - maxHp, 0) / maxHp
        float shield2Value = maxHp > 0 ? Mathf.Max((float)(curHp + shieldValue - maxHp), 0f) / (float)maxHp : 0;
        sliderShield1.value = shield1Value;
        sliderShield2.value = shield2Value;
    }
    
    public void SetActive(bool active)
    {
        gameObject.SetActive(active);
    }
    #endregion
    #region 公共方法 - Buff管理
    
    public void RefreshBuff(List<HB428_tagSCBuffRefresh> datas)
    {
@@ -111,11 +125,20 @@
                buffCells[i].SetActive(false);
            }
        }
    }
    #endregion
    #region 公共方法 - 飘字管理
        // check shield buff
        long shieldValue = battleObject.buffMgr.GetShieldValue();
        long curHp = battleObject.teamHero.curHp;
        long maxHp = battleObject.teamHero.maxHp;
        // 护盾1的值 = min(当前血量 + 护盾值, maxHp) / maxHp
        float shield1Value = maxHp > 0 ? Mathf.Min((float)(curHp + shieldValue), (float)maxHp) / (float)maxHp : 0;
        // 护盾2的值 = max(当前血量 + 护盾值 - maxHp, 0) / maxHp
        float shield2Value = maxHp > 0 ? Mathf.Max((float)(curHp + shieldValue - maxHp), 0f) / (float)maxHp : 0;
        sliderShield1.value = shield1Value;
        sliderShield2.value = shield2Value;
    }
    
    /// <summary>
    /// 添加飘字到队列
@@ -139,24 +162,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);
        }
@@ -172,6 +205,9 @@
    public void UpdateHP(float value)
    {
        sliderHp.value = value; 
        bool IsTianziBoss = battleObject.IsTianziBoss();
        sliderShield1.SetActive(!IsTianziBoss);
        sliderShield2.SetActive(!IsTianziBoss);
        //Debug.Log("TianziDamageBar UpdateHP value:" + value);
    }
@@ -181,37 +217,159 @@
    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);
        // 判断是目标视角还是施法者视角,使用对应的数据
        bool isCasterView = dmgInfo.hurtObj == dmgInfo.casterObj;
        long maxHp, fromHp, toHp, fromShield, toShield;
        if (isCasterView)
        {
            // 施法者视角:使用 caster 数据
            BattleCastObj caster = dmgInfo.battleHurtParam.caster;
            maxHp = caster.maxHp;
            fromHp = caster.fromHp;
            toHp = caster.toHp;
            fromShield = caster.fromShieldValue;
            toShield = caster.toShieldValue;
        }
        else
        {
            // 目标视角:使用 hurter 数据
            BattleHurtObj hurter = dmgInfo.battleHurtParam.hurter;
            maxHp = hurter.maxHp;
            fromHp = hurter.fromHp;
            toHp = hurter.toHp;
            fromShield = hurter.fromShieldValue;
            toShield = hurter.toShieldValue;
        }
        if (maxHp <= 0)
        {
            sliderShield1.value = 0;
            sliderShield2.value = 0;
            return;
        }
        damageSequence = DOTween.Sequence();
        bool IsTianziBoss = battleObject.IsTianziBoss();
        // 护盾动画
        if (fromShield > 0 && !IsTianziBoss)
        {
            float fromShield1Value = Mathf.Min((float)(fromHp + fromShield), (float)maxHp) / (float)maxHp;
            float fromShield2Value = Mathf.Max((float)(fromHp + fromShield - maxHp), 0f) / (float)maxHp;
            sliderShield1.value = fromShield1Value;
            sliderShield2.value = fromShield2Value;
            // 护盾2动画
            if (fromShield2Value > 0)
            {
                float toShield2Value = Mathf.Max((float)(toHp + toShield - maxHp), 0f) / (float)maxHp;
                if (Mathf.Abs(fromShield2Value - toShield2Value) > 0.001f)
                {
                    damageSequence.Append(sliderShield2.DOValue(toShield2Value, 0.2f));
                }
            }
            // 护盾1动画
            if (fromShield1Value > 0)
            {
                float toShield1Value = Mathf.Min((float)(toHp + toShield), (float)maxHp) / (float)maxHp;
                if (Mathf.Abs(fromShield1Value - toShield1Value) > 0.001f)
                {
                    damageSequence.Append(sliderShield1.DOValue(toShield1Value, 0.2f));
                }
            }
        }
        else
        {
            sliderShield1.value = 0f;
            sliderShield2.value = 0f;
        }
        // 血量动画
        float fromHpValue = (float)fromHp / (float)maxHp;
        float toHpValue = (float)toHp / (float)maxHp;
        sliderHp.value = fromHpValue;
        if (Mathf.Abs(fromHpValue - toHpValue) > 0.001f)
        {
            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 +383,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 +466,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);
    }
@@ -276,7 +505,7 @@
    /// </summary>
    private void ConfigureTips(BattleTips tips, TipsInfo tipsInfo)
    {
        FloatingConfig targetConfig = tipsInfo.followCharacter
        FloatingConfig targetConfig = tipsInfo.isRage ? rageFloatingConfig : tipsInfo.followCharacter
            ? followFloatingConfig 
            : noFollowFloatingConfig;
        
@@ -327,6 +556,7 @@
    private void RemoveTips(BattleTips tips)
    {
        tipsList.Remove(tips);
        tips.controller = null;
        GameObject.DestroyImmediate(tips.gameObject);
    }
    
@@ -337,25 +567,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 +593,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 +620,4 @@
    {
        // TODO: 显示buff描述/当前身上所有buff
    }
    #endregion
}