yyl
6 天以前 97f4169588320fcf84bda92e12415650869ddaff
125 战斗 飘字配置化
6个文件已修改
3个文件已添加
1092 ■■■■ 已修改文件
Main/Component/UI/Effect/ScriptableObject.meta 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Component/UI/Effect/ScriptableObject/FloatingConfig.cs 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Component/UI/Effect/ScriptableObject/FloatingConfig.cs.meta 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleField/RecordActions/BuffMountAction.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/BattleHUDWin.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/UIComp/BattleFloatingUIController.cs 290 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/UIComp/BattleHeroInfoBar.cs 412 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/UIComp/BattleTips.cs 253 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/System/Battle/UIComp/DamageContent.cs 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Main/Component/UI/Effect/ScriptableObject.meta
New file
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c6e2ae278e429844e9d5da2ebc5e5d6d
folderAsset: yes
DefaultImporter:
  externalObjects: {}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/Component/UI/Effect/ScriptableObject/FloatingConfig.cs
New file
@@ -0,0 +1,51 @@
using UnityEngine;
/// <summary>
/// 战斗飘字配置 ScriptableObject
/// 用于配置飘字的动画效果参数
/// </summary>
[CreateAssetMenu(fileName = "FloatingConfig", menuName = "Battle/FloatingConfig", order = 1)]
public class FloatingConfig : ScriptableObject
{
    [Header("Position Settings")]
    [Tooltip("起始位置偏移")]
    public Vector2 beginPos = Vector2.zero;
    [Tooltip("结束位置偏移")]
    public Vector2 endPos = new Vector2(0, 150);
    [Tooltip("位置移动曲线 (X轴=时间进度0-1, Y轴=插值进度0-1)")]
    public AnimationCurve positionCurve = AnimationCurve.Linear(0, 0, 1, 1);
    [Header("Time Settings")]
    [Tooltip("缩放变化时间 (约16帧)")]
    public float scaleChangeTime = 1f / BattleConst.skillMotionFps * 16f + 0.1f;
    [Tooltip("总显示时间 (约48帧)")]
    public float totalShowTime = 1f / BattleConst.skillMotionFps * 48f + 0.1f;
    [Header("Normal Animation Settings")]
    [Tooltip("普通飘字初始缩放")]
    public Vector3 normalBeginScale = new Vector3(2f, 2f, 2f);
    [Tooltip("普通飘字结束缩放")]
    public Vector3 normalEndScale = new Vector3(1f, 1f, 1f);
    [Header("Critical Animation Settings")]
    [Tooltip("暴击飘字初始缩放")]
    public Vector3 critBeginScale = new Vector3(3f, 3f, 3f);
    [Tooltip("暴击飘字结束缩放")]
    public Vector3 critEndScale = new Vector3(1.5f, 1.5f, 1.5f);
    [Tooltip("缩放变化曲线 (X轴=时间进度0-1, Y轴=插值进度0-1)")]
    public AnimationCurve scaleCurve = AnimationCurve.Linear(0, 0, 1, 1);
    [Header("Color Settings")]
    [Tooltip("初始颜色和透明度")]
    public Color beginColor = new Color(1f, 1f, 1f, 0.5f);
    [Tooltip("结束颜色和透明度")]
    public Color endColor = new Color(1f, 1f, 1f, 1f);
    [Tooltip("颜色变化曲线 (X轴=时间进度0-1, Y轴=插值进度0-1)")]
    public AnimationCurve colorCurve = AnimationCurve.Linear(0, 0, 1, 1);
    [Header("Buff Color Settings")]
    [Tooltip("正向Buff颜色 (增益buff)")]
    public Color gainBuffColor = Color.green;
    [Tooltip("负向Buff颜色 (debuff)")]
    public Color debuffColor = Color.red;
}
Main/Component/UI/Effect/ScriptableObject/FloatingConfig.cs.meta
New file
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 900f40b168d3e0b4f9bebd9917a43c7e
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 0
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:
Main/System/Battle/BattleField/RecordActions/BuffMountAction.cs
@@ -40,9 +40,11 @@
                    tipsInfo.useArtText = false;
                    tipsInfo.followCharacter = true;
                    tipsInfo.scaleRatio = 1f;
                    tipsInfo.textColor = skillConfig.IsDebuff() ?
                                            Color.red : skillConfig.IsGainBuff() ? Color.green : Color.white;
                    tipsInfo.showBackground = true;
                    // 使用 Buff 颜色
                    tipsInfo.useBuffColor = true;
                    tipsInfo.isDebuff = skillConfig.IsDebuff();
                    obj.heroInfoBar.ShowTips(tipsInfo);
                }
Main/System/Battle/BattleHUDWin.cs
@@ -120,9 +120,10 @@
            null,
            out anchoredPos);
        // 设置初始位置和结束位置
        content.beginPos = anchoredPos;
        content.endPos = anchoredPos + new Vector2(0, 150);
        // 设置动态位置(会覆盖配置中的位置)
        Vector2 beginPos = anchoredPos;
        Vector2 endPos = anchoredPos + new Vector2(0, 150);
        content.SetPosition(beginPos, endPos);
        
        // 设置速度比例
        if (battleField != null)
Main/System/Battle/UIComp/BattleFloatingUIController.cs
@@ -2,117 +2,297 @@
using System;
/// <summary>
/// 战斗飘字UI控制器:处理缩放、透明度、位置动画逻辑
/// 战斗飘字UI控制器
/// 职责:处理飘字的缩放、透明度、位置动画逻辑
/// </summary>
[SerializeField]
[Serializable]
public class BattleFloatingUIController
{
    // Position Settings
    public Vector2 beginPos = Vector2.zero;
    public Vector2 endPos = new Vector2(0, 150);
    // Time Settings
    public float scaleChangeTime = 1f / BattleConst.skillMotionFps * 16f + 0.1f; // 7~8帧 (8/30=0.2667秒)
    public float totalShowTime = 1f / BattleConst.skillMotionFps * 48f + 0.1f; // 总时间约24帧 (8+16=24帧)
    // Normal Settings
    public Vector3 normalBeginScale = new Vector3(2f, 2f, 2f);
    public Vector3 normalEndScale = new Vector3(1f, 1f, 1f);
    // Critical Settings
    public Vector3 critBeginScale = new Vector3(3f, 3f, 3f);
    public Vector3 critEndScale = new Vector3(1.5f, 1.5f, 1.5f);
    #region 私有字段
    
    // Color Settings
    public Color beginColor = new Color(1f, 1f, 1f, 0.5f);
    public Color endColor = new Color(1f, 1f, 1f, 1f);
    private FloatingConfig config;
    private RectTransform rectTransform;
    private GameObject gameObject;
    private Action<Color> applyColorCallback;
    // 运行时状态
    private float timer = 0f;
    private float speedRatio = 1f;
    private float scaleRatio = 1f;
    private bool isCritical = false;
    private Action onFinishCallback;
    private Action<Color> applyColorCallback;
    private GameObject gameObject;
    // 运行时位置覆盖(优先级高于配置)
    private Vector2? runtimeBeginPos = null;
    private Vector2? runtimeEndPos = null;
    // 运行时颜色覆盖(优先级高于配置)
    private Color? runtimeBeginColor = null;
    private Color? runtimeEndColor = null;
    #endregion
    // 添加只读属性以对外暴露
    #region 公共属性
    public float Timer => timer;
    public float SpeedRatio => speedRatio;
    public float ScaleRatio => scaleRatio;
    #endregion
    public BattleFloatingUIController(RectTransform rect, GameObject go, Action<Color> applyColor)
    #region 构造函数
    public BattleFloatingUIController(
        RectTransform rect,
        GameObject go,
        Action<Color> applyColor,
        FloatingConfig cfg)
    {
        rectTransform = rect;
        gameObject = go;
        applyColorCallback = applyColor;
        config = cfg;
    }
    #endregion
    #region 配置管理
    /// <summary>
    /// 设置或更新配置
    /// </summary>
    public void SetConfig(FloatingConfig cfg)
    {
        config = cfg;
    }
    /// <summary>
    /// 设置运行时位置覆盖(会覆盖配置中的位置)
    /// </summary>
    public void SetRuntimePosition(Vector2 beginPos, Vector2 endPos)
    {
        runtimeBeginPos = beginPos;
        runtimeEndPos = endPos;
    }
    /// <summary>
    /// 清除运行时位置覆盖,恢复使用配置中的位置
    /// </summary>
    public void ClearRuntimePosition()
    {
        runtimeBeginPos = null;
        runtimeEndPos = null;
    }
    /// <summary>
    /// 设置运行时颜色覆盖(会覆盖配置中的颜色)
    /// </summary>
    public void SetRuntimeColor(Color beginColor, Color endColor)
    {
        runtimeBeginColor = beginColor;
        runtimeEndColor = endColor;
    }
    /// <summary>
    /// 清除运行时颜色覆盖,恢复使用配置中的颜色
    /// </summary>
    public void ClearRuntimeColor()
    {
        runtimeBeginColor = null;
        runtimeEndColor = null;
    }
    #endregion
    #region 播放控制
    /// <summary>
    /// 设置速度和缩放比例
    /// </summary>
    public void SetRatio(float speed, float scale)
    {
        speedRatio = speed;
        scaleRatio = scale;
    }
    /// <summary>
    /// 开始播放动画
    /// </summary>
    public void Play(bool isCrit, Action onComplete = null)
    {
        if (!ValidateConfig()) return;
        isCritical = isCrit;
        onFinishCallback = onComplete;
        timer = 0f;
        
        Vector3 beginScale = isCritical ? critBeginScale : normalBeginScale;
        // 初始化位置和缩放
        Vector2 beginPos = GetBeginPosition();
        Vector3 beginScale = GetBeginScale();
        rectTransform.anchoredPosition = beginPos;
        rectTransform.localScale = beginScale * scaleRatio;
        
        gameObject.SetActive(true);
    }
    /// <summary>
    /// 每帧更新
    /// </summary>
    public void Run()
    {
        if (!gameObject.activeSelf)
        if (!gameObject.activeSelf || !ValidateConfig())
            return;
        if (timer >= totalShowTime)
        // 检查是否完成
        if (timer >= config.totalShowTime)
        {
            gameObject.SetActive(false);
            onFinishCallback?.Invoke();
            onFinishCallback = null;
            OnAnimationComplete();
            return;
        }
        // 整个过程都往上飘
        float moveProgress = timer / totalShowTime;
        rectTransform.anchoredPosition = Vector2.Lerp(beginPos, endPos, moveProgress);
        Vector3 currentBeginScale = isCritical ? critBeginScale : normalBeginScale;
        Vector3 currentEndScale = isCritical ? critEndScale : normalEndScale;
        // 阶段1: 7~8帧内缩放和透明度变化
        if (timer < scaleChangeTime)
        {
            float scaleProgress = timer / scaleChangeTime;
            rectTransform.localScale = Vector3.Lerp(currentBeginScale, currentEndScale, scaleProgress) * scaleRatio;
            Color currentColor = Color.Lerp(beginColor, endColor, scaleProgress);
            applyColorCallback?.Invoke(currentColor);
        }
        // 阶段2: 保持缩放和透明度,继续往上飘
        else
        {
            rectTransform.localScale = currentEndScale * scaleRatio;
            applyColorCallback?.Invoke(endColor);
        }
        timer += 1f / BattleConst.skillMotionFps * speedRatio;
        // 更新动画
        UpdatePosition();
        UpdateScaleAndColor();
        // 增加计时器
        timer += GetDeltaTime();
    }
    /// <summary>
    /// 暂停(预留接口)
    /// </summary>
    public void Stop()
    {
        // 可以添加暂停逻辑
    }
    /// <summary>
    /// 恢复(预留接口)
    /// </summary>
    public void Resume()
    {
        // 可以添加恢复逻辑
    }
    #endregion
    #region 私有方法
    /// <summary>
    /// 验证配置有效性
    /// </summary>
    private bool ValidateConfig()
    {
        if (config == null)
        {
            Debug.LogError("[BattleFloatingUIController] FloatingConfig 配置为空");
            return false;
        }
        return true;
    }
    /// <summary>
    /// 获取起始位置(运行时位置优先)
    /// </summary>
    private Vector2 GetBeginPosition()
    {
        return runtimeBeginPos ?? config.beginPos;
    }
    /// <summary>
    /// 获取结束位置(运行时位置优先)
    /// </summary>
    private Vector2 GetEndPosition()
    {
        return runtimeEndPos ?? config.endPos;
    }
    /// <summary>
    /// 获取起始缩放
    /// </summary>
    private Vector3 GetBeginScale()
    {
        return isCritical ? config.critBeginScale : config.normalBeginScale;
    }
    /// <summary>
    /// 获取结束缩放
    /// </summary>
    private Vector3 GetEndScale()
    {
        return isCritical ? config.critEndScale : config.normalEndScale;
    }
    /// <summary>
    /// 获取时间增量
    /// </summary>
    private float GetDeltaTime()
    {
        return 1f / BattleConst.skillMotionFps * speedRatio;
    }
    /// <summary>
    /// 更新位置
    /// </summary>
    private void UpdatePosition()
    {
        float moveProgress = timer / config.totalShowTime;
        Vector2 currentPos = Vector2.Lerp(GetBeginPosition(), GetEndPosition(), moveProgress);
        rectTransform.anchoredPosition = currentPos;
    }
    /// <summary>
    /// 获取起始颜色(运行时颜色优先)
    /// </summary>
    private Color GetBeginColor()
    {
        return runtimeBeginColor ?? config.beginColor;
    }
    /// <summary>
    /// 获取结束颜色(运行时颜色优先)
    /// </summary>
    private Color GetEndColor()
    {
        return runtimeEndColor ?? config.endColor;
    }
    /// <summary>
    /// 更新缩放和颜色
    /// </summary>
    private void UpdateScaleAndColor()
    {
        // 阶段1: 缩放和透明度变化
        if (timer < config.scaleChangeTime)
        {
            float progress = timer / config.scaleChangeTime;
            // 缩放插值
            Vector3 currentScale = Vector3.Lerp(GetBeginScale(), GetEndScale(), progress);
            rectTransform.localScale = currentScale * scaleRatio;
            // 颜色插值(使用运行时颜色或配置颜色)
            Color currentColor = Color.Lerp(GetBeginColor(), GetEndColor(), progress);
            applyColorCallback?.Invoke(currentColor);
        }
        // 阶段2: 保持最终缩放和透明度
        else
        {
            rectTransform.localScale = GetEndScale() * scaleRatio;
            applyColorCallback?.Invoke(GetEndColor());
        }
    }
    /// <summary>
    /// 动画完成回调
    /// </summary>
    private void OnAnimationComplete()
    {
        gameObject.SetActive(false);
        onFinishCallback?.Invoke();
        onFinishCallback = null;
    }
    #endregion
}
Main/System/Battle/UIComp/BattleHeroInfoBar.cs
@@ -4,49 +4,78 @@
using System;
using DG.Tweening;
/// <summary>
/// 战斗角色信息栏
/// 职责:显示角色血条、怒气、Buff和飘字提示
/// </summary>
public class BattleHeroInfoBar : MonoBehaviour
{
    #region 内部类
    /// <summary>
    /// 飘字信息配置
    /// </summary>
    public class TipsInfo
    {
        public string message;
        public bool useArtText;
        public bool followCharacter;
        public float scaleRatio;
        public Color textColor = Color.white;
        public bool showBackground = false;
        public bool useBuffColor = false;  // 是否使用 Buff 颜色(从 FloatingConfig 读取)
        public bool isDebuff = false;      // 是否是负向 Buff(决定用哪个颜色)
    }
    #endregion
    protected BattleObject battleObject;
    #region Inspector字段
    [Header("UI Components")]
    public Slider sliderHp;
    public Slider sliderXp; //怒气
    protected float timer = 0f;
    public float PopUpInterval = 0.2f;
    [SerializeField] public List<BattleBuffCell> buffCells = new List<BattleBuffCell>();
    protected List<TipsInfo> messages = new List<TipsInfo>();
    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
    #region Unity生命周期
    protected void OnDisable()
    {
        CleanupTips();
    }
    #endregion
    #region 公共方法 - 初始化
    public void SetBattleObject(BattleObject _battleObject)
    {
        battleObject = _battleObject;
@@ -55,23 +84,27 @@
        UpdateHP(battleObject.teamHero.curHp, battleObject.teamHero.curHp, battleObject.teamHero.maxHp, false);
        UpdateXP(battleObject.teamHero.rage, battleObject.teamHero.rage, 100, false);
    }
    public void SetActive(bool active)
    {
        gameObject.SetActive(active);
    }
    #endregion
    #region 公共方法 - Buff管理
    public void RefreshBuff(List<HB428_tagSCBuffRefresh> datas)
    {
        if (buffCells.IsNullOrEmpty())
            return;
        for (int i = 0; i < buffCells.Count; i++)
        {
            if (i < datas.Count)
            {
                buffCells[i].SetActive(true);
                HB428_tagSCBuffRefresh buffData = datas[i];
                buffCells[i].Init(buffData, () =>
                {
                    //  点击buff图标 显示buff描述/当前身上所有buff
                });
                buffCells[i].Init(datas[i], OnBuffCellClicked);
            }
            else
            {
@@ -79,19 +112,14 @@
            }
        }
    }
    #endregion
    protected void OnDisable()
    {
        //  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();
    }
    #region 公共方法 - 飘字管理
    /// <summary>
    /// 添加飘字到队列
    /// </summary>
    public void ShowTips(string message, bool useArtText = false, bool followCharacter = true, float scaleRatio = 1f)
    {
        messages.Add(new TipsInfo
@@ -103,134 +131,266 @@
        });
    }
    /// <summary>
    /// 添加自定义飘字配置到队列
    /// </summary>
    public void ShowTips(TipsInfo tipsInfo)
    {
        messages.Add(tipsInfo);
    }
    #endregion
    public void SetActive(bool active)
    #region 公共方法 - 数值更新
    /// <summary>
    /// 更新血量显示
    /// </summary>
    public void UpdateHP(long fromHp, long toHp, long maxHp, bool tween = true)
    {
        gameObject.SetActive(active);
    }
    public void PopUpTipsDirectly(TipsInfo tipsInfo)
    {
        GameObject prefab = textTips.gameObject;
        GameObject go = GameObject.Instantiate(prefab, tipsInfo.followCharacter ? transform : battleObject.battleField.battleRootNode.transform);
        BattleTips tips = go.GetComponent<BattleTips>();
        if (!tipsInfo.followCharacter)
        {
            var contentRect = go.GetComponent<RectTransform>();
            var contentParentRect = contentRect.parent as RectTransform;
            var infoBarRect = GetComponent<RectTransform>();
            Vector3 worldTargetPos = infoBarRect.transform.TransformPoint(infoBarRect.rect.center);
            Vector2 anchoredPos;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                contentParentRect,
                RectTransformUtility.WorldToScreenPoint(null, worldTargetPos),
                null,
                out anchoredPos);
            tips.UpdatePositions(anchoredPos, anchoredPos + new Vector2(0, 150));
            // 同时更新缩放
            Vector3 newBeginScale = tips.normalBeginScale * tipsInfo.scaleRatio;
            Vector3 newEndScale = tips.normalEndScale * tipsInfo.scaleRatio;
            tips.UpdateScales(newBeginScale, newEndScale);
        }
        tips.SetRatio(battleObject.battleField.speedRatio, 1f);
        tips.SetText(tipsInfo.message, tipsInfo.useArtText, false, tipsInfo.textColor);
        tips.ShowBackground(tipsInfo.showBackground);
        tips.OnFinish = () =>
        {
            tipsList.Remove(tips);
            GameObject.DestroyImmediate(tips.gameObject);
        };
        tipsList.Add(tips);
    }
    public void UpdateHP(long fromHp, long toHp, long maxHp,  bool tween = true)
    {
        //  做hp增加或者减少的动画
        // sliderHp.value = ((float)fromHp) / ((float)maxHp);
        if (hpTween != null)
        {
            battleObject.battleField.battleTweenMgr.OnKillTween(hpTween);
        }
        KillTween(ref hpTween);
        float targetValue = (float)toHp / (float)maxHp;
        if (tween)
        {
            hpTween = sliderHp.DOValue((float)toHp / (float)maxHp, 0.3f);
            hpTween = sliderHp.DOValue(targetValue, 0.3f);
            battleObject.battleField.battleTweenMgr.OnPlayTween(hpTween);
        }
        else
        {
            sliderHp.value = ((float)toHp) / ((float)maxHp);
            sliderHp.value = targetValue;
        }
        // BattleDebug.LogError("update hp from " + fromHp + " to " + toHp + " maxHp " + maxHp);
    }
    /// <summary>
    /// 更新怒气显示
    /// </summary>
    public void UpdateXP(long fromXp, long toXp, long maxXp, bool tween = true)
    {
        //  做Xp增加或者减少的动画
        // sliderXp.value = ((float)fromXp) / ((float)maxXp);
        if (xpTween != null)
        {
            battleObject.battleField.battleTweenMgr.OnKillTween(xpTween);
        }
        KillTween(ref xpTween);
        float targetValue = (float)toXp / (float)maxXp;
        if (tween)
        {
            xpTween = sliderXp.DOValue((float)toXp / (float)maxXp, 0.2f);
            xpTween = sliderXp.DOValue(targetValue, 0.2f);
            battleObject.battleField.battleTweenMgr.OnPlayTween(xpTween);
        }
        else
        {
            sliderXp.value = ((float)toXp) / ((float)maxXp);
            sliderXp.value = targetValue;
        }
        // BattleDebug.LogError("update xp from " + fromXp + " to " + toXp + " maxXp " + maxXp);
    }
    #endregion
    #region 公共方法 - 运行时更新
    /// <summary>
    /// 每帧更新
    /// </summary>
    public void Run()
    {
        // 倒序遍历 删除.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);
    }
    /// <summary>
    /// 创建飘字实例
    /// </summary>
    private BattleTips CreateTipsInstance(TipsInfo tipsInfo)
    {
        Transform parent = tipsInfo.followCharacter
            ? transform
            : battleObject.battleField.battleRootNode.transform;
        GameObject go = GameObject.Instantiate(textTips.gameObject, parent);
        return go.GetComponent<BattleTips>();
    }
    /// <summary>
    /// 配置飘字
    /// </summary>
    private void ConfigureTips(BattleTips tips, TipsInfo tipsInfo)
    {
        FloatingConfig targetConfig = tipsInfo.followCharacter
            ? followFloatingConfig
            : noFollowFloatingConfig;
        if (targetConfig == null)
        {
            Debug.LogError($"[BattleHeroInfoBar] FloatingConfig 未配置! " +
                $"followCharacter={tipsInfo.followCharacter}, GameObject: {gameObject.name}");
            return;
        }
        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>();
        // 计算世界坐标
        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 += 1f / (float)BattleConst.skillMotionFps * battleObject.battleField.speedRatio;
    }
    /// <summary>
    /// 处理飘字队列
    /// </summary>
    private void ProcessTipsQueue()
    {
        timer += GetDeltaTime();
        if (messages.Count > 0 && timer >= PopUpInterval)
        {
            // 播放飘字
            TipsInfo tipsInfo = messages[0];
            messages.RemoveAt(0);
            PopUpTipsDirectly(tipsInfo);
            timer = 0f;
        }
    }
    public void SetSpeedRatio(float ratio)
    /// <summary>
    /// 清理所有飘字
    /// </summary>
    private void CleanupTips()
    {
        for (int i = 0; i < tipsList.Count; i++)
        messages.Clear();
        foreach (var tip in tipsList)
        {
            tipsList[i].SetRatio(ratio, 1f);
            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
}
Main/System/Battle/UIComp/BattleTips.cs
@@ -2,79 +2,135 @@
using System;
using UnityEngine.UI;
/// <summary>
/// 战斗飘字UI组件
/// 职责:管理UI元素和与控制器的交互
/// </summary>
public class BattleTips : MonoBehaviour, IBattleFloatingUI
{
    public Vector2 beginPos = Vector2.zero;
    public Vector2 endPos = new Vector2(0, 150);
    #region Inspector字段
    [Header("UI Components")]
    public RectTransform rectTransform;
    public Text tipText;
    public Text artText;
    public Image background;
    public Vector3 normalBeginScale = new Vector3(2f, 2f, 2f);
    public Vector3 normalEndScale = new Vector3(1f, 1f, 1f);
    [Header("Floating Config")]
    [Tooltip("飘字动画配置,请在Inspector中拖拽赋值")]
    public FloatingConfig floatingConfig;
    #endregion
    public Action OnFinish; // 保留 OnFinish
    #region 公共字段
    public Action OnFinish;
    // Buff 颜色相关
    private bool useBuffColor = false;
    private bool isDebuff = false;
    #endregion
    [SerializeField]
    #region 私有字段
    // 移除 [SerializeField],controller 不应该被序列化
    private BattleFloatingUIController controller;
    #endregion
    void Awake()
    {
        InitController();
    }
    private void InitController()
    {
        if (controller != null) return;
        controller = new BattleFloatingUIController(rectTransform, gameObject, ApplyColor);
        controller.beginPos = beginPos;
        controller.endPos = endPos;
        controller.normalBeginScale =   normalBeginScale;
        controller.normalEndScale = normalEndScale;
    }
    #region 公共方法
    /// <summary>
    /// 设置速度和缩放比例
    /// </summary>
    public void SetRatio(float speed, float scale)
    {
        InitController(); // 确保 controller 已初始化
        controller.SetRatio(speed, scale);
        EnsureControllerInitialized();
        controller?.SetRatio(speed, scale);
    }
    public void SetText(string text, bool useArtText = false, bool isCrit = false, Color textColor = default)
    /// <summary>
    /// 设置运行时位置(用于不跟随角色的飘字)
    /// </summary>
    public void SetPosition(Vector2 beginPos, Vector2 endPos)
    {
        if (textColor == default)
        EnsureControllerInitialized();
        controller?.SetRuntimePosition(beginPos, endPos);
    }
    /// <summary>
    /// 运行时更新配置
    /// </summary>
    public void SetFloatingConfig(FloatingConfig config)
    {
        floatingConfig = config;
        if (controller != null)
        {
            textColor = Color.white;
        }
        InitController();
        if (useArtText)
        {
            artText.text = text;
            tipText.gameObject.SetActive(false);
            artText.gameObject.SetActive(true);
            controller.SetConfig(config);
        }
        else
        {
            tipText.text = text;
            artText.gameObject.SetActive(false);
            tipText.gameObject.SetActive(true);
            InitController();
        }
    }
        controller.beginColor = new Color(textColor.r, textColor.g, textColor.b, controller.beginColor.a);
        controller.endColor = new Color(textColor.r, textColor.g, textColor.b, controller.endColor.a);
        ApplyColor(controller.beginColor);
    /// <summary>
    /// 设置是否使用 Buff 颜色
    /// </summary>
    public void SetBuffColor(bool useBuff, bool isDebuffBuff)
    {
        useBuffColor = useBuff;
        isDebuff = isDebuffBuff;
        // 如果使用 buff 颜色,立即设置运行时颜色覆盖
        if (useBuffColor && floatingConfig != null)
        {
            Color buffColor = isDebuff ? floatingConfig.debuffColor : floatingConfig.gainBuffColor;
            Color beginColor = new Color(buffColor.r, buffColor.g, buffColor.b, floatingConfig.beginColor.a);
            Color endColor = new Color(buffColor.r, buffColor.g, buffColor.b, floatingConfig.endColor.a);
            EnsureControllerInitialized();
            controller?.SetRuntimeColor(beginColor, endColor);
        }
    }
    /// <summary>
    /// 设置文本内容和样式
    /// </summary>
    public void SetText(string text, bool useArtText = false, bool isCrit = false)
    {
        EnsureControllerInitialized();
        // 切换文本显示类型
        SwitchTextDisplay(useArtText, text);
        // 开始播放
        Play(isCrit);
    }
    /// <summary>
    /// 显示/隐藏背景
    /// </summary>
    public void ShowBackground(bool show)
    {
        if (background != null)
            background.enabled = show;
    }
    #endregion
    #region IBattleFloatingUI接口实现
    public void Play(bool isCrit, Action onComplete = null)
    {
        InitController(); // 确保 controller 已初始化
        EnsureControllerInitialized();
        
        // 合并 OnFinish 和 onComplete
        if (controller == null) return;
        // 合并回调
        Action combinedCallback = () =>
        {
            OnFinish?.Invoke();
@@ -87,51 +143,100 @@
    public void Run()
    {
        if (controller == null) return; // 防止在 Awake 前调用
        controller.Run();
        controller?.Run();
    }
    public void Stop()
    {
        if (controller == null) return;
        controller.Stop();
        controller?.Stop();
    }
    public void Resume()
    {
        if (controller == null) return;
        controller.Resume();
        controller?.Resume();
    }
    #endregion
    #region 私有方法
    /// <summary>
    /// 初始化控制器
    /// </summary>
    private void InitController()
    {
        if (controller != null) return;
        if (floatingConfig == null)
        {
            Debug.LogError($"[BattleTips] FloatingConfig 未配置! GameObject: {gameObject.name}");
            return;
        }
        controller = new BattleFloatingUIController(
            rectTransform,
            gameObject,
            ApplyColor,
            floatingConfig
        );
    }
    /// <summary>
    /// 确保控制器已初始化
    /// </summary>
    private void EnsureControllerInitialized()
    {
        if (controller == null)
            InitController();
    }
    /// <summary>
    /// 切换文本显示类型
    /// </summary>
    private void SwitchTextDisplay(bool useArtText, string text)
    {
        if (useArtText)
        {
            artText.text = text;
            tipText.gameObject.SetActive(false);
            artText.gameObject.SetActive(true);
        }
        else
        {
            tipText.text = text;
            artText.gameObject.SetActive(false);
            tipText.gameObject.SetActive(true);
        }
    }
    /// <summary>
    /// 应用文本颜色(保留配置的透明度)
    /// </summary>
    private void ApplyTextColor(Color textColor)
    {
        if (floatingConfig != null)
        {
            Color colorWithAlpha = new Color(
                textColor.r,
                textColor.g,
                textColor.b,
                floatingConfig.beginColor.a
            );
            ApplyColor(colorWithAlpha);
        }
    }
    /// <summary>
    /// 应用颜色到激活的文本组件
    /// </summary>
    private void ApplyColor(Color color)
    {
        if (tipText.gameObject.activeSelf)
            tipText.color = color;
        if (artText.gameObject.activeSelf)
            artText.color = color;
    }
    public void ShowBackground(bool showBackground)
    {
        // Implement the logic to show or hide the background
        background.enabled = showBackground;
    }
    public void UpdatePositions(Vector2 begin, Vector2 end)
    {
        InitController();
        beginPos = begin;
        endPos = end;
        controller.beginPos = begin;
        controller.endPos = end;
    }
    public void UpdateScales(Vector3 beginScale, Vector3 endScale)
    {
        InitController();
        normalBeginScale = beginScale;
        normalEndScale = endScale;
        controller.normalBeginScale = beginScale;
        controller.normalEndScale = endScale;
    }
    #endregion
}
Main/System/Battle/UIComp/DamageContent.cs
@@ -9,15 +9,13 @@
    public GameObject line;
    public RectTransform parent;
    public Vector2 beginPos = Vector2.zero;
    public Vector2 endPos = new Vector2(0, 150);
    [Header("Floating Config")]
    [Tooltip("请在Inspector中拖拽FloatingConfig资源")]
    public FloatingConfig floatingConfig;
    protected List<DamageLine> damageLineList = new List<DamageLine>();
    private BattleDmgInfo battleDmgInfo;
    private BattleFloatingUIController controller;
    //  飘血优化:初始放大200%,透明度50%,7~8帧内缩放回100%,透明度回到100%,再往上飘14~16帧【30帧/秒】,暴击初始放大300%,缩回150%
    //    战斗帧BattleConst.skillMotionFps 1秒=30帧
    void Awake()
    {
@@ -28,26 +26,29 @@
    {
        if (controller != null) return;
        if (floatingConfig == null)
        {
            Debug.LogError($"[DamageContent] FloatingConfig 未配置,请在Inspector中拖拽赋值! GameObject: {gameObject.name}");
            return;
        }
        RectTransform rectTransform = GetComponent<RectTransform>();
        controller = new BattleFloatingUIController(rectTransform, gameObject, ApplyColor);
        // 使用当前设置的 beginPos 和 endPos
        controller.beginPos = beginPos;
        controller.endPos = endPos;
        // controller.scaleChangeTime = scaleChangeTime;
        // controller.totalShowTime = totalShowTime;
        // controller.normalBeginScale = normalBeginScale;
        // controller.normalEndScale = normalEndScale;
        // controller.critBeginScale = critBeginScale;
        // controller.critEndScale = critEndScale;
        // controller.beginColor = beginColor;
        // controller.endColor = endColor;
        controller = new BattleFloatingUIController(rectTransform, gameObject, ApplyColor, floatingConfig);
    }
    public void SetRatio(float speed, float scale)
    {
        InitController();
        controller.SetRatio(speed, scale);
        controller?.SetRatio(speed, scale);
    }
    /// <summary>
    /// 设置飘字的起点和终点位置(运行时动态设置)
    /// </summary>
    public void SetPosition(Vector2 beginPos, Vector2 endPos)
    {
        InitController();
        controller?.SetRuntimePosition(beginPos, endPos);
    }
    public async void SetDamage(BattleDmgInfo _damageInfo, Action _onComplete)
@@ -81,10 +82,7 @@
    public void Play(bool isCrit, Action onComplete = null)
    {
        InitController();
        // 每次Play前更新controller的位置设置
        controller.beginPos = beginPos;
        controller.endPos = endPos;
        controller.Play(isCrit, onComplete);
        controller?.Play(isCrit, onComplete);
    }
    public void Run()
@@ -115,4 +113,14 @@
            }
        }
    }
    // 运行时更新配置
    public void SetFloatingConfig(FloatingConfig config)
    {
        floatingConfig = config;
        if (controller != null)
        {
            controller.SetConfig(config);
        }
    }
}