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 } }