using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using DG.Tweening;
///
/// 战斗角色信息栏
/// 职责:显示角色血条、怒气、Buff和飘字提示
///
public class BattleHeroInfoBar : MonoBehaviour
{
///
/// 飘字信息配置
///
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(决定用哪个颜色)
public bool isRage = false;
}
///
/// 血条更新请求
///
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;
[Header("Buff Components")]
[SerializeField]
public List buffCells = new List();
[Header("Floating Configs")]
[Tooltip("跟随角色的飘字配置")]
public FloatingConfig followFloatingConfig;
[Tooltip("不跟随角色的飘字配置(固定在战场节点)")]
public FloatingConfig noFollowFloatingConfig;
public FloatingConfig rageFloatingConfig;
protected BattleObject battleObject;
protected List messages = new List();
protected List tipsList = new List();
protected List buffList = new List();
protected Tween hpTween;
protected Tween xpTween;
protected Tween shieldTween1;
protected Tween shieldTween2;
protected Sequence damageSequence;
private Queue hpUpdateQueue = new Queue();
private Queue damageUpdateQueue = new Queue();
// 飘字GCD相关
private float tipsGCDTimer = 0f;
private const int TIPS_GCD_FRAMES = 5;
// 全局血量记录(按战场guid组织,以最大PackUID为准,记录所有对象:施法者和受击者)
public static Dictionary largestPackUID = new Dictionary();
public static Dictionary> largestPackUIDAllObjectsToHp = new Dictionary>();
public static Dictionary> largestPackUIDAllObjectsMaxHp = new Dictionary>();
protected void OnDisable()
{
CleanupTips();
}
public void SetBattleObject(BattleObject _battleObject)
{
battleObject = _battleObject;
heroInfoContainer.SetHeroInfo(battleObject.teamHero);
RefreshBuff(battleObject.buffMgr.GetBuffList());
if (!battleObject.IsTianziBoss())
{
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;
// 记录设置前的护盾值
float oldShield1Value = sliderShield1.value;
float oldShield2Value = sliderShield2.value;
// 护盾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;
// 打印设置护盾时的状态
// Debug.LogError($"[BattleHeroInfoBar.SetBattleObject] 设置护盾 - curHp: {curHp}, shieldValue: {shieldValue}, maxHp: {maxHp}, shield1前: {oldShield1Value}, shield1后: {shield1Value}, shield2前: {oldShield2Value}, shield2后: {shield2Value}");
}
public void SetActive(bool active)
{
gameObject.SetActive(active);
}
public void RefreshBuff(List datas)
{
if (buffCells.IsNullOrEmpty())
return;
for (int i = 0; i < buffCells.Count; i++)
{
if (i < datas.Count)
{
buffCells[i].SetActive(true);
buffCells[i].Init(datas[i], OnBuffCellClicked);
}
else
{
buffCells[i].SetActive(false);
}
}
// check shield buff
long shieldValue = battleObject.buffMgr.GetShieldValue();
long curHp = battleObject.teamHero.curHp;
long maxHp = battleObject.teamHero.maxHp;
// 记录设置前的护盾值
float oldShield1Value = sliderShield1.value;
float oldShield2Value = sliderShield2.value;
// 护盾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;
if (!battleObject.IsTianziBoss())
{
UpdateHP(curHp, curHp, maxHp, false);
}
// 打印刷新护盾时的状态
// Debug.LogError($"[BattleHeroInfoBar.RefreshBuff] 设置护盾 - curHp: {curHp}, shieldValue: {shieldValue}, maxHp: {maxHp}, shield1前: {oldShield1Value}, shield1后: {shield1Value}, shield2前: {oldShield2Value}, shield2后: {shield2Value}");
}
///
/// 添加飘字到队列
///
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
});
}
///
/// 添加自定义飘字配置到队列
///
public void ShowTips(TipsInfo tipsInfo)
{
messages.Add(tipsInfo);
}
///
/// 更新血量显示
///
public void UpdateHP(long fromHp, long toHp, long maxHp, bool tween = true)
{
// 加入队列
hpUpdateQueue.Enqueue(new HpUpdateRequest
{
fromHp = fromHp,
toHp = toHp,
maxHp = maxHp,
tween = tween
});
}
///
/// 实际执行血量更新
///
private void ExecuteHpUpdate(HpUpdateRequest request)
{
KillTween(ref hpTween);
float fromValue = (float)request.fromHp / (float)request.maxHp;
float targetValue = (float)request.toHp / (float)request.maxHp;
if (request.tween)
{
sliderHp.value = fromValue;
hpTween = sliderHp.DOValue(targetValue, 0.3f).SetAutoKill(false);
battleObject.battleField.battleTweenMgr.OnPlayTween(hpTween);
}
else
{
sliderHp.value = targetValue;
}
}
///
/// !!!临时的用于天子更新血量显示,等接口完善后删除
///
public void UpdateHP(float value)
{
sliderHp.value = value;
bool IsTianziBoss = battleObject.IsTianziBoss();
sliderShield1.SetActive(!IsTianziBoss);
sliderShield2.SetActive(!IsTianziBoss);
//Debug.Log("TianziDamageBar UpdateHP value:" + value);
}
///
/// 更新怒气显示
///
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;
}
}
///
/// 播放血条 护盾的变化
///
public void UpdateDamage(BattleDmgInfo dmgInfo)
{
// 加入队列
damageUpdateQueue.Enqueue(dmgInfo);
}
///
/// 实际执行伤害更新(统一处理目标和施法者)
///
private void ExecuteDamageUpdate(BattleDmgInfo dmgInfo)
{
KillTween(ref damageSequence);
string guid = battleObject.battleField.guid;
long objID = battleObject.ObjID;
ulong currentPackUID = dmgInfo.battleHurtParam.packUID;
// 检查是否是最新的 PackUID,如果不是则忽略
if (!largestPackUID.ContainsKey(guid) || currentPackUID < largestPackUID[guid])
{
// Debug.LogWarning($"[ExecuteDamageUpdate] 忽略旧包 - ObjID:{objID}, 当前PackUID:{currentPackUID} < 最大PackUID:{largestPackUID[guid]}");
return;
}
long maxHp, fromHp, toHp, fromShield, toShield;
// 优先判断当前InfoBar是否为受击者(血量变化总是体现在hurter里)
BattleHurtObj hurter = dmgInfo.battleHurtParam.hurter;
if (hurter?.hurtObj != null && hurter.hurtObj.ObjID == objID)
{
// 当前InfoBar是受击者(包括给自己治疗、给自己造成伤害的情况)
if (hurter.hurtObj.IsTianziBoss())
{
return;
}
// 直接使用 dmgInfo 中的数据(已经被 CompareAndExchangeLargestPackUIDHp 验证过)
maxHp = hurter.maxHp;
fromHp = hurter.fromHp;
toHp = hurter.toHp;
fromShield = hurter.fromShieldValue;
toShield = hurter.toShieldValue;
// Debug.LogError($"[ExecuteDamageUpdate] 受击者 - ObjID:{objID}, fromHp:{fromHp}, toHp:{toHp}, maxHp:{maxHp} (PackUID:{currentPackUID})");
}
// 其次判断是否为施法者(施法消耗生命等情况)
else
{
BattleCastObj caster = dmgInfo.battleHurtParam.caster;
if (caster?.casterObj == null || caster.casterObj.ObjID != objID)
{
// Debug.LogWarning($"[ExecuteDamageUpdate] 当前对象 {objID} 既不是施法者也不是受击者");
return;
}
if (caster.casterObj.IsTianziBoss())
{
return;
}
// 直接使用 dmgInfo 中的数据(已经被 CompareAndExchangeLargestPackUIDHp 验证过)
maxHp = caster.maxHp;
fromHp = caster.fromHp;
toHp = caster.toHp;
fromShield = caster.fromShieldValue;
toShield = caster.toShieldValue;
// Debug.LogError($"[ExecuteDamageUpdate] 施法者 - ObjID:{objID}, fromHp:{fromHp}, toHp:{toHp}, maxHp:{maxHp} (PackUID:{currentPackUID})");
}
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);
}
///
/// 每帧更新
///
public void Run()
{
// 处理血条和伤害队列
UpdateHpAndDamageQueue();
// 更新飘字GCD并处理队列
UpdateTipsGCDAndQueue();
// 更新所有飘字
UpdateActiveTips();
}
///
/// 设置速度比例
///
public void SetSpeedRatio(float ratio)
{
foreach (var tip in tipsList)
{
tip.SetRatio(ratio, 1f);
}
}
private void CompareAndExchangeLargestPackUIDHp(BattleDmgInfo dmgInfo)
{
string guid = battleObject.battleField.guid;
ulong currentPackUID = dmgInfo.battleHurtParam.packUID;
// 获取或初始化当前战场的数据
if (!largestPackUID.ContainsKey(guid))
{
largestPackUID[guid] = 0ul;
}
if (!largestPackUIDAllObjectsToHp.ContainsKey(guid))
{
largestPackUIDAllObjectsToHp[guid] = new Dictionary();
}
if (!largestPackUIDAllObjectsMaxHp.ContainsKey(guid))
{
largestPackUIDAllObjectsMaxHp[guid] = new Dictionary();
}
ulong currentLargestPackUID = largestPackUID[guid];
Dictionary hpDict = largestPackUIDAllObjectsToHp[guid];
Dictionary maxHpDict = largestPackUIDAllObjectsMaxHp[guid];
// 如果遇到更大的packUID,更新标记(不清空数据,保留所有证据)
if (currentPackUID > currentLargestPackUID)
{
// Debug.LogError($"[血量记录] 检测到新批次PackUID: {currentPackUID} > {currentLargestPackUID},保留所有历史数据");
largestPackUID[guid] = currentPackUID;
currentLargestPackUID = currentPackUID;
}
// 记录所有packUID的数据(包括早触发的包),但只采用最大packUID的数据
// 记录施法者的血量变化
if (dmgInfo.battleHurtParam.caster?.casterObj != null)
{
BattleCastObj caster = dmgInfo.battleHurtParam.caster;
long casterID = caster.casterObj.ObjID;
// 获取旧血量用于计算变化
long oldHp = hpDict.ContainsKey(casterID) ? hpDict[casterID] : caster.fromHp;
long newHp = caster.toHp;
long maxHp = caster.maxHp;
long hpChange = newHp - oldHp;
// 只有当前packUID等于最大packUID时才更新记录
if (currentPackUID == currentLargestPackUID)
{
hpDict[casterID] = newHp;
maxHpDict[casterID] = maxHp;
// 打印血量变化日志(施法者通常是恢复生命)
// string casterName = caster.casterObj.teamHero?.heroConfig.Name ?? "未知武将";
if (hpChange != 0)
{
// string changeType = hpChange > 0 ? "恢复" : "损失";
// Debug.LogError($"[血量变化] {casterName}(ID:{casterID}) {changeType} {Math.Abs(hpChange)} 生命,血量从 {oldHp}/{maxHp} 变为 {newHp}/{maxHp} (PackUID:{currentPackUID})");
}
}
else
{
// Debug.LogWarning($"[血量记录] 忽略旧包数据 - 施法者{casterID}, 当前PackUID:{currentPackUID} < 最大PackUID:{currentLargestPackUID}");
}
}
// 记录受击者的血量变化
if (dmgInfo.battleHurtParam.hurter?.hurtObj != null)
{
BattleHurtObj hurter = dmgInfo.battleHurtParam.hurter;
long hurterID = hurter.hurtObj.ObjID;
// 获取旧血量用于计算伤害
long oldHp = hpDict.ContainsKey(hurterID) ? hpDict[hurterID] : hurter.fromHp;
long newHp = hurter.toHp;
long maxHp = hurter.maxHp;
long damage = oldHp - newHp;
// 只有当前packUID等于最大packUID时才更新记录
if (currentPackUID == currentLargestPackUID)
{
hpDict[hurterID] = newHp;
maxHpDict[hurterID] = maxHp;
// 打印血量变化日志
// string hurterName = hurter.hurtObj.teamHero?.heroConfig.Name ?? "未知武将";
if (damage != 0)
{
// Debug.LogError($"[血量变化] {hurterName}(ID:{hurterID}) 受到 {damage} 伤害,血量从 {oldHp}/{maxHp} 变为 {newHp}/{maxHp} (PackUID:{currentPackUID})");
}
}
else
{
// Debug.LogWarning($"[血量记录] 忽略旧包数据 - 受击者{hurterID}, 当前PackUID:{currentPackUID} < 最大PackUID:{currentLargestPackUID}");
}
}
}
///
/// 处理血条和伤害更新队列
///
private void UpdateHpAndDamageQueue()
{
// 优先处理UpdateDamage
if (damageUpdateQueue.Count > 0)
{
BattleDmgInfo dmgInfo = damageUpdateQueue.Dequeue();
CompareAndExchangeLargestPackUIDHp(dmgInfo);
ExecuteDamageUpdate(dmgInfo);
return;
}
// 其次处理UpdateHP
else if (hpUpdateQueue.Count > 0)
{
HpUpdateRequest request = hpUpdateQueue.Dequeue();
ExecuteHpUpdate(request);
return;
}
}
///
/// 更新飘字GCD并处理队列
///
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();
}
}
///
/// 重置飘字GCD计时器
///
private void ResetTipsGCD()
{
float speedRatio = GetCurrentSpeedRatio();
float frameTime = 1f / (float)BattleConst.skillMotionFps;
tipsGCDTimer = frameTime * TIPS_GCD_FRAMES / speedRatio;
}
///
/// 获取当前速度倍率
///
private float GetCurrentSpeedRatio()
{
// 回退到战场速度
if (battleObject != null && battleObject.battleField != null)
{
return battleObject.battleField.speedRatio;
}
return 1f;
}
///
/// 立即弹出飘字
///
private void PopUpTipsDirectly(TipsInfo tipsInfo)
{
// 创建飘字实例
BattleTips tips = CreateTipsInstance(tipsInfo);
// 配置飘字
ConfigureTips(tips, tipsInfo);
// 设置位置(如果不跟随)
if (!tipsInfo.followCharacter)
{
SetNonFollowPosition(tips);
}
// 设置参数并显示
// 注册完成回调
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);
}
///
/// 创建飘字实例
///
private BattleTips CreateTipsInstance(TipsInfo tipsInfo)
{
Transform parent = tipsInfo.followCharacter
? transform
: battleObject.battleField.battleRootNode.transform;
GameObject go = GameObject.Instantiate(textTips.gameObject, parent);
return go.GetComponent();
}
///
/// 配置飘字
///
private void ConfigureTips(BattleTips tips, TipsInfo tipsInfo)
{
FloatingConfig targetConfig = tipsInfo.isRage ? rageFloatingConfig : 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);
}
}
///
/// 设置不跟随飘字的位置
///
private void SetNonFollowPosition(BattleTips tips)
{
RectTransform contentRect = tips.GetComponent();
RectTransform contentParentRect = contentRect.parent as RectTransform;
RectTransform infoBarRect = GetComponent();
// 计算世界坐标
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));
}
///
/// 移除飘字
///
private void RemoveTips(BattleTips tips)
{
tipsList.Remove(tips);
tips.controller = null;
GameObject.DestroyImmediate(tips.gameObject);
}
///
/// 更新所有激活的飘字
///
private void UpdateActiveTips()
{
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();
}
}
///
/// 清理所有飘字
///
private void CleanupTips()
{
messages.Clear();
foreach (var tip in tipsList)
{
tip.OnFinish = null;
GameObject.DestroyImmediate(tip.gameObject);
}
tipsList.Clear();
}
///
/// 停止并清理Tween
///
private void KillTween(ref T tween) where T : Tween
{
if (tween != null && battleObject != null)
{
battleObject.battleField.battleTweenMgr.OnKillTween(tween);
tween = null;
}
}
///
/// 获取时间增量
///
private float GetDeltaTime()
{
return 1f / (float)BattleConst.skillMotionFps * battleObject.battleField.speedRatio;
}
///
/// Buff图标点击回调
///
private void OnBuffCellClicked()
{
// TODO: 显示buff描述/当前身上所有buff
}
}