using System.Collections.Generic;
|
using UnityEngine;
|
using UnityEngine.UI;
|
using System;
|
using DG.Tweening;
|
|
/// <summary>
|
/// 战斗角色信息栏
|
/// 职责:显示角色血条、怒气、Buff和飘字提示
|
/// </summary>
|
public class BattleHeroInfoBar : MonoBehaviour
|
{
|
/// <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(决定用哪个颜色)
|
}
|
|
/// <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 RendererAdjuster maxXpGO;
|
public Slider sliderShield1;
|
public Slider sliderShield2;
|
public BasicHeroInfoContainer heroInfoContainer;
|
public BattleTips textTips;
|
|
[Header("Buff Components")]
|
[SerializeField]
|
public List<BattleBuffCell> buffCells = new List<BattleBuffCell>();
|
|
[Header("Floating Configs")]
|
[Tooltip("跟随角色的飘字配置")]
|
public FloatingConfig followFloatingConfig;
|
[Tooltip("不跟随角色的飘字配置(固定在战场节点)")]
|
public FloatingConfig noFollowFloatingConfig;
|
|
protected BattleObject battleObject;
|
|
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;
|
protected Tween shieldTween1;
|
protected Tween shieldTween2;
|
protected Sequence damageSequence;
|
|
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();
|
}
|
|
public void SetBattleObject(BattleObject _battleObject)
|
{
|
battleObject = _battleObject;
|
heroInfoContainer.SetHeroInfo(battleObject.teamHero);
|
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);
|
}
|
|
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);
|
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;
|
|
// 护盾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>
|
/// 添加飘字到队列
|
/// </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);
|
}
|
|
/// <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)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;
|
}
|
}
|
|
/// <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);
|
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;
|
}
|
}
|
|
/// <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;
|
|
if (maxHp <= 0)
|
{
|
sliderShield1.value = 0;
|
sliderShield2.value = 0;
|
return;
|
}
|
|
long fromHp = dmgInfo.battleHurtParam.fromHp;
|
long toHp = dmgInfo.battleHurtParam.toHp;
|
long fromShield = dmgInfo.battleHurtParam.fromShieldValue;
|
long toShield = dmgInfo.battleHurtParam.toShieldValue;
|
|
damageSequence = DOTween.Sequence();
|
|
// 设置初始值
|
if (fromShield > 0)
|
{
|
// 护盾1的值 = min(当前血量 + 当前护盾值, maxHp) / maxHp
|
float fromShield1Value = Mathf.Min((float)(fromHp + fromShield), (float)maxHp) / (float)maxHp;
|
// 护盾2的值 = max(当前血量 + 当前护盾值 - maxHp, 0) / maxHp
|
float fromShield2Value = Mathf.Max((float)(fromHp + fromShield - maxHp), 0f) / (float)maxHp;
|
|
sliderShield1.value = fromShield1Value;
|
sliderShield2.value = fromShield2Value;
|
|
// 如果护盾2有值,先播放护盾2的动画
|
if (fromShield2Value > 0)
|
{
|
// 计算护盾2的初始值和目标值
|
long beginShield2Value = fromHp + fromShield - maxHp;
|
long endShield2Value = Mathf.Max(0, (int)(toHp + toShield - maxHp));
|
|
float toShield2Value = (float)endShield2Value / (float)maxHp;
|
|
if (Mathf.Abs(fromShield2Value - toShield2Value) > 0.001f)
|
{
|
damageSequence.Append(sliderShield2.DOValue(toShield2Value, 0.2f));
|
}
|
}
|
|
// 播放护盾1的动画
|
if (fromShield1Value > 0)
|
{
|
// 计算护盾1的目标值
|
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
|
{
|
// 没有护盾,直接设置为0
|
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();
|
}
|
|
/// <summary>
|
/// 设置速度比例
|
/// </summary>
|
public void SetSpeedRatio(float ratio)
|
{
|
foreach (var tip in tipsList)
|
{
|
tip.SetRatio(ratio, 1f);
|
}
|
}
|
|
/// <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();
|
}
|
}
|
|
/// <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>
|
/// 立即弹出飘字
|
/// </summary>
|
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);
|
}
|
|
/// <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);
|
tips.controller = null;
|
GameObject.DestroyImmediate(tips.gameObject);
|
}
|
|
/// <summary>
|
/// 更新所有激活的飘字
|
/// </summary>
|
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();
|
}
|
}
|
|
/// <summary>
|
/// 清理所有飘字
|
/// </summary>
|
private void CleanupTips()
|
{
|
messages.Clear();
|
|
foreach (var tip in tipsList)
|
{
|
tip.OnFinish = null;
|
GameObject.DestroyImmediate(tip.gameObject);
|
}
|
|
tipsList.Clear();
|
}
|
|
/// <summary>
|
/// 停止并清理Tween
|
/// </summary>
|
private void KillTween<T>(ref T tween) where T : 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
|
}
|
}
|