Main/System/Battle/BattleConst.cs
@@ -202,4 +202,5 @@ PassiveSkillLimitGroup, }; public const int ShieldBuffAttackType = 1003;//护盾吸收伤害类型ID } Main/System/Battle/BattleHUDWin.cs
@@ -10,6 +10,10 @@ // 这个界面是 persistent的界面 public class BattleHUDWin : UIBase { private const int CASTER_DAMAGE_HEIGHT_OFFSET = 100; // 施法者伤害高度间隔 private const int CASTER_DAMAGE_FLOAT_HEIGHT = 150; // 飘字向上移动高度 private const int TARGET_DAMAGE_FLOAT_HEIGHT = 150; // 目标伤害飘字向上移动高度 private GameObjectPoolManager.GameObjectPool damagePrefabPool; private GameObjectPoolManager.GameObjectPool buffIconPrefabPool; private GameObjectPoolManager.GameObjectPool buffLabelPrefabPool; @@ -29,35 +33,14 @@ protected override void OnPreOpen() { base.OnPreOpen(); EventBroadcast.Instance.AddListener<BattleDmgInfo>(EventName.BATTLE_DAMAGE_TAKEN, OnDamageTaken); EventBroadcast.Instance.AddListener<string, JsonData>(EventName.BATTLE_END, OnBattleEnd); damagePrefabPool = GameObjectPoolManager.Instance.RequestPool(UILoader.LoadPrefab("DamageContent")); } private void OnBattleEnd(string guid, JsonData data) { ClearContent(guid); } private void ClearContent(string guid, bool force = false) { if ((battleField != null && battleField.guid == guid) || force) { for (int i = damageContentList.Count - 1; i >= 0; i--) { var content = damageContentList[i]; content.Stop(); RemoveDamageContent(content); } damageContentList.Clear(); } RegisterEvents(); InitializePools(); } protected override void OnPreClose() { base.OnPreClose(); EventBroadcast.Instance.RemoveListener<BattleDmgInfo>(EventName.BATTLE_DAMAGE_TAKEN, OnDamageTaken); EventBroadcast.Instance.RemoveListener<string, JsonData>(EventName.BATTLE_END, OnBattleEnd); UnregisterEvents(); } protected override void OnOpen() @@ -68,13 +51,7 @@ protected override void OnClose() { base.OnClose(); if (battleField != null) { battleField.OnBattlePause -= OnBattlePause; battleField.OnBattleRun -= OnBattleRun; battleField.OnSpeedRatioChange -= OnSpeedRatioChange; battleField = null; } CleanupBattleField(); } protected override void NextFrameAfterOpen() @@ -87,121 +64,65 @@ base.CompleteClose(); } private void RemoveDamageContent(DamageContent content) private void RegisterEvents() { damageContentList.Remove(content); damagePrefabPool.Release(content.gameObject); EventBroadcast.Instance.AddListener<BattleDmgInfo>(EventName.BATTLE_DAMAGE_TAKEN, OnDamageTaken); EventBroadcast.Instance.AddListener<string, JsonData>(EventName.BATTLE_END, OnBattleEnd); } private void UnregisterEvents() { EventBroadcast.Instance.RemoveListener<BattleDmgInfo>(EventName.BATTLE_DAMAGE_TAKEN, OnDamageTaken); EventBroadcast.Instance.RemoveListener<string, JsonData>(EventName.BATTLE_END, OnBattleEnd); } private void InitializePools() { damagePrefabPool = GameObjectPoolManager.Instance.RequestPool(UILoader.LoadPrefab("DamageContent")); } public void SetBattleField(BattleField _battleField) { CleanupBattleField(); ClearContent(string.Empty, true); battleField = _battleField; RegisterBattleFieldEvents(); } private void RegisterBattleFieldEvents() { if (battleField == null) return; battleField.OnBattlePause += OnBattlePause; battleField.OnBattleRun += OnBattleRun; battleField.OnSpeedRatioChange += OnSpeedRatioChange; } private void UnregisterBattleFieldEvents() { if (battleField == null) return; battleField.OnBattlePause -= OnBattlePause; battleField.OnBattleRun -= OnBattleRun; battleField.OnSpeedRatioChange -= OnSpeedRatioChange; } private void CleanupBattleField() { UnregisterBattleFieldEvents(); battleField = null; } private void OnBattleEnd(string guid, JsonData data) { ClearContent(guid); } private void OnDamageTaken(BattleDmgInfo damageInfo) { SetTargetDamage(damageInfo); SetSelfDamage(damageInfo); } private void SetSelfDamage(BattleDmgInfo damageInfo) { if (damageInfo.casterDamageList.Count > 0) { GameObject damageContent = damagePrefabPool.Request(); DamageContent content = damageContent.GetComponent<DamageContent>(); damageContent.transform.SetParent(damageNode, false); var heroRect = damageInfo.casterObj.heroRectTrans; if (heroRect == null) { damagePrefabPool.Release(damageContent); return; } var contentRect = content.GetComponent<RectTransform>(); var contentParentRect = contentRect.parent as RectTransform; // 获取 heroRect 的世界坐标(锚点为中心) Vector3 worldTargetPos = heroRect.transform.TransformPoint(heroRect.rect.center); // 转换到 content 父节点下的 anchoredPosition Vector2 anchoredPos; RectTransformUtility.ScreenPointToLocalPointInRectangle( contentParentRect, RectTransformUtility.WorldToScreenPoint(null, worldTargetPos), null, out anchoredPos); // 设置动态位置(会覆盖配置中的位置) Vector2 beginPos = anchoredPos; Vector2 endPos = anchoredPos + new Vector2(0, 150); content.SetPosition(beginPos, endPos); // 设置速度比例 if (battleField != null) { content.SetRatio(battleField.speedRatio, 1f); } content.SetDamage(damageInfo, damageInfo.casterDamageList, () => RemoveDamageContent(content)); damageContentList.Add(content); } } private void SetTargetDamage(BattleDmgInfo damageInfo) { if (damageInfo.targetDamageList.Count > 0) { GameObject damageContent = damagePrefabPool.Request(); DamageContent content = damageContent.GetComponent<DamageContent>(); damageContent.transform.SetParent(damageNode, false); var heroRect = damageInfo.hurtObj.heroRectTrans; if (heroRect == null) { damagePrefabPool.Release(damageContent); return; } var contentRect = content.GetComponent<RectTransform>(); var contentParentRect = contentRect.parent as RectTransform; // 获取 heroRect 的世界坐标(锚点为中心) Vector3 worldTargetPos = heroRect.transform.TransformPoint(heroRect.rect.center); // 转换到 content 父节点下的 anchoredPosition Vector2 anchoredPos; RectTransformUtility.ScreenPointToLocalPointInRectangle( contentParentRect, RectTransformUtility.WorldToScreenPoint(null, worldTargetPos), null, out anchoredPos); // 设置动态位置(会覆盖配置中的位置) Vector2 beginPos = anchoredPos; Vector2 endPos = anchoredPos + new Vector2(0, 150); content.SetPosition(beginPos, endPos); // 设置速度比例 if (battleField != null) { content.SetRatio(battleField.speedRatio, 1f); } content.SetDamage(damageInfo, damageInfo.targetDamageList, () => RemoveDamageContent(content)); damageContentList.Add(content); } } public void SetBattleField(BattleField _battleField) { if (battleField != null) { battleField.OnBattlePause -= OnBattlePause; battleField.OnBattleRun -= OnBattleRun; battleField.OnSpeedRatioChange -= OnSpeedRatioChange; } ClearContent(string.Empty, true); battleField = _battleField; battleField.OnBattlePause += OnBattlePause; battleField.OnBattleRun += OnBattleRun; battleField.OnSpeedRatioChange += OnSpeedRatioChange; } private void OnSpeedRatioChange(float newSpeedRatio) @@ -216,21 +137,166 @@ { if (isPause) { PauseAllDamageContent(); } else { ResumeAllDamageContent(); } } private void OnBattleRun() { RunAllDamageContent(); } /// <summary> /// 设置目标受到的伤害显示 /// </summary> private void SetTargetDamage(BattleDmgInfo damageInfo) { if (damageInfo.targetDamageList.Count == 0) return; RectTransform heroRect = damageInfo.hurtObj.heroRectTrans; if (heroRect == null) return; DamageContent content = CreateDamageContent(); if (content == null) return; Vector2 anchoredPos = CalculateWorldToLocalPosition(heroRect, content); SetupTargetDamagePosition(content, anchoredPos); SetupDamageContent(content, damageInfo.targetDamageList, damageInfo); } /// <summary> /// 设置目标伤害的起始和结束位置 /// </summary> private void SetupTargetDamagePosition(DamageContent content, Vector2 anchoredPos) { // 保持英雄的 X 坐标,只在 Y 轴向上飘 Vector2 beginPos = anchoredPos; Vector2 endPos = anchoredPos + new Vector2(0, TARGET_DAMAGE_FLOAT_HEIGHT); content.SetPosition(beginPos, endPos); } /// <summary> /// 设置施法者受到的伤害显示(反伤、吸血等) /// </summary> private void SetSelfDamage(BattleDmgInfo damageInfo) { if (damageInfo.casterDamageList.Count == 0) return; RectTransform heroRect = damageInfo.casterObj.heroRectTrans; if (heroRect == null) return; // 创建单个DamageContent显示所有施法者伤害 DamageContent content = CreateDamageContent(); if (content == null) return; Vector2 anchoredPos = CalculateWorldToLocalPosition(heroRect, content); SetupCasterDamagePosition(content, anchoredPos); SetupDamageContent(content, damageInfo.casterDamageList, damageInfo); } /// <summary> /// 设置施法者伤害的起始和结束位置 /// </summary> private void SetupCasterDamagePosition(DamageContent content, Vector2 anchoredPos) { // 保持英雄的 X 坐标,只在 Y 轴向上飘 Vector2 beginPos = anchoredPos; Vector2 endPos = anchoredPos + new Vector2(0, CASTER_DAMAGE_FLOAT_HEIGHT); content.SetPosition(beginPos, endPos); } /// <summary> /// 创建一个DamageContent对象 /// </summary> private DamageContent CreateDamageContent() { GameObject damageContentObj = damagePrefabPool.Request(); DamageContent content = damageContentObj.GetComponent<DamageContent>(); damageContentObj.transform.SetParent(damageNode, false); return content; } /// <summary> /// 计算英雄世界坐标到本地坐标的转换 /// </summary> private Vector2 CalculateWorldToLocalPosition(RectTransform heroRect, DamageContent content) { RectTransform contentRect = content.GetComponent<RectTransform>(); RectTransform contentParentRect = contentRect.parent as RectTransform; Vector3 worldTargetPos = heroRect.transform.TransformPoint(heroRect.rect.center); Vector2 anchoredPos; RectTransformUtility.ScreenPointToLocalPointInRectangle( contentParentRect, RectTransformUtility.WorldToScreenPoint(null, worldTargetPos), null, out anchoredPos); return anchoredPos; } /// <summary> /// 配置DamageContent的速度、伤害数据和回调 /// </summary> private void SetupDamageContent(DamageContent content, List<BattleDmg> damageList, BattleDmgInfo damageInfo) { if (battleField != null) { content.SetRatio(battleField.speedRatio, 1f); } content.SetDamage(damageInfo, damageList, () => RemoveDamageContent(content)); damageContentList.Add(content); } /// <summary> /// 移除DamageContent对象 /// </summary> private void RemoveDamageContent(DamageContent content) { damageContentList.Remove(content); damagePrefabPool.Release(content.gameObject); } /// <summary> /// 清除所有伤害显示内容 /// </summary> private void ClearContent(string guid, bool force = false) { if ((battleField != null && battleField.guid == guid) || force) { for (int i = damageContentList.Count - 1; i >= 0; i--) { var content = damageContentList[i]; content.Stop(); RemoveDamageContent(content); } damageContentList.Clear(); } } private void PauseAllDamageContent() { foreach (var content in damageContentList) { content.Stop(); } } else private void ResumeAllDamageContent() { foreach (var content in damageContentList) { content.Resume(); } } } private void OnBattleRun() private void RunAllDamageContent() { for (int i = damageContentList.Count - 1; i >= 0; i--) { Main/System/Battle/BattleManager.cs
@@ -495,6 +495,13 @@ BattleDebug.LogError("战场已存在 先进行销毁"); battleField.Destroy(); } } var bf = GetBattleFieldByMapID(MapID); if (bf != null && !string.IsNullOrEmpty(guid)) { BattleDebug.LogError("相同地图ID的战场已存在 先进行销毁"); bf.Destroy(); } if (isCreate) Main/System/Battle/BattleObject/BattleObject.cs
@@ -170,12 +170,12 @@ { case PlayerDataType.HP: long toHp = GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx); heroInfoBar.UpdateHP(teamHero.curHp, toHp, teamHero.maxHp); heroInfoBar.UpdateHP(teamHero.curHp, toHp, teamHero.maxHp, false); teamHero.curHp = GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx); break; case PlayerDataType.MaxHP: teamHero.maxHp = GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx); heroInfoBar.UpdateHP(teamHero.curHp, teamHero.curHp, teamHero.maxHp); heroInfoBar.UpdateHP(teamHero.curHp, teamHero.curHp, teamHero.maxHp, false); break; case PlayerDataType.XP: long toXp = GeneralDefine.GetFactValue(_refreshInfo.Value, _refreshInfo.ValueEx); @@ -198,12 +198,12 @@ { case PlayerDataType.HP: long toHp = GeneralDefine.GetFactValue(vNetData.Value, vNetData.ValueEx); heroInfoBar.UpdateHP(teamHero.curHp, toHp, teamHero.maxHp); heroInfoBar.UpdateHP(teamHero.curHp, toHp, teamHero.maxHp, false); teamHero.curHp = GeneralDefine.GetFactValue(vNetData.Value, vNetData.ValueEx); break; case PlayerDataType.MaxHP: teamHero.maxHp = GeneralDefine.GetFactValue(vNetData.Value, vNetData.ValueEx); heroInfoBar.UpdateHP(teamHero.curHp, teamHero.curHp, teamHero.maxHp); heroInfoBar.UpdateHP(teamHero.curHp, teamHero.curHp, teamHero.maxHp, false); break; case PlayerDataType.XP: long toXp = GeneralDefine.GetFactValue(vNetData.Value, vNetData.ValueEx); @@ -365,6 +365,12 @@ motionBase.ShowIllusionShadow(true); DamageNumConfig damageNumConfig = DamageNumConfig.Get((int)DamageType.Dodge); string dodgeStr = ((char)damageNumConfig.prefix).ToString(); heroInfoBar.ShowTips(dodgeStr, true, false); tween.onComplete += () => { motionBase.ShowIllusionShadow(false); @@ -428,8 +434,7 @@ } else { // 使用传入的 fromHp 和 toHp 更新血条显示 heroInfoBar.UpdateHP(battleHurtParam.fromHp, battleHurtParam.toHp, teamHero.maxHp); heroInfoBar.UpdateDamage(battleDmgInfo); // YYL TODO 是否需要挂在在自身的follow点上 EventBroadcast.Instance.Broadcast(EventName.BATTLE_DAMAGE_TAKEN, battleDmgInfo); Main/System/Battle/BattleObject/BattleObjectFactory.cs
@@ -34,8 +34,6 @@ GameObject battleGO = ResManager.Instance.LoadAsset<GameObject>("Hero/SpineRes", "Hero_001"/*skinCfg.SpineRes*/); GameObject goParent = posNodeList[teamHero.positionNum]; BattleObject battleObject = new BattleObject(_battleField); battleObject.ObjID = teamHero.ObjID; @@ -50,7 +48,6 @@ return null; } float finalScaleRate = modelScaleRate * teamHero.modelScale; skeletonAnimation.skeletonDataAsset = skeletonDataAsset; @@ -61,6 +58,14 @@ rectTrans.anchoredPosition = Vector2.zero; battleObject.Init(realGO, teamHero, _Camp); #if UNITY_EDITOR BattleDebug.LogError( "初始化 未行动" + (battleObject.Camp == BattleCamp.Red ? "【红方】" : "【蓝方】 ") + $"武将: {battleObject.teamHero.name}\n" + $"当前血量: {battleObject.teamHero.curHp} -> 最大血量{battleObject.teamHero.maxHp}\n" ); #endif return battleObject; } Main/System/Battle/Buff/BattleObjectBuffMgr.cs
@@ -367,6 +367,31 @@ public List<HB428_tagSCBuffRefresh> GetBuffList() { return buffDataDict.Values.ToList(); } public long GetShieldValue() { // 承伤盾判断,当释放方式为1003时可以视为承伤盾 // 前端目前应该是承伤盾会用到 // Value1 当前剩余盾值求余亿部分 // Value2 当前剩余盾值整除亿部分 return GetBuffValue(BattleConst.ShieldBuffAttackType); } public long GetBuffValue(int buffAtkType) { long values = 0; foreach (var kv in buffDataDict) { HB428_tagSCBuffRefresh hB428_TagSCBuffRefresh = kv.Value; SkillConfig skillConfig = SkillConfig.Get((int)hB428_TagSCBuffRefresh.SkillID); if (null != skillConfig && skillConfig.AtkType == buffAtkType) { values += GeneralDefine.GetFactValue(hB428_TagSCBuffRefresh.Value1, hB428_TagSCBuffRefresh.Value2); } } return values; } public void InsertBuff(HB428_tagSCBuffRefresh vNetData) Main/System/Battle/Define/BattleDmgInfo.cs
@@ -1,4 +1,5 @@ using System.Collections.Generic; using UnityEngine; public class BattleDmg { @@ -11,42 +12,43 @@ } } public class BattleDmgInfo { public string battleFieldGuid { get; private set; } public BattleHurtParam battleHurtParam { get; private set; } public List<long> damageList { get { return battleHurtParam.damageList; } } public BattleObject hurtObj { get { return battleHurtParam.hurtObj; } } public BattleObject casterObj { get { return battleHurtParam.casterObj; } } public HB427_tagSCUseSkill.tagSCUseSkillHurt hurt { get { return battleHurtParam.hurt; } } public SkillConfig skillConfig { get { return battleHurtParam.skillConfig; } } // 是否被格挡了 public bool isBlocked = false; public bool isLastHit = false; public List<BattleDmg> targetDamageList = new List<BattleDmg>(); public List<BattleDmg> casterDamageList = new List<BattleDmg>(); #region Initialization public BattleDmgInfo(string battleFieldGuid, BattleHurtParam battleHurtParam) { this.battleFieldGuid = battleFieldGuid; this.battleHurtParam = battleHurtParam; this.isLastHit = battleHurtParam.hitIndex >= battleHurtParam.skillConfig.DamageDivide.Length - 1; HandleDamageType(); HandleAttackTypeAndDamage(); } #endregion #region Damage Type Processing private void HandleDamageType() { if (hurt == null) return; int attackTypes = 0; foreach (ServerDamageType serverDamageType in System.Enum.GetValues(typeof(ServerDamageType))) { @@ -59,21 +61,45 @@ hurt.AttackTypes = (uint)attackTypes; } #endregion #region Damage List Generation private void HandleAttackTypeAndDamage() { isBlocked = HaveBlockDamage(); int rawAttackType = (int)hurt.AttackTypes; float blockRatio = GeneralDefine.blockRatio; // 格挡减伤率 int rawAttackType = hurt == null ? 0 : (int)hurt.AttackTypes; // 处理每一段伤害及其对应的反伤和吸血 for (int i = 0; i < damageList.Count; i++) { long actualDamage = damageList[i]; int maxCount = CalculateMaxDamageSegmentCount(); // ============ 1. 先处理当前段对应的反伤 ============ if (battleHurtParam.reflectHpList != null && i < battleHurtParam.reflectHpList.Count) for (int i = 0; i < maxCount; i++) { long reflectHp = battleHurtParam.reflectHpList[i]; ProcessReflectDamage(i); ProcessSuckHpDamage(i); ProcessMainDamage(i, rawAttackType); } } /// <summary> /// 计算最大伤害段数 /// </summary> private int CalculateMaxDamageSegmentCount() { int maxCount = damageList != null ? damageList.Count : 0; maxCount = Mathf.Max(maxCount, battleHurtParam.suckHpList != null ? battleHurtParam.suckHpList.Count : 0); maxCount = Mathf.Max(maxCount, battleHurtParam.reflectHpList != null ? battleHurtParam.reflectHpList.Count : 0); return maxCount; } /// <summary> /// 处理反伤伤害 /// </summary> private void ProcessReflectDamage(int segmentIndex) { if (battleHurtParam.reflectHpList == null || segmentIndex >= battleHurtParam.reflectHpList.Count) return; long reflectHp = battleHurtParam.reflectHpList[segmentIndex]; if (reflectHp > 0) { casterDamageList.Add(new BattleDmg @@ -84,10 +110,15 @@ } } // ============ 2. 然后处理当前段对应的吸血 ============ if (battleHurtParam.suckHpList != null && i < battleHurtParam.suckHpList.Count) /// <summary> /// 处理吸血伤害 /// </summary> private void ProcessSuckHpDamage(int segmentIndex) { long suckHp = battleHurtParam.suckHpList[i]; if (battleHurtParam.suckHpList == null || segmentIndex >= battleHurtParam.suckHpList.Count) return; long suckHp = battleHurtParam.suckHpList[segmentIndex]; if (suckHp > 0) { casterDamageList.Add(new BattleDmg @@ -98,72 +129,110 @@ } } // ============ 3. 最后处理主要伤害 ============ // 格挡处理 /// <summary> /// 处理主要伤害 /// </summary> private void ProcessMainDamage(int segmentIndex, int rawAttackType) { if (damageList == null || segmentIndex >= damageList.Count) return; long actualDamage = damageList[segmentIndex]; if (isBlocked) { // 去掉格挡类型 ProcessBlockedDamage(actualDamage, rawAttackType); } else { ProcessNormalDamage(actualDamage, rawAttackType); } } /// <summary> /// 处理被格挡的伤害 /// </summary> private void ProcessBlockedDamage(long actualDamage, int rawAttackType) { float blockRatio = GeneralDefine.blockRatio; int attackType = rawAttackType & (~(int)DamageType.Block); // 计算格挡伤害 // 添加格挡伤害显示 long totalDamage = (long)(actualDamage / (1 - blockRatio)); long blockDmg = totalDamage - actualDamage; targetDamageList.Add(new BattleDmg { damage = blockDmg, attackType = (int)DamageType.Block }); // 真实伤害特殊处理 // 添加实际伤害显示 if (IsRealdamage()) { int showAttackType = (int)DamageType.Realdamage + (IsCrit() ? (int)DamageType.Crit : 0); targetDamageList.Add(new BattleDmg { damage = actualDamage, attackType = showAttackType }); AddRealdamageToList(actualDamage); } else { // 普通伤害/治疗处理 if (DamageNumConfig.Get(attackType) == null) { UnityEngine.Debug.LogError($"服务器给的伤害类型不对,强制转换为普通伤害/治疗, attackType: {attackType}"); if ((attackType & (int)DamageType.Damage) != 0) attackType = (int)DamageType.Damage; else if ((attackType & (int)DamageType.Recovery) != 0) attackType = (int)DamageType.Recovery; else UnityEngine.Debug.LogError($"强制转换失败,该类型不是治疗也不是伤害 {attackType}"); } targetDamageList.Add(new BattleDmg { damage = actualDamage, attackType = attackType }); AddNormalDamageToList(actualDamage, attackType); } } else { int attackType = rawAttackType; // 真实伤害特殊处理 /// <summary> /// 处理正常伤害(未被格挡) /// </summary> private void ProcessNormalDamage(long actualDamage, int rawAttackType) { if (IsRealdamage()) { AddRealdamageToList(actualDamage); } else { AddNormalDamageToList(actualDamage, rawAttackType); } } /// <summary> /// 添加真实伤害到列表 /// </summary> private void AddRealdamageToList(long damage) { int showAttackType = (int)DamageType.Realdamage + (IsCrit() ? (int)DamageType.Crit : 0); targetDamageList.Add(new BattleDmg { damage = actualDamage, attackType = showAttackType }); targetDamageList.Add(new BattleDmg { damage = damage, attackType = showAttackType }); } else /// <summary> /// 添加普通伤害/治疗到列表 /// </summary> private void AddNormalDamageToList(long damage, int attackType) { // 普通伤害/治疗处理 if (DamageNumConfig.Get(attackType) == null) attackType = ValidateAndFixAttackType(attackType); targetDamageList.Add(new BattleDmg { damage = damage, attackType = attackType }); } /// <summary> /// 验证并修复攻击类型 /// </summary> private int ValidateAndFixAttackType(int attackType) { if (DamageNumConfig.Get(attackType) != null) return attackType; UnityEngine.Debug.LogError($"服务器给的伤害类型不对,强制转换为普通伤害/治疗, attackType: {attackType}"); if ((attackType & (int)DamageType.Damage) != 0) attackType = (int)DamageType.Damage; else if ((attackType & (int)DamageType.Recovery) != 0) attackType = (int)DamageType.Recovery; else return (int)DamageType.Damage; if ((attackType & (int)DamageType.Recovery) != 0) return (int)DamageType.Recovery; UnityEngine.Debug.LogError($"强制转换失败,该类型不是治疗也不是伤害 {attackType}"); return attackType; } targetDamageList.Add(new BattleDmg { damage = actualDamage, attackType = attackType }); } } } } #endregion #region Type Checking public bool IsType(DamageType damageType) { return (hurt.AttackTypes & (int)damageType) == (int)damageType; return hurt != null && (hurt.AttackTypes & (int)damageType) == (int)damageType; } public bool IsCrit() @@ -181,6 +250,12 @@ return skillConfig.HurtType / 10 == 1; } public bool IsDamage() { return IsType(DamageType.Damage) || IsRealdamage() || IsType((DamageType)11) || IsType((DamageType)12); } #endregion } public class BattleHurtParam @@ -191,17 +266,87 @@ public List<long> suckHpList; public List<long> reflectHpList; public long fromShieldValue; public long toShieldValue; public long fromHp; public long toHp; public BattleDrops battleDrops; public HB427_tagSCUseSkill.tagSCUseSkillHurt hurt; public int hitIndex; public HB422_tagMCTurnFightObjDead deadPack; public SkillConfig skillConfig; public long maxHp; #region Shield Value Calculations public long MaxSheildValue { get { return hurtObj == null ? 0 : hurtObj.teamHero.maxHp; } } public long phase1FromShieldValue { get { if (fromShieldValue > 0) { return Mathf.Min((int)fromShieldValue, (int)MaxSheildValue); } else { return 0; } } } public long phase1ToShieldValue { get { if (toShieldValue > 0) { return Mathf.Min((int)toShieldValue, (int)MaxSheildValue); } else { return 0; } } } public long phase2FromShieldValue { get { if (fromShieldValue > MaxSheildValue) { return fromShieldValue - MaxSheildValue; } else { return 0; } } } public long phase2ToShieldValue { get { if (toShieldValue > MaxSheildValue) { return toShieldValue - MaxSheildValue; } else { return 0; } } } #endregion } Main/System/Battle/Define/DamageType.cs
@@ -12,11 +12,23 @@ // 服务器拥有的DamageType public enum ServerDamageType { Damage = 2, Recovery = 4, Block = 32, Crit = 128, Dodge = 512 Damage = 2,//普通伤害 Recovery = 4,//治疗 Immune = 16,//免疫 Block = 32, //格挡 Realdamage = 64, //真伤 Crit = 128, //暴击 Dodge = 256, //闪避 DamageReverse = 512,//伤害反转为治疗 SuckHpReverse = 1024,//吸血反转为伤害 SelfHarm = 2048,//自残 } public enum DamageType Main/System/Battle/Skill/SkillBase.cs
@@ -418,11 +418,20 @@ // battleField.battleRootNode.SetSortingOrder(); } protected long suckHp = 0; // 命中目标回调:处理所有被命中的目标 protected virtual void OnHitTargets(int _hitIndex, List<HB427_tagSCUseSkill.tagSCUseSkillHurt> hitList) { // 造成伤害前先处理血量刷新包 HandleRefreshHP(); suckHp = 0; foreach (var hurt in hitList) { suckHp += hurt.SuckHP; } foreach (var hurt in hitList) { @@ -436,6 +445,46 @@ OnHitEachTarget(_hitIndex, target, hurt); } } // protected void RecoveryHp(long suckHp, int _hitIndex) // { // // long suckHp = hurt.SuckHP; // if (suckHp <= 0) // { // return; // } // List<long> suckHpList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, suckHp); // long currentHitSuckHp = 0; // foreach (long suck in suckHpList) // { // currentHitSuckHp += suck; // } // long fromHp = caster.teamHero.curHp; // long toHp = caster.teamHero.curHp + currentHitSuckHp; // // 参数打包 // BattleHurtParam hurtParam = new BattleHurtParam() // { // casterObj = caster, // hurtObj = null, // damageList = new List<long>(), // suckHpList = suckHpList, // reflectHpList = new List<long>(), // fromHp = fromHp, // toHp = toHp, // battleDrops = null, // hurt = null, // hitIndex = _hitIndex, // deadPack = null, // skillConfig = skillConfig // }; // caster.Hurt(hurtParam); // } // 处理单个目标被命中:应用伤害和施法者效果 protected virtual void OnHitEachTarget(int _hitIndex, BattleObject target, HB427_tagSCUseSkill.tagSCUseSkillHurt hurt) @@ -463,17 +512,20 @@ long totalDamage = GeneralDefine.GetFactValue(hurt.HurtHP, hurt.HurtHPEx); List<long> damageList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, totalDamage); long totalSuckHp = suckHp; List<long> suckHpList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, totalSuckHp); // ============ 第二步:刷新实际血量 ============ long fromHp = target.teamHero.curHp; // ============处理吸血跟反伤 =============== // ============处理反伤 =============== // 也要按每一击平均算 最后要补齐伤害 long suckHp = hurt.SuckHP; long reflectHp = hurt.BounceHP; List<long> suckHpList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, suckHp); List<long> reflectHpList = BattleUtility.DivideDamageToList(skillConfig.DamageDivide, _hitIndex, reflectHp); // long currentSuckHp = suckHp / tagUseSkillAttack.HurtCount; @@ -485,11 +537,7 @@ currentHitDamage += dmg; } long currentHitSuckHp = 0; foreach (long suck in suckHpList) { currentHitSuckHp += suck; } long currentHitReflectHp = 0; foreach (long reflect in reflectHpList) @@ -497,7 +545,50 @@ currentHitReflectHp += reflect; } long toHp = Math.Max(0, fromHp - currentHitDamage); long shieldValue = target.buffMgr.GetShieldValue(); long remainSheild = shieldValue - currentHitDamage; long toShieldValue = Math.Max(0, shieldValue - currentHitDamage); long shieldRecieveDamage = shieldValue - toShieldValue; // if (shieldValue > 0 && remainSheild >= 0) // { // currentHitDamage = 0; // target.buffMgr.ReduceShieldValue(currentHitDamage); // } // else if (shieldValue > 0 && remainSheild < 0) // { // currentHitDamage = Math.Abs(remainSheild); // target.buffMgr.ClearShield(); // } long toHp = fromHp; //Math.Max(0, fromHp - currentHitDamage); // 判断是治疗还是伤害 bool isHealing = ((hurt.AttackTypes & (int)ServerDamageType.Recovery) != 0 || (hurt.AttackTypes & (int)ServerDamageType.DamageReverse) != 0) && (hurt.AttackTypes & (int)ServerDamageType.Damage) == 0 && (hurt.AttackTypes & (int)ServerDamageType.Realdamage) == 0 && (hurt.AttackTypes & (int)ServerDamageType.SuckHpReverse) == 0 && (hurt.AttackTypes & (int)ServerDamageType.SelfHarm) == 0; if (isHealing) { // 治疗逻辑:直接加血,不考虑护盾 toHp = Math.Min(target.teamHero.maxHp, fromHp + currentHitDamage); } else { // 伤害逻辑:先扣护盾,再扣血 if (remainSheild < 0) { // 盾被打破 toHp = Math.Max(0, fromHp + remainSheild); } } // 更新目标血量 @@ -505,12 +596,14 @@ #if UNITY_EDITOR BattleDebug.LogError( (caster.Camp == BattleCamp.Red ? "【红方行动】" : "【蓝方行动】") + "\n" + (caster.Camp == BattleCamp.Red ? "【红方行动】" : "【蓝方行动】 ") + $"攻击者: {caster.teamHero.name}\n" + $"目标: {target.teamHero.name}\n" + $"技能: {skillConfig.SkillName} (第{_hitIndex}击)\n" + $"伤害: {currentHitDamage} (总伤害: {totalDamage})\n" + $"吸血: {currentHitSuckHp}\n" + $"伤害: {currentHitDamage} 实际受到伤害: {fromHp-toHp} (总伤害: {totalDamage})\n" + $"承伤前护盾值: {shieldValue}\n" + $"承伤后护盾值: {toShieldValue}\n" + $"护盾承受伤害: {shieldRecieveDamage}\n" + $"反伤: {currentHitReflectHp}\n" + $"血量变化: {fromHp} -> {toHp}\n" + $"技能包里的血量是: {GeneralDefine.GetFactValue(hurt.CurHP, hurt.CurHPEx)}\n" @@ -532,10 +625,13 @@ casterObj = caster, hurtObj = target, damageList = damageList, suckHpList = suckHpList, suckHpList = suckHpList, //suckHpList, reflectHpList = reflectHpList, fromHp = fromHp, toHp = toHp, maxHp = target.teamHero.maxHp, fromShieldValue = shieldValue, toShieldValue = toShieldValue, battleDrops = battleDrops, hurt = hurt, hitIndex = _hitIndex, Main/System/Battle/UIComp/BattleFloatingUIController.cs
@@ -299,5 +299,10 @@ onFinishCallback = null; } public bool IsValid() { return ValidateConfig() && rectTransform != null && gameObject != null; } #endregion } Main/System/Battle/UIComp/BattleHeroInfoBar.cs
@@ -10,8 +10,6 @@ /// </summary> public class BattleHeroInfoBar : MonoBehaviour { #region 内部类 /// <summary> /// 飘字信息配置 /// </summary> @@ -26,13 +24,23 @@ public bool isDebuff = false; // 是否是负向 Buff(决定用哪个颜色) } #endregion #region Inspector字段 /// <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 GameObject maxXpGO; public Slider sliderShield1; public Slider sliderShield2; public BasicHeroInfoContainer heroInfoContainer; public BattleTips textTips; @@ -46,15 +54,7 @@ [Tooltip("不跟随角色的飘字配置(固定在战场节点)")] public FloatingConfig noFollowFloatingConfig; [Header("Settings")] public float PopUpInterval = 0.2f; #endregion #region 私有字段 protected BattleObject battleObject; protected float timer = 0f; protected List<TipsInfo> messages = new List<TipsInfo>(); protected List<BattleTips> tipsList = new List<BattleTips>(); @@ -62,19 +62,21 @@ protected Tween hpTween; protected Tween xpTween; protected Tween shieldTween1; protected Tween shieldTween2; protected Sequence damageSequence; #endregion private Queue<HpUpdateRequest> hpUpdateQueue = new Queue<HpUpdateRequest>(); private Queue<BattleDmgInfo> damageUpdateQueue = new Queue<BattleDmgInfo>(); #region Unity生命周期 // 飘字GCD相关 private float tipsGCDTimer = 0f; private const int TIPS_GCD_FRAMES = 5; protected void OnDisable() { CleanupTips(); } #endregion #region 公共方法 - 初始化 public void SetBattleObject(BattleObject _battleObject) { @@ -83,16 +85,19 @@ 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); } #endregion #region 公共方法 - Buff管理 public void RefreshBuff(List<HB428_tagSCBuffRefresh> datas) { @@ -111,11 +116,14 @@ 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; } #endregion #region 公共方法 - 飘字管理 /// <summary> /// 添加飘字到队列 @@ -139,24 +147,34 @@ messages.Add(tipsInfo); } #endregion #region 公共方法 - 数值更新 /// <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)fromHp / (float)maxHp; float targetValue = (float)toHp / (float)maxHp; float fromValue = (float)request.fromHp / (float)request.maxHp; float targetValue = (float)request.toHp / (float)request.maxHp; if (tween) if (request.tween) { // 关键修复:先设置起始值,再播放动画到目标值 sliderHp.value = fromValue; // ← 这行是关键! sliderHp.value = fromValue; hpTween = sliderHp.DOValue(targetValue, 0.3f).SetAutoKill(false); battleObject.battleField.battleTweenMgr.OnPlayTween(hpTween); } @@ -187,31 +205,110 @@ 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; } } #endregion /// <summary> /// 播放血条 护盾的变化 /// </summary> public void UpdateDamage(BattleDmgInfo dmgInfo) { // 加入队列 damageUpdateQueue.Enqueue(dmgInfo); } #region 公共方法 - 运行时更新 /// <summary> /// 实际执行伤害更新 /// </summary> 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); } /// <summary> /// 每帧更新 /// </summary> public void Run() { // 处理血条和伤害队列 UpdateHpAndDamageQueue(); // 更新飘字GCD并处理队列 UpdateTipsGCDAndQueue(); // 更新所有飘字 UpdateActiveTips(); // 处理飘字队列 ProcessTipsQueue(); } /// <summary> @@ -225,9 +322,81 @@ } } #endregion /// <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; } } #region 私有方法 - 飘字处理 /// <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> /// 立即弹出飘字 @@ -247,12 +416,11 @@ } // 设置参数并显示 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); @@ -327,6 +495,7 @@ private void RemoveTips(BattleTips tips) { tipsList.Remove(tips); tips.controller = null; GameObject.DestroyImmediate(tips.gameObject); } @@ -337,25 +506,13 @@ { 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; } } @@ -375,14 +532,10 @@ 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) { @@ -406,6 +559,4 @@ { // TODO: 显示buff描述/当前身上所有buff } #endregion } Main/System/Battle/UIComp/BattleTips.cs
@@ -35,7 +35,7 @@ #region 私有字段 // 移除 [SerializeField],controller 不应该被序列化 private BattleFloatingUIController controller; public BattleFloatingUIController controller; #endregion @@ -165,8 +165,6 @@ /// </summary> private void InitController() { if (controller != null) return; if (floatingConfig == null) { Debug.LogError($"[BattleTips] FloatingConfig 未配置! GameObject: {gameObject.name}"); @@ -186,7 +184,7 @@ /// </summary> private void EnsureControllerInitialized() { if (controller == null) if (controller == null || !controller.IsValid()) InitController(); } Main/System/Battle/UIComp/DamageContent.cs
@@ -3,6 +3,7 @@ using UnityEngine; using System; using Cysharp.Threading.Tasks; using DG.Tweening; public class DamageContent : MonoBehaviour, IBattleFloatingUI { @@ -17,10 +18,16 @@ private BattleDmgInfo battleDmgInfo; private BattleFloatingUIController controller; #region Unity Lifecycle void Awake() { line.SetActive(false); } #endregion #region Controller Management private void InitController() { @@ -42,6 +49,19 @@ controller?.SetRatio(speed, scale); } public void SetFloatingConfig(FloatingConfig config) { floatingConfig = config; if (controller != null) { controller.SetConfig(config); } } #endregion #region Position Management /// <summary> /// 设置飘字的起点和终点位置(运行时动态设置) /// </summary> @@ -51,30 +71,95 @@ controller?.SetRuntimePosition(beginPos, endPos); } public async void SetDamage(BattleDmgInfo _battleDmgInfo, List<BattleDmg> damages, Action _onComplete) #endregion #region Damage Display public void SetDamage(BattleDmgInfo _battleDmgInfo, List<BattleDmg> damages, Action _onComplete) { battleDmgInfo = _battleDmgInfo; for (int i = damages.Count; i < damageLineList.Count; i++) EnsureDamageLineCapacity(damages.Count); DisplayDamageLines(damages); HideExcessDamageLines(damages.Count); bool isCrit = battleDmgInfo.IsCrit(); Play(isCrit, _onComplete); } /// <summary> /// 确保有足够的DamageLine对象 /// </summary> private void EnsureDamageLineCapacity(int requiredCount) { RectTransform lineTemplate = line.GetComponent<RectTransform>(); Vector2 templateAnchorMin = lineTemplate.anchorMin; Vector2 templateAnchorMax = lineTemplate.anchorMax; Vector2 templatePivot = lineTemplate.pivot; for (int i = damageLineList.Count; i < requiredCount; i++) { GameObject newLine = GameObject.Instantiate(line, parent); DamageLine damageLine = newLine.GetComponent<DamageLine>(); RectTransform newLineRect = newLine.GetComponent<RectTransform>(); if (newLineRect != null) { newLineRect.anchorMin = templateAnchorMin; newLineRect.anchorMax = templateAnchorMax; newLineRect.pivot = templatePivot; newLineRect.anchoredPosition = Vector2.zero; newLineRect.localScale = Vector3.one; } damageLineList.Add(damageLine); } } /// <summary> /// 显示伤害行并设置位置 /// </summary> private void DisplayDamageLines(List<BattleDmg> damages) { for (int i = 0; i < damages.Count; i++) { DamageLine damageLine = damageLineList[i]; SetDamageLinePosition(damageLine, i); damageLine.SetActive(true); damageLine.SetDamage(damages[i]); } } /// <summary> /// 设置单个伤害行的位置(使用Y轴偏移) /// </summary> private void SetDamageLinePosition(DamageLine damageLine, int index) { RectTransform lineRect = damageLine.GetComponent<RectTransform>(); if (lineRect == null) return; RectTransform lineTemplate = line.GetComponent<RectTransform>(); Vector2 basePos = lineTemplate.anchoredPosition; Vector2 pos = basePos; pos.y += 60f * index; lineRect.anchoredPosition = pos; } /// <summary> /// 隐藏多余的伤害行 /// </summary> private void HideExcessDamageLines(int displayCount) { for (int i = displayCount; i < damageLineList.Count; i++) { damageLineList[i].SetActive(false); } } // 使用控制器的Play方法 bool isCrit = battleDmgInfo.IsCrit(); Play(isCrit, _onComplete); #endregion for (int i = 0; i < damages.Count; i++) { if (i >= damageLineList.Count) { GameObject newLine = GameObject.Instantiate(line, parent); damageLineList.Add(newLine.GetComponent<DamageLine>()); } damageLineList[i].SetActive(true); damageLineList[i].SetDamage(damages[i]); await UniTask.Delay(100); } } #region Animation Control public void Play(bool isCrit, Action onComplete = null) { @@ -100,6 +185,10 @@ controller.Resume(); } #endregion #region Visual Effects private void ApplyColor(Color color) { for (int i = 0; i < damageLineList.Count; i++) @@ -111,13 +200,5 @@ } } // 运行时更新配置 public void SetFloatingConfig(FloatingConfig config) { floatingConfig = config; if (controller != null) { controller.SetConfig(config); } } #endregion }