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(决定用哪个颜色) } /// /// 血条更新请求 /// 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 buffCells = new List(); [Header("Floating Configs")] [Tooltip("跟随角色的飘字配置")] public FloatingConfig followFloatingConfig; [Tooltip("不跟随角色的飘字配置(固定在战场节点)")] public FloatingConfig noFollowFloatingConfig; 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; 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 maxHp = battleObject.teamHero.maxHp; // 第一条护盾的值最大值是当前的MaxHp 第二条护盾的最大值其实也是MaxHp 多余的不做显示 sliderShield1.value = maxHp > 0 ? Mathf.Min((float)shieldValue, (float)maxHp) / (float)maxHp : 0; sliderShield2.value = maxHp > 0 ? Mathf.Max((float)(shieldValue - maxHp), 0f) / (float)maxHp : 0; } 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 maxHp = battleObject.teamHero.maxHp; // 第一条护盾的值最大值是当前的MaxHp 第二条护盾的最大值其实也是MaxHp 多余的不做显示 sliderShield1.value = maxHp > 0 ? Mathf.Min((float)shieldValue, (float)maxHp) / (float)maxHp : 0; sliderShield2.value = maxHp > 0 ? Mathf.Max((float)(shieldValue - maxHp), 0f) / (float)maxHp : 0; } /// /// 添加飘字到队列 /// 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; //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); long maxHp = dmgInfo.battleHurtParam.maxHp; long fromShield = dmgInfo.battleHurtParam.fromShieldValue; long toShield = dmgInfo.battleHurtParam.toShieldValue; if (maxHp <= 0) { sliderShield1.value = 0; sliderShield2.value = 0; return; } // 第一条护盾的值最大值是当前的MaxHp 第二条护盾的最大值其实也是MaxHp 多余的不做显示 float fromValue1 = (float)dmgInfo.battleHurtParam.phase1FromShieldValue / (float)maxHp; float targetValue1 = (float)dmgInfo.battleHurtParam.phase1ToShieldValue / (float)maxHp; float fromValue2 = (float)dmgInfo.battleHurtParam.phase2FromShieldValue / (float)maxHp; float targetValue2 = (float)dmgInfo.battleHurtParam.phase2ToShieldValue / (float)maxHp; damageSequence = DOTween.Sequence(); sliderShield2.value = fromValue2; if (fromValue2 > 0 && fromValue2 != targetValue2) { damageSequence.Append(sliderShield2.DOValue(targetValue2, 0.2f)); } sliderShield1.value = fromValue1; if (fromValue1 > 0 && fromValue1 != targetValue1) { damageSequence.Append(sliderShield1.DOValue(targetValue1, 0.2f)); } if (dmgInfo.battleHurtParam.fromHp != dmgInfo.battleHurtParam.toHp) { float fromHpValue = (float)dmgInfo.battleHurtParam.fromHp / (float)maxHp; float toHpValue = (float)dmgInfo.battleHurtParam.toHp / (float)maxHp; sliderHp.value = fromHpValue; 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 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; } } /// /// 更新飘字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.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 } }