lcy
2025-11-06 09bc892c7283df8757a07b646d5af21ddaa263d1
Main/System/Battle/UIComp/BattleHeroInfoBar.cs
@@ -4,161 +4,408 @@
using System;
using DG.Tweening;
/// <summary>
/// 战斗角色信息栏
/// 职责:显示角色血条、怒气、Buff和飘字提示
/// </summary>
public class BattleHeroInfoBar : MonoBehaviour
{
    protected BattleObject battleObject;
    #region 内部类
    /// <summary>
    /// 飘字信息配置
    /// </summary>
    public class TipsInfo
    {
        public string message;
        public bool useArtText;
        public bool followCharacter;
        public float scaleRatio;
        public bool showBackground = false;
        public bool useBuffColor = false;  // 是否使用 Buff 颜色(从 FloatingConfig 读取)
        public bool isDebuff = false;      // 是否是负向 Buff(决定用哪个颜色)
    }
    #endregion
    #region Inspector字段
    [Header("UI Components")]
    public Slider sliderHp;
    public Slider sliderXp; //怒气
    protected float timer = 0f;
    public float PopUpInterval = 0.2f;
    // public List<BattleBuffCell> buffCells = new List<BattleBuffCell>();
    protected List<string> messages = new List<string>();
    public Slider sliderXp;
    public BasicHeroInfoContainer heroInfoContainer;
    public BattleTips textTips;
    [Header("Buff Components")]
    [SerializeField]
    public List<BattleBuffCell> buffCells = new List<BattleBuffCell>();
    protected Tween hpTween;
    [Header("Floating Configs")]
    [Tooltip("跟随角色的飘字配置")]
    public FloatingConfig followFloatingConfig;
    [Tooltip("不跟随角色的飘字配置(固定在战场节点)")]
    public FloatingConfig noFollowFloatingConfig;
    [Header("Settings")]
    public float PopUpInterval = 0.2f;
    #endregion
    protected Tween xpTween;
    #region 私有字段
    protected BattleObject battleObject;
    protected float timer = 0f;
    protected List<TipsInfo> messages = new List<TipsInfo>();
    protected List<BattleTips> tipsList = new List<BattleTips>();
    protected List<HB428_tagSCBuffRefresh> buffList = new List<HB428_tagSCBuffRefresh>();
    protected Tween hpTween;
    protected Tween xpTween;
    #endregion
    public ScrollerController scroller;
    #region Unity生命周期
    protected void OnDisable()
    {
        CleanupTips();
    }
    #endregion
    #region 公共方法 - 初始化
    public void SetBattleObject(BattleObject _battleObject)
    {
        battleObject = _battleObject;
        heroInfoContainer.SetHeroInfo(battleObject.teamHero);
        RefreshBuff(buffList);
        RefreshBuff(battleObject.buffMgr.GetBuffList());
        UpdateHP(battleObject.teamHero.curHp, battleObject.teamHero.curHp, battleObject.teamHero.maxHp, false);
        UpdateXP(battleObject.teamHero.rage, battleObject.teamHero.rage, 100, false);
    }
    public void RefreshBuff(List<HB428_tagSCBuffRefresh> datas)
    {
        buffList = datas;
        //  更新buff图标 or 创建新的buff图标
        scroller.Refresh();
        for (int i = 0; i < buffList.Count; i++)
        {
            if (i % 5 == 0)
            {
                scroller.AddCell(ScrollerDataType.Header, i);
            }
        }
        scroller.Restart();
    }
    protected void OnEnable()
    {
        scroller.OnRefreshCell += OnRefreshCell;
    }
    protected void OnDisable()
    {
        scroller.OnRefreshCell -= OnRefreshCell;
        //  TODO YYL 考虑池化
        messages.Clear();
        for (int i = 0; i < tipsList.Count; i++)
        {
            var tip = tipsList[i];
            tip.OnFinish = null;
            GameObject.DestroyImmediate(tip.gameObject);
        }
        tipsList.Clear();
    }
    protected void OnRefreshCell(ScrollerDataType type, CellView cell)
    {
        var _cell = cell as BattleBuffLineCell;
        _cell.Display(buffList, cell.index);
    }
    public void ShowTips(string message)
    {
        messages.Add(message);
    }
    public void SetActive(bool active)
    {
        gameObject.SetActive(active);
    }
    #endregion
    public void PopUpTipsDirectly(string message)
    #region 公共方法 - Buff管理
    public void RefreshBuff(List<HB428_tagSCBuffRefresh> datas)
    {
        GameObject prefab = textTips.gameObject;
        if (buffCells.IsNullOrEmpty())
            return;
        GameObject go = GameObject.Instantiate(prefab, transform);
        BattleTips tips = go.GetComponent<BattleTips>();
        tips.SetText(message);
        tips.OnFinish = () =>
        for (int i = 0; i < buffCells.Count; i++)
        {
            //  TODO YYL 考虑池化
            tipsList.Remove(tips);
            GameObject.DestroyImmediate(tips.gameObject);
        };
            if (i < datas.Count)
            {
                buffCells[i].SetActive(true);
                buffCells[i].Init(datas[i], OnBuffCellClicked);
            }
            else
            {
                buffCells[i].SetActive(false);
            }
        }
    }
    #endregion
    #region 公共方法 - 飘字管理
    /// <summary>
    /// 添加飘字到队列
    /// </summary>
    public void ShowTips(string message, bool useArtText = false, bool followCharacter = true, float scaleRatio = 1f)
    {
        messages.Add(new TipsInfo
        {
            message = message,
            useArtText = useArtText,
            followCharacter = followCharacter,
            scaleRatio = scaleRatio
        });
    }
    /// <summary>
    /// 添加自定义飘字配置到队列
    /// </summary>
    public void ShowTips(TipsInfo tipsInfo)
    {
        messages.Add(tipsInfo);
    }
    #endregion
    #region 公共方法 - 数值更新
    /// <summary>
    /// 更新血量显示
    /// </summary>
    public void UpdateHP(long fromHp, long toHp, long maxHp, bool tween = true)
    {
        KillTween(ref hpTween);
        float fromValue = (float)fromHp / (float)maxHp;
        float targetValue = (float)toHp / (float)maxHp;
        if (tween)
        {
            // 关键修复:先设置起始值,再播放动画到目标值
            sliderHp.value = fromValue;  // ← 这行是关键!
            hpTween = sliderHp.DOValue(targetValue, 0.3f).SetAutoKill(false);
            battleObject.battleField.battleTweenMgr.OnPlayTween(hpTween);
        }
        else
        {
            sliderHp.value = targetValue;
        }
    }
    /// <summary>
    /// !!!临时的用于天子更新血量显示,等接口完善后删除
    /// </summary>
    public void UpdateHP(float value)
    {
        sliderHp.value = value;
        //Debug.Log("TianziDamageBar UpdateHP value:" + value);
    }
    /// <summary>
    /// 更新怒气显示
    /// </summary>
    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);
            battleObject.battleField.battleTweenMgr.OnPlayTween(xpTween);
        }
        else
        {
            sliderXp.value = targetValue;
        }
    }
    #endregion
    #region 公共方法 - 运行时更新
    /// <summary>
    /// 每帧更新
    /// </summary>
    public void Run()
    {
        // 更新所有飘字
        UpdateActiveTips();
        // 处理飘字队列
        ProcessTipsQueue();
    }
    /// <summary>
    /// 设置速度比例
    /// </summary>
    public void SetSpeedRatio(float ratio)
    {
        foreach (var tip in tipsList)
        {
            tip.SetRatio(ratio, 1f);
        }
    }
    #endregion
    #region 私有方法 - 飘字处理
    /// <summary>
    /// 立即弹出飘字
    /// </summary>
    private void PopUpTipsDirectly(TipsInfo tipsInfo)
    {
        // 创建飘字实例
        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);
        // 添加到列表
        tipsList.Add(tips);
    }
    public void UpdateHP(long fromHp, long toHp, long maxHp)
    /// <summary>
    /// 创建飘字实例
    /// </summary>
    private BattleTips CreateTipsInstance(TipsInfo tipsInfo)
    {
        //  做hp增加或者减少的动画
        // sliderHp.value = ((float)fromHp) / ((float)maxHp);
        if (hpTween != null)
        {
            battleObject.battleField.battleTweenMgr.OnKillTween(hpTween);
        }
        hpTween = sliderHp.DOValue((float)toHp / maxHp, 0.3f);
        battleObject.battleField.battleTweenMgr.OnPlayTween(hpTween);
        BattleDebug.LogError("update hp from " + fromHp + " to " + toHp + " maxHp " + maxHp);
        Transform parent = tipsInfo.followCharacter
            ? transform
            : battleObject.battleField.battleRootNode.transform;
        GameObject go = GameObject.Instantiate(textTips.gameObject, parent);
        return go.GetComponent<BattleTips>();
    }
    public void UpdateXP(long fromXp, long toXp, long maxXp)
    /// <summary>
    /// 配置飘字
    /// </summary>
    private void ConfigureTips(BattleTips tips, TipsInfo tipsInfo)
    {
        //  做Xp增加或者减少的动画
        // sliderXp.value = ((float)fromXp) / ((float)maxXp);
        if (xpTween != null)
        FloatingConfig targetConfig = tipsInfo.followCharacter
            ? followFloatingConfig
            : noFollowFloatingConfig;
        if (targetConfig == null)
        {
            battleObject.battleField.battleTweenMgr.OnKillTween(xpTween);
            Debug.LogError($"[BattleHeroInfoBar] FloatingConfig 未配置! " +
                $"followCharacter={tipsInfo.followCharacter}, GameObject: {gameObject.name}");
            return;
        }
        xpTween = sliderHp.DOValue((float)toXp / maxXp, 0.2f);
        battleObject.battleField.battleTweenMgr.OnPlayTween(xpTween);
        BattleDebug.LogError("update xp from " + fromXp + " to " + toXp + " maxXp " + maxXp);
        tips.SetFloatingConfig(targetConfig);
        // 设置是否使用 Buff 颜色
        if (tipsInfo.useBuffColor)
        {
            tips.SetBuffColor(true, tipsInfo.isDebuff);
        }
    }
    /// <summary>
    /// 设置不跟随飘字的位置
    /// </summary>
    private void SetNonFollowPosition(BattleTips tips)
    {
        RectTransform contentRect = tips.GetComponent<RectTransform>();
        RectTransform contentParentRect = contentRect.parent as RectTransform;
        RectTransform infoBarRect = GetComponent<RectTransform>();
    public void Run()
        // 计算世界坐标
        Vector3 worldTargetPos = infoBarRect.transform.TransformPoint(infoBarRect.rect.center);
        // 转换到父节点坐标
        Vector2 anchoredPos;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            contentParentRect,
            RectTransformUtility.WorldToScreenPoint(null, worldTargetPos),
            null,
            out anchoredPos
        );
        // 设置动态位置
        tips.SetPosition(anchoredPos, anchoredPos + new Vector2(0, 150));
    }
    /// <summary>
    /// 移除飘字
    /// </summary>
    private void RemoveTips(BattleTips tips)
    {
        tipsList.Remove(tips);
        GameObject.DestroyImmediate(tips.gameObject);
    }
    /// <summary>
    /// 更新所有激活的飘字
    /// </summary>
    private void UpdateActiveTips()
    {
        for (int i = tipsList.Count - 1; i >= 0; i--)
        {
            tipsList[i].Run();
        }
        timer += Time.deltaTime;
    }
    /// <summary>
    /// 处理飘字队列
    /// </summary>
    private void ProcessTipsQueue()
    {
        timer += GetDeltaTime();
        if (messages.Count > 0 && timer >= PopUpInterval)
        {
            // 播放飘字
            string message = messages[0];
            TipsInfo tipsInfo = messages[0];
            messages.RemoveAt(0);
            PopUpTipsDirectly(message);
            PopUpTipsDirectly(tipsInfo);
            timer = 0f;
        }
    }
    /// <summary>
    /// 清理所有飘字
    /// </summary>
    private void CleanupTips()
    {
        messages.Clear();
        foreach (var tip in tipsList)
        {
            tip.OnFinish = null;
            GameObject.DestroyImmediate(tip.gameObject);
        }
        tipsList.Clear();
    }
    #endregion
    #region 私有方法 - 辅助方法
    /// <summary>
    /// 停止并清理Tween
    /// </summary>
    private void KillTween(ref Tween tween)
    {
        if (tween != null && battleObject != null)
        {
            battleObject.battleField.battleTweenMgr.OnKillTween(tween);
            tween = null;
        }
    }
    /// <summary>
    /// 获取时间增量
    /// </summary>
    private float GetDeltaTime()
    {
        return 1f / (float)BattleConst.skillMotionFps * battleObject.battleField.speedRatio;
    }
    /// <summary>
    /// Buff图标点击回调
    /// </summary>
    private void OnBuffCellClicked()
    {
        // TODO: 显示buff描述/当前身上所有buff
    }
    #endregion
}