| | |
| | | /// </summary> |
| | | public class BattleHeroInfoBar : MonoBehaviour |
| | | { |
| | | #region 内部类 |
| | | |
| | | /// <summary> |
| | | /// 飘字信息配置 |
| | | /// </summary> |
| | |
| | | public bool showBackground = false; |
| | | public bool useBuffColor = false; // 是否使用 Buff 颜色(从 FloatingConfig 读取) |
| | | public bool isDebuff = false; // 是否是负向 Buff(决定用哪个颜色) |
| | | |
| | | public bool isRage = false; |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region Inspector字段 |
| | | |
| | | [Header("UI Components")] |
| | | public Slider sliderHp; |
| | | public Slider sliderSlowHp; |
| | | public Slider sliderXp; |
| | | public GameObject maxXpGO; |
| | | public Slider sliderShield1; |
| | | public Slider sliderShield2; |
| | | public BasicHeroInfoContainer heroInfoContainer; |
| | | public BattleTips textTips; |
| | | |
| | |
| | | public FloatingConfig followFloatingConfig; |
| | | [Tooltip("不跟随角色的飘字配置(固定在战场节点)")] |
| | | public FloatingConfig noFollowFloatingConfig; |
| | | |
| | | [Header("Settings")] |
| | | public float PopUpInterval = 0.2f; |
| | | |
| | | #endregion |
| | | |
| | | #region 私有字段 |
| | | public FloatingConfig rageFloatingConfig; |
| | | |
| | | 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; |
| | | [SerializeField] ButtonEx buffInfoButton; |
| | | protected Sequence hpTween; |
| | | protected Tween xpTween; |
| | | protected Tween shieldTween1; |
| | | protected Tween shieldTween2; |
| | | protected Sequence damageSequence; |
| | | |
| | | #endregion |
| | | private Queue<BattleDmgInfo> damageUpdateQueue = new Queue<BattleDmgInfo>(); |
| | | |
| | | // 飘字GCD相关 |
| | | private float tipsGCDTimer = 0f; |
| | | private const int TIPS_GCD_FRAMES = 5; |
| | | |
| | | #region Unity生命周期 |
| | | // 全局血量记录(按战场guid组织,以最大PackUID为准,记录所有对象:施法者和受击者) |
| | | public static Dictionary<string, ulong> largestPackUID = new Dictionary<string, ulong>(); |
| | | public static Dictionary<string, Dictionary<long, long>> largestPackUIDAllObjectsToHp = new Dictionary<string, Dictionary<long, long>>(); |
| | | public static Dictionary<string, Dictionary<long, long>> largestPackUIDAllObjectsMaxHp = new Dictionary<string, Dictionary<long, long>>(); |
| | | public static Dictionary<string, Dictionary<long, ulong>> objectLargestPackUID = new Dictionary<string, Dictionary<long, ulong>>(); |
| | | |
| | | protected void OnDisable() |
| | | { |
| | | CleanupTips(); |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 公共方法 - 初始化 |
| | | |
| | | 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); |
| | | if (battleObject is HeroBattleObject heroBattleObject) |
| | | { |
| | | heroInfoContainer.SetHeroInfo(heroBattleObject.teamHero); |
| | | } |
| | | CleanupTips(); |
| | | InitBuff(); |
| | | |
| | | var buffMgr = battleObject.GetBuffMgr(); |
| | | if (buffMgr != null) // 命格不有 buff |
| | | { |
| | | RefreshBuff(buffMgr.GetBuffIconList()); |
| | | } |
| | | |
| | | if (!battleObject.IsTianziBoss()) |
| | | { |
| | | UpdateHP(battleObject.GetCurHp(), battleObject.GetCurHp(), battleObject.GetMaxHp(), false); |
| | | } |
| | | |
| | | UpdateXP(battleObject.GetRage(), battleObject.GetRage(), 100, false); |
| | | |
| | | long shieldValue = buffMgr != null ? buffMgr.GetShieldValue() : 0; // 命格没有护盾 |
| | | long curHp = battleObject.GetCurHp(); |
| | | long maxHp = battleObject.GetMaxHp(); |
| | | |
| | | // 记录设置前的护盾值 |
| | | 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}"); |
| | | } |
| | | |
| | | protected void InitBuff() |
| | | { |
| | | for (int i = 0; i < buffCells.Count; i++) |
| | | { |
| | | buffCells[i].SetActive(false); |
| | | } |
| | | } |
| | | |
| | | public void SetActive(bool active) |
| | |
| | | gameObject.SetActive(active); |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 公共方法 - Buff管理 |
| | | |
| | | public void RefreshBuff(List<HB428_tagSCBuffRefresh> datas) |
| | | { |
| | | if (buffCells.IsNullOrEmpty()) |
| | | return; |
| | | RefreshBuffCells(buffCells, datas); |
| | | |
| | | for (int i = 0; i < buffCells.Count; i++) |
| | | // check shield buff |
| | | var buffMgr = battleObject.GetBuffMgr(); |
| | | long shieldValue = buffMgr != null ? buffMgr.GetShieldValue() : 0; // 命格没有护盾 |
| | | long curHp = battleObject.GetCurHp(); |
| | | long maxHp = battleObject.GetMaxHp(); |
| | | |
| | | // 记录设置前的护盾值 |
| | | 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}"); |
| | | } |
| | | |
| | | protected void RefreshBuffCells(List<BattleBuffCell> cells, List<HB428_tagSCBuffRefresh> datas) |
| | | { |
| | | if (datas == null) |
| | | { |
| | | if (i < datas.Count) |
| | | for (int i = 0; i < cells.Count; i++) |
| | | { |
| | | buffCells[i].SetActive(true); |
| | | buffCells[i].Init(datas[i], OnBuffCellClicked); |
| | | cells[i].SetActive(false); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | if (battleObject.battleField.battleSwitch.BuffIcon) |
| | | { |
| | | for (int i = 0; i < cells.Count; i++) |
| | | { |
| | | var cell = cells[i]; |
| | | if (i < datas.Count) |
| | | { |
| | | cell.SetActive(true); |
| | | HB428_tagSCBuffRefresh buffData = datas[i]; |
| | | SkillConfig skillConfig = SkillConfig.Get((int)buffData.SkillID); |
| | | cell.Init(buffData, () => |
| | | { |
| | | // 点击buff图标 显示buff描述/当前身上所有buff |
| | | }); |
| | | } |
| | | else |
| | | { |
| | | cell.SetActive(false); |
| | | } |
| | | } |
| | | |
| | | |
| | | buffInfoButton.SetListener(() => |
| | | { |
| | | if (datas.IsNullOrEmpty()) return; |
| | | |
| | | string clickBuffBattleName = battleObject?.battleField?.ToString(); |
| | | if (clickBuffBattleName == BattleConst.StoryBattleField) return; |
| | | |
| | | EventBroadcast.Instance.Broadcast(EventName.BATTLE_CLICK_BUFF, new BattleClickBuffData() |
| | | { |
| | | isMySide = battleObject?.Camp == BattleCamp.Red, |
| | | heroID = (battleObject as HeroBattleObject)?.teamHero?.heroId ?? 0, |
| | | skinID = (battleObject as HeroBattleObject)?.teamHero?.SkinID ?? 0, |
| | | datas = datas, |
| | | }); |
| | | }); |
| | | } |
| | | else |
| | | { |
| | | buffCells[i].SetActive(false); |
| | | for (int i = 0; i < cells.Count; i++) |
| | | { |
| | | cells[i].SetActive(false); |
| | | } |
| | | } |
| | | |
| | | } |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 公共方法 - 飘字管理 |
| | | |
| | | /// <summary> |
| | | /// 添加飘字到队列 |
| | | /// 添加飘字到队列(非伤害飘字) |
| | | /// </summary> |
| | | public void ShowTips(string message, bool useArtText = false, bool followCharacter = true, float scaleRatio = 1f) |
| | | { |
| | | messages.Add(new TipsInfo |
| | | if (battleObject.battleField.battleSwitch.NonDamageTips) |
| | | { |
| | | message = message, |
| | | useArtText = useArtText, |
| | | followCharacter = followCharacter, |
| | | scaleRatio = scaleRatio |
| | | }); |
| | | messages.Add(new TipsInfo |
| | | { |
| | | message = message, |
| | | useArtText = useArtText, |
| | | followCharacter = followCharacter, |
| | | scaleRatio = scaleRatio |
| | | }); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 添加自定义飘字配置到队列 |
| | | /// 添加自定义飘字配置到队列(非伤害飘字) |
| | | /// </summary> |
| | | public void ShowTips(TipsInfo tipsInfo) |
| | | { |
| | | messages.Add(tipsInfo); |
| | | // BUFF飘字 |
| | | if (tipsInfo.useBuffColor) |
| | | { |
| | | if (battleObject.battleField.battleSwitch.BuffAction) |
| | | { |
| | | messages.Add(tipsInfo); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | // 非伤害飘字 |
| | | if (battleObject.battleField.battleSwitch.NonDamageTips) |
| | | { |
| | | messages.Add(tipsInfo); |
| | | } |
| | | } |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 公共方法 - 数值更新 |
| | | |
| | | /// <summary> |
| | | /// 更新血量显示 |
| | |
| | | |
| | | if (tween) |
| | | { |
| | | // 关键修复:先设置起始值,再播放动画到目标值 |
| | | sliderHp.value = fromValue; // ← 这行是关键! |
| | | hpTween = sliderHp.DOValue(targetValue, 0.3f).SetAutoKill(false); |
| | | hpTween = DOTween.Sequence(); |
| | | // sliderHp.value = fromValue; |
| | | float diff = targetValue - fromValue; |
| | | float cost = Mathf.Lerp(0, 1f, diff); |
| | | |
| | | hpTween.Append(sliderHp.DOValue(targetValue, cost).SetAutoKill(false)); |
| | | hpTween.Join(sliderSlowHp.DOValue(targetValue, cost * 1.5f).SetAutoKill(false)); |
| | | hpTween.onComplete += () => |
| | | { |
| | | sliderHp.value = targetValue; |
| | | sliderSlowHp.value = targetValue; |
| | | }; |
| | | // sliderSlowHp |
| | | battleObject.battleField.battleTweenMgr.OnPlayTween(hpTween); |
| | | } |
| | | else |
| | | { |
| | | sliderHp.value = targetValue; |
| | | sliderSlowHp.value = targetValue; |
| | | } |
| | | } |
| | | |
| | | |
| | | /// <summary> |
| | | /// !!!临时的用于天子更新血量显示,等接口完善后删除 |
| | |
| | | public void UpdateHP(float value) |
| | | { |
| | | sliderHp.value = value; |
| | | sliderSlowHp.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(() => |
| | | { |
| | | maxXpGO.SetActive(toXp >= maxXp); |
| | | }); |
| | | battleObject.battleField.battleTweenMgr.OnPlayTween(xpTween); |
| | | } |
| | | else |
| | | { |
| | | if (toXp >= maxXp) |
| | | { |
| | | maxXpGO.SetActive(true); |
| | | } |
| | | else |
| | | { |
| | | maxXpGO.SetActive(false); |
| | | } |
| | | sliderXp.value = targetValue; |
| | | } |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 公共方法 - 运行时更新 |
| | | /// <summary> |
| | | /// 播放血条 护盾的变化 |
| | | /// </summary> |
| | | public void UpdateDamage(BattleDmgInfo dmgInfo) |
| | | { |
| | | // 验证数据有效性,防止空引用 |
| | | if (dmgInfo?.battleHurtParam == null) |
| | | return; |
| | | |
| | | // 检查受击者对象有效性 |
| | | if (dmgInfo.battleHurtParam.hurter?.hurtObj != null) |
| | | { |
| | | var hurtObj = dmgInfo.battleHurtParam.hurter.hurtObj; |
| | | // 检查对象是否已被销毁 |
| | | if (hurtObj == null || hurtObj.Equals(null)) |
| | | { |
| | | Debug.LogWarning($"[UpdateDamage] 受击者对象已被销毁,跳过伤害更新"); |
| | | return; |
| | | } |
| | | |
| | | // 验证是否能安全获取 maxHp(间接检查 HeroBattleObject 内部状态) |
| | | if (hurtObj is HeroBattleObject && hurtObj.GetMaxHp() <= 0) |
| | | { |
| | | Debug.LogWarning($"[UpdateDamage] 受击者 maxHp 无效,跳过伤害更新"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 检查施法者对象有效性 |
| | | if (dmgInfo.battleHurtParam.caster?.casterObj != null) |
| | | { |
| | | var casterObj = dmgInfo.battleHurtParam.caster.casterObj; |
| | | // 检查对象是否已被销毁 |
| | | if (casterObj == null || casterObj.Equals(null)) |
| | | { |
| | | Debug.LogWarning($"[UpdateDamage] 施法者对象已被销毁,跳过伤害更新"); |
| | | return; |
| | | } |
| | | |
| | | // 验证是否能安全获取 maxHp |
| | | if (casterObj is HeroBattleObject && casterObj.GetMaxHp() <= 0) |
| | | { |
| | | Debug.LogWarning($"[UpdateDamage] 施法者 maxHp 无效,跳过伤害更新"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 数据验证通过,加入队列 |
| | | damageUpdateQueue.Enqueue(dmgInfo); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 实际执行伤害更新(统一处理目标和施法者) |
| | | /// </summary> |
| | | private void ExecuteDamageUpdate(BattleDmgInfo dmgInfo) |
| | | { |
| | | KillTween(ref damageSequence); |
| | | |
| | | string guid = battleObject.battleField.guid; |
| | | long objID = battleObject.ObjID; |
| | | ulong currentPackUID = dmgInfo.battleHurtParam.packUID; |
| | | |
| | | // 获取该战场的对象PackUID字典(用于按角色+角色身份粒度判断) |
| | | Dictionary<long, ulong> objPackUIDDict = null; |
| | | if (objectLargestPackUID.ContainsKey(guid)) |
| | | { |
| | | objPackUIDDict = objectLargestPackUID[guid]; |
| | | } |
| | | |
| | | long maxHp, fromHp, toHp, fromShield, toShield; |
| | | |
| | | // 优先判断当前InfoBar是否为受击者(血量变化总是体现在hurter里) |
| | | BattleHurtObj hurter = dmgInfo.battleHurtParam.hurter; |
| | | if (hurter?.hurtObj != null && hurter.hurtObj.ObjID == objID) |
| | | { |
| | | // 按对象+受击者身份检查PackUID,避免不同身份(hurter/caster)的包互相阻拦 |
| | | long hurterKey = objID * 2; // hurter用偶数key |
| | | if (objPackUIDDict != null |
| | | && objPackUIDDict.ContainsKey(hurterKey) |
| | | && currentPackUID < objPackUIDDict[hurterKey]) |
| | | { |
| | | // Debug.LogWarning($"[ExecuteDamageUpdate] 忽略旧包(受击者) - ObjID:{objID}, 当前PackUID:{currentPackUID} < 对象受击最大PackUID:{objPackUIDDict[hurterKey]}"); |
| | | return; |
| | | } |
| | | |
| | | // 当前InfoBar是受击者(包括给自己治疗、给自己造成伤害的情况) |
| | | if (hurter.hurtObj.IsTianziBoss()) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | // 直接使用 dmgInfo 中的数据(已经被 CompareAndExchangeLargestPackUIDHp 验证过) |
| | | maxHp = hurter.maxHp; |
| | | fromHp = hurter.fromHp; |
| | | toHp = hurter.toHp; |
| | | fromShield = hurter.fromShieldValue; |
| | | toShield = hurter.toShieldValue; |
| | | |
| | | // 更新该对象作为受击者的最大PackUID |
| | | if (objPackUIDDict != null) |
| | | { |
| | | objPackUIDDict[hurterKey] = currentPackUID; |
| | | } |
| | | |
| | | // 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; |
| | | } |
| | | |
| | | // 按对象+施法者身份检查PackUID |
| | | long casterKey = objID * 2 + 1; // caster用奇数key |
| | | if (objPackUIDDict != null |
| | | && objPackUIDDict.ContainsKey(casterKey) |
| | | && currentPackUID < objPackUIDDict[casterKey]) |
| | | { |
| | | // Debug.LogWarning($"[ExecuteDamageUpdate] 忽略旧包(施法者) - ObjID:{objID}, 当前PackUID:{currentPackUID} < 对象施法最大PackUID:{objPackUIDDict[casterKey]}"); |
| | | return; |
| | | } |
| | | |
| | | if (caster.casterObj.IsTianziBoss()) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | // 直接使用 dmgInfo 中的数据(已经被 CompareAndExchangeLargestPackUIDHp 验证过) |
| | | maxHp = caster.maxHp; |
| | | fromHp = caster.fromHp; |
| | | toHp = caster.toHp; |
| | | fromShield = caster.fromShieldValue; |
| | | toShield = caster.toShieldValue; |
| | | |
| | | // 更新该对象作为施法者的最大PackUID |
| | | if (objPackUIDDict != null) |
| | | { |
| | | objPackUIDDict[casterKey] = currentPackUID; |
| | | } |
| | | |
| | | // 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; |
| | | // sliderSlowHp.value = fromHpValue; |
| | | float diff = Mathf.Abs(toHpValue - fromHpValue); |
| | | float cost = Mathf.Lerp(0, 1f, diff); |
| | | |
| | | if (Mathf.Abs(fromHpValue - toHpValue) > 0.001f) |
| | | { |
| | | damageSequence.Append(sliderHp.DOValue(toHpValue, cost)); |
| | | damageSequence.Join(sliderSlowHp.DOValue(toHpValue, cost * 1.5f)); |
| | | } |
| | | |
| | | damageSequence.onComplete += () => |
| | | { |
| | | sliderHp.value = toHpValue; |
| | | sliderSlowHp.value = toHpValue; |
| | | }; |
| | | |
| | | damageSequence.Play(); |
| | | battleObject.battleField.battleTweenMgr.OnPlayTween(damageSequence); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 每帧更新 |
| | | /// </summary> |
| | | public void Run() |
| | | { |
| | | // 处理血条和伤害队列 |
| | | UpdateHpAndDamageQueue(); |
| | | |
| | | // 更新飘字GCD并处理队列 |
| | | UpdateTipsGCDAndQueue(); |
| | | |
| | | // 更新所有飘字 |
| | | UpdateActiveTips(); |
| | | |
| | | // 处理飘字队列 |
| | | ProcessTipsQueue(); |
| | | } |
| | | |
| | | /// <summary> |
| | |
| | | } |
| | | } |
| | | |
| | | #endregion |
| | | 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<long, long>(); |
| | | } |
| | | if (!largestPackUIDAllObjectsMaxHp.ContainsKey(guid)) |
| | | { |
| | | largestPackUIDAllObjectsMaxHp[guid] = new Dictionary<long, long>(); |
| | | } |
| | | if (!objectLargestPackUID.ContainsKey(guid)) |
| | | { |
| | | objectLargestPackUID[guid] = new Dictionary<long, ulong>(); |
| | | } |
| | | |
| | | ulong currentLargestPackUID = largestPackUID[guid]; |
| | | Dictionary<long, long> hpDict = largestPackUIDAllObjectsToHp[guid]; |
| | | Dictionary<long, long> maxHpDict = largestPackUIDAllObjectsMaxHp[guid]; |
| | | Dictionary<long, ulong> objPackUIDDict = objectLargestPackUID[guid]; |
| | | |
| | | // 如果遇到更大的packUID,更新标记(不清空数据,保留所有证据) |
| | | if (currentPackUID > currentLargestPackUID) |
| | | { |
| | | // Debug.LogError($"[血量记录] 检测到新批次PackUID: {currentPackUID} > {currentLargestPackUID},保留所有历史数据"); |
| | | largestPackUID[guid] = currentPackUID; |
| | | currentLargestPackUID = currentPackUID; |
| | | } |
| | | |
| | | // 记录所有packUID的数据(包括早触发的包),但只采用最大packUID的数据 |
| | | // 记录施法者的血量变化 |
| | | BattleCastObj battleCastObj = dmgInfo.battleHurtParam.caster; |
| | | if (battleCastObj != null && battleCastObj.casterObj != null) |
| | | { |
| | | long casterID = battleCastObj.casterObj.ObjID; |
| | | |
| | | // 获取旧血量用于计算变化 |
| | | long oldHp = hpDict.ContainsKey(casterID) ? hpDict[casterID] : battleCastObj.fromHp; |
| | | long newHp = battleCastObj.toHp; |
| | | long maxHp = battleCastObj.maxHp; |
| | | long hpChange = newHp - oldHp; |
| | | |
| | | // 只有当前packUID不小于该对象的最大packUID时才更新记录 |
| | | ulong casterLastPackUID = objPackUIDDict.ContainsKey(casterID) ? objPackUIDDict[casterID] : 0ul; |
| | | if (currentPackUID >= casterLastPackUID) |
| | | { |
| | | hpDict[casterID] = newHp; |
| | | maxHpDict[casterID] = maxHp; |
| | | objPackUIDDict[casterID] = currentPackUID; |
| | | |
| | | // 打印血量变化日志(施法者通常是恢复生命) |
| | | // 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:{casterLastPackUID}"); |
| | | } |
| | | } |
| | | |
| | | // 记录受击者的血量变化 |
| | | BattleHurtObj battleHurtObj = dmgInfo.battleHurtParam.hurter; |
| | | if (battleHurtObj != null && battleHurtObj.hurtObj != null) |
| | | { |
| | | BattleObject hurter = battleHurtObj.hurtObj; |
| | | long hurterID = hurter.ObjID; |
| | | |
| | | // 获取旧血量用于计算伤害 |
| | | long oldHp = hpDict.ContainsKey(hurterID) ? hpDict[hurterID] : battleHurtObj.fromHp; |
| | | long newHp = battleHurtObj.toHp; |
| | | long maxHp = battleHurtObj.maxHp; |
| | | long damage = oldHp - newHp; |
| | | |
| | | // 只有当前packUID不小于该对象的最大packUID时才更新记录 |
| | | ulong hurterLastPackUID = objPackUIDDict.ContainsKey(hurterID) ? objPackUIDDict[hurterID] : 0ul; |
| | | if (currentPackUID >= hurterLastPackUID) |
| | | { |
| | | hpDict[hurterID] = newHp; |
| | | maxHpDict[hurterID] = maxHp; |
| | | objPackUIDDict[hurterID] = currentPackUID; |
| | | |
| | | // 打印血量变化日志 |
| | | // 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:{hurterLastPackUID}"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | #region 私有方法 - 飘字处理 |
| | | /// <summary> |
| | | /// 处理血条和伤害更新队列 |
| | | /// </summary> |
| | | private void UpdateHpAndDamageQueue() |
| | | { |
| | | // 优先处理UpdateDamage |
| | | if (damageUpdateQueue.Count > 0) |
| | | { |
| | | BattleDmgInfo dmgInfo = damageUpdateQueue.Dequeue(); |
| | | CompareAndExchangeLargestPackUIDHp(dmgInfo); |
| | | ExecuteDamageUpdate(dmgInfo); |
| | | 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> |
| | | /// 立即弹出飘字 |
| | |
| | | { |
| | | // 创建飘字实例 |
| | | 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); |
| | | |
| | | tips.SetRatio(battleObject.battleField.speedRatio, tipsInfo.scaleRatio); |
| | | tips.ShowBackground(tipsInfo.showBackground); |
| | | tips.SetText(tipsInfo.message, tipsInfo.useArtText, false); |
| | | |
| | | // 添加到列表 |
| | | tipsList.Add(tips); |
| | | } |
| | |
| | | { |
| | | Transform parent = tipsInfo.followCharacter |
| | | ? transform |
| | | : battleObject.battleField.battleRootNode.transform; |
| | | : battleObject.battleField.battleRootNode.notFollowTipsAdjuster.transform; |
| | | |
| | | GameObject go = GameObject.Instantiate(textTips.gameObject, parent); |
| | | return go.GetComponent<BattleTips>(); |
| | |
| | | /// </summary> |
| | | private void ConfigureTips(BattleTips tips, TipsInfo tipsInfo) |
| | | { |
| | | FloatingConfig targetConfig = tipsInfo.followCharacter |
| | | FloatingConfig targetConfig = tipsInfo.isRage ? rageFloatingConfig : tipsInfo.followCharacter |
| | | ? followFloatingConfig |
| | | : noFollowFloatingConfig; |
| | | |
| | |
| | | private void RemoveTips(BattleTips tips) |
| | | { |
| | | tipsList.Remove(tips); |
| | | tips.controller = null; |
| | | GameObject.DestroyImmediate(tips.gameObject); |
| | | } |
| | | |
| | |
| | | { |
| | | 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 ProcessTipsQueue() |
| | | { |
| | | timer += GetDeltaTime(); |
| | | |
| | | if (messages.Count > 0 && timer >= PopUpInterval) |
| | | { |
| | | TipsInfo tipsInfo = messages[0]; |
| | | messages.RemoveAt(0); |
| | | |
| | | PopUpTipsDirectly(tipsInfo); |
| | | |
| | | timer = 0f; |
| | | } |
| | | } |
| | | |
| | |
| | | { |
| | | messages.Clear(); |
| | | |
| | | foreach (var tip in tipsList) |
| | | for (int i = tipsList.Count - 1; i >= 0; i--) |
| | | { |
| | | tip.OnFinish = null; |
| | | GameObject.DestroyImmediate(tip.gameObject); |
| | | RemoveTips(tipsList[i]); |
| | | } |
| | | |
| | | tipsList.Clear(); |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 私有方法 - 辅助方法 |
| | | |
| | | /// <summary> |
| | | /// 停止并清理Tween |
| | | /// </summary> |
| | | private void KillTween(ref Tween tween) |
| | | private void KillTween<T>(ref T tween) where T : Tween |
| | | { |
| | | if (tween != null && battleObject != null) |
| | | { |
| | |
| | | { |
| | | // TODO: 显示buff描述/当前身上所有buff |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | /// <summary> |
| | | /// 清理指定战场的静态PackUID和血量记录 |
| | | /// </summary> |
| | | public static void ClearStaticBattleData(string guid) |
| | | { |
| | | largestPackUID.Remove(guid); |
| | | largestPackUIDAllObjectsToHp.Remove(guid); |
| | | largestPackUIDAllObjectsMaxHp.Remove(guid); |
| | | objectLargestPackUID.Remove(guid); |
| | | } |
| | | |
| | | public void HaveRest() |
| | | { |
| | | CleanupTips(); |
| | | SetActive(false); |
| | | |
| | | // 关掉所有的tween |
| | | KillTween(ref hpTween); |
| | | KillTween(ref xpTween); |
| | | KillTween(ref damageSequence); |
| | | } |
| | | } |