| | |
| | | PassiveSkillLimitGroup, |
| | | }; |
| | | |
| | | public const int ShieldBuffAttackType = 1003;//护盾吸收伤害类型ID |
| | | } |
| | |
| | | // 这个界面是 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; |
| | |
| | | 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() |
| | |
| | | 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() |
| | |
| | | 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) |
| | |
| | | { |
| | | if (isPause) |
| | | { |
| | | foreach (var content in damageContentList) |
| | | { |
| | | content.Stop(); |
| | | } |
| | | PauseAllDamageContent(); |
| | | } |
| | | else |
| | | { |
| | | foreach (var content in damageContentList) |
| | | { |
| | | content.Resume(); |
| | | } |
| | | 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(); |
| | | } |
| | | } |
| | | |
| | | private void ResumeAllDamageContent() |
| | | { |
| | | foreach (var content in damageContentList) |
| | | { |
| | | content.Resume(); |
| | | } |
| | | } |
| | | |
| | | private void RunAllDamageContent() |
| | | { |
| | | for (int i = damageContentList.Count - 1; i >= 0; i--) |
| | | { |
| | | if (i < damageContentList.Count) |
| | |
| | | isCreate = false; |
| | | } |
| | | else |
| | | { |
| | | { |
| | | BattleDebug.LogError("战场已存在 先进行销毁"); |
| | | battleField.Destroy(); |
| | | } |
| | | } |
| | | |
| | | var bf = GetBattleFieldByMapID(MapID); |
| | | if (bf != null && !string.IsNullOrEmpty(guid)) |
| | | { |
| | | BattleDebug.LogError("相同地图ID的战场已存在 先进行销毁"); |
| | | bf.Destroy(); |
| | | } |
| | | |
| | | if (isCreate) |
| | | { |
| | | battleField = BattleFieldFactory.CreateBattleField(guid, MapID, FuncLineID, extendData, redTeamList, blueTeamList); |
| | |
| | | { |
| | | 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); |
| | |
| | | { |
| | | 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); |
| | |
| | | |
| | | 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); |
| | |
| | | } |
| | | 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); |
| | |
| | | |
| | | 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; |
| | |
| | | return null; |
| | | } |
| | | |
| | | |
| | | float finalScaleRate = modelScaleRate * teamHero.modelScale; |
| | | |
| | | skeletonAnimation.skeletonDataAsset = skeletonDataAsset; |
| | |
| | | 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; |
| | | } |
| | |
| | | |
| | | return false; |
| | | } |
| | | |
| | | |
| | | 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) |
| | | { |
| | | RefreshBuff(vNetData, true); |
| | |
| | | using System.Collections.Generic; |
| | | using UnityEngine; |
| | | |
| | | public class BattleDmg |
| | | { |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | 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))) |
| | | { |
| | |
| | | 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; |
| | | |
| | | int maxCount = CalculateMaxDamageSegmentCount(); |
| | | |
| | | // 处理每一段伤害及其对应的反伤和吸血 |
| | | for (int i = 0; i < damageList.Count; i++) |
| | | for (int i = 0; i < maxCount; i++) |
| | | { |
| | | long actualDamage = damageList[i]; |
| | | |
| | | // ============ 1. 先处理当前段对应的反伤 ============ |
| | | if (battleHurtParam.reflectHpList != null && i < battleHurtParam.reflectHpList.Count) |
| | | { |
| | | long reflectHp = battleHurtParam.reflectHpList[i]; |
| | | if (reflectHp > 0) |
| | | { |
| | | casterDamageList.Add(new BattleDmg |
| | | { |
| | | damage = reflectHp, |
| | | attackType = (int)DamageType.Reflect |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // ============ 2. 然后处理当前段对应的吸血 ============ |
| | | if (battleHurtParam.suckHpList != null && i < battleHurtParam.suckHpList.Count) |
| | | { |
| | | long suckHp = battleHurtParam.suckHpList[i]; |
| | | if (suckHp > 0) |
| | | { |
| | | casterDamageList.Add(new BattleDmg |
| | | { |
| | | damage = suckHp, |
| | | attackType = (int)DamageType.SuckHP |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // ============ 3. 最后处理主要伤害 ============ |
| | | // 格挡处理 |
| | | if (isBlocked) |
| | | { |
| | | // 去掉格挡类型 |
| | | 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 }); |
| | | } |
| | | 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 }); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | int attackType = rawAttackType; |
| | | |
| | | // 真实伤害特殊处理 |
| | | if (IsRealdamage()) |
| | | { |
| | | int showAttackType = (int)DamageType.Realdamage + (IsCrit() ? (int)DamageType.Crit : 0); |
| | | targetDamageList.Add(new BattleDmg { damage = actualDamage, attackType = showAttackType }); |
| | | } |
| | | 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 }); |
| | | } |
| | | } |
| | | 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 |
| | | { |
| | | damage = reflectHp, |
| | | attackType = (int)DamageType.Reflect |
| | | }); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理吸血伤害 |
| | | /// </summary> |
| | | private void ProcessSuckHpDamage(int segmentIndex) |
| | | { |
| | | if (battleHurtParam.suckHpList == null || segmentIndex >= battleHurtParam.suckHpList.Count) |
| | | return; |
| | | |
| | | long suckHp = battleHurtParam.suckHpList[segmentIndex]; |
| | | if (suckHp > 0) |
| | | { |
| | | casterDamageList.Add(new BattleDmg |
| | | { |
| | | damage = suckHp, |
| | | attackType = (int)DamageType.SuckHP |
| | | }); |
| | | } |
| | | } |
| | | |
| | | /// <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()) |
| | | { |
| | | AddRealdamageToList(actualDamage); |
| | | } |
| | | else |
| | | { |
| | | AddNormalDamageToList(actualDamage, attackType); |
| | | } |
| | | } |
| | | |
| | | /// <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 = damage, attackType = showAttackType }); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 添加普通伤害/治疗到列表 |
| | | /// </summary> |
| | | private void AddNormalDamageToList(long damage, int attackType) |
| | | { |
| | | 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) |
| | | return (int)DamageType.Damage; |
| | | |
| | | if ((attackType & (int)DamageType.Recovery) != 0) |
| | | return (int)DamageType.Recovery; |
| | | |
| | | UnityEngine.Debug.LogError($"强制转换失败,该类型不是治疗也不是伤害 {attackType}"); |
| | | return 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() |
| | |
| | | return skillConfig.HurtType / 10 == 1; |
| | | } |
| | | |
| | | public bool IsDamage() |
| | | { |
| | | return IsType(DamageType.Damage) || IsRealdamage() || IsType((DamageType)11) || IsType((DamageType)12); |
| | | } |
| | | |
| | | #endregion |
| | | } |
| | | |
| | | public class BattleHurtParam |
| | |
| | | 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 |
| | | } |
| | |
| | | // 服务器拥有的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 |
| | |
| | | List<BattleObject> targetList = battleField.battleObjMgr.GetBattleObjList(tagUseSkillAttack); |
| | | List<BattleObject> highlightList = new List<BattleObject>(targetList) { caster }; |
| | | List<BattleObject> allList = battleField.battleObjMgr.allBattleObjDict.Values.ToList<BattleObject>(); |
| | | |
| | | |
| | | // 修复:使用HashSet优化性能,避免重复设置 |
| | | var targetSet = new HashSet<BattleObject>(targetList); |
| | | var highlightSet = new HashSet<BattleObject>(highlightList); |
| | | |
| | | |
| | | caster.heroInfoBar.SetActive(false); |
| | | |
| | | foreach (BattleObject bo in allList) |
| | | { |
| | | bool isHighlight = highlightSet.Contains(bo); |
| | | bool isTarget = targetSet.Contains(bo); |
| | | |
| | | |
| | | if (isHighlight) |
| | | { |
| | | bo.layerMgr.SetFront(); |
| | |
| | | // 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) |
| | | { |
| | | BattleObject target = caster.battleField.battleObjMgr.GetBattleObject((int)hurt.ObjID); |
| | | if (target == null) |
| | | { |
| | |
| | | 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) |
| | |
| | | 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; |
| | |
| | | currentHitDamage += dmg; |
| | | } |
| | | |
| | | long currentHitSuckHp = 0; |
| | | foreach (long suck in suckHpList) |
| | | { |
| | | currentHitSuckHp += suck; |
| | | } |
| | | |
| | | |
| | | long currentHitReflectHp = 0; |
| | | foreach (long reflect in reflectHpList) |
| | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | |
| | | // 更新目标血量 |
| | |
| | | |
| | | #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" |
| | |
| | | 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, |
| | |
| | | applyColorCallback?.Invoke(GetEndColor()); |
| | | } |
| | | } |
| | | |
| | | |
| | | /// <summary> |
| | | /// 动画完成回调 |
| | | /// </summary> |
| | |
| | | onFinishCallback = null; |
| | | } |
| | | |
| | | public bool IsValid() |
| | | { |
| | | return ValidateConfig() && rectTransform != null && gameObject != null; |
| | | } |
| | | |
| | | #endregion |
| | | } |
| | |
| | | /// </summary> |
| | | public class BattleHeroInfoBar : MonoBehaviour |
| | | { |
| | | #region 内部类 |
| | | |
| | | /// <summary> |
| | | /// 飘字信息配置 |
| | | /// </summary> |
| | |
| | | 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; |
| | | |
| | |
| | | [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>(); |
| | |
| | | |
| | | protected Tween hpTween; |
| | | protected Tween xpTween; |
| | | protected Tween shieldTween1; |
| | | protected Tween shieldTween2; |
| | | protected Sequence damageSequence; |
| | | |
| | | #endregion |
| | | |
| | | #region Unity生命周期 |
| | | private Queue<HpUpdateRequest> hpUpdateQueue = new Queue<HpUpdateRequest>(); |
| | | private Queue<BattleDmgInfo> damageUpdateQueue = new Queue<BattleDmgInfo>(); |
| | | |
| | | // 飘字GCD相关 |
| | | private float tipsGCDTimer = 0f; |
| | | private const int TIPS_GCD_FRAMES = 5; |
| | | |
| | | protected void OnDisable() |
| | | { |
| | | CleanupTips(); |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 公共方法 - 初始化 |
| | | |
| | | public void SetBattleObject(BattleObject _battleObject) |
| | | { |
| | |
| | | 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) |
| | | { |
| | |
| | | buffCells[i].SetActive(false); |
| | | } |
| | | } |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 公共方法 - 飘字管理 |
| | | // 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; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 添加飘字到队列 |
| | |
| | | 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); |
| | | } |
| | |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 公共方法 - 运行时更新 |
| | | /// <summary> |
| | | /// 播放血条 护盾的变化 |
| | | /// </summary> |
| | | public void UpdateDamage(BattleDmgInfo dmgInfo) |
| | | { |
| | | // 加入队列 |
| | | damageUpdateQueue.Enqueue(dmgInfo); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 实际执行伤害更新 |
| | | /// </summary> |
| | | private void ExecuteDamageUpdate(BattleDmgInfo dmgInfo) |
| | | { |
| | | KillTween(ref damageSequence); |
| | | |
| | | long maxHp = dmgInfo.battleHurtParam.maxHp; |
| | | 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> |
| | |
| | | } |
| | | } |
| | | |
| | | #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; |
| | | } |
| | | } |
| | | |
| | | /// <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(); |
| | | } |
| | | } |
| | | |
| | | #region 私有方法 - 飘字处理 |
| | | /// <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); |
| | | } |
| | |
| | | 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; |
| | | } |
| | | } |
| | | |
| | |
| | | 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 |
| | | } |
| | |
| | | #region 私有字段 |
| | | |
| | | // 移除 [SerializeField],controller 不应该被序列化 |
| | | private BattleFloatingUIController controller; |
| | | public BattleFloatingUIController controller; |
| | | |
| | | #endregion |
| | | |
| | |
| | | /// </summary> |
| | | private void InitController() |
| | | { |
| | | if (controller != null) return; |
| | | |
| | | if (floatingConfig == null) |
| | | { |
| | | Debug.LogError($"[BattleTips] FloatingConfig 未配置! GameObject: {gameObject.name}"); |
| | |
| | | /// </summary> |
| | | private void EnsureControllerInitialized() |
| | | { |
| | | if (controller == null) |
| | | if (controller == null || !controller.IsValid()) |
| | | InitController(); |
| | | } |
| | | |
| | |
| | | using UnityEngine; |
| | | using System; |
| | | using Cysharp.Threading.Tasks; |
| | | using DG.Tweening; |
| | | |
| | | public class DamageContent : MonoBehaviour, IBattleFloatingUI |
| | | { |
| | |
| | | private BattleDmgInfo battleDmgInfo; |
| | | private BattleFloatingUIController controller; |
| | | |
| | | #region Unity Lifecycle |
| | | |
| | | void Awake() |
| | | { |
| | | line.SetActive(false); |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region Controller Management |
| | | |
| | | private void InitController() |
| | | { |
| | |
| | | controller?.SetRatio(speed, scale); |
| | | } |
| | | |
| | | public void SetFloatingConfig(FloatingConfig config) |
| | | { |
| | | floatingConfig = config; |
| | | if (controller != null) |
| | | { |
| | | controller.SetConfig(config); |
| | | } |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region Position Management |
| | | |
| | | /// <summary> |
| | | /// 设置飘字的起点和终点位置(运行时动态设置) |
| | | /// </summary> |
| | |
| | | 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); |
| | | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region Animation Control |
| | | |
| | | public void Play(bool isCrit, Action onComplete = null) |
| | | { |
| | |
| | | controller.Resume(); |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region Visual Effects |
| | | |
| | | private void ApplyColor(Color color) |
| | | { |
| | | for (int i = 0; i < damageLineList.Count; i++) |
| | |
| | | } |
| | | } |
| | | |
| | | // 运行时更新配置 |
| | | public void SetFloatingConfig(FloatingConfig config) |
| | | { |
| | | floatingConfig = config; |
| | | if (controller != null) |
| | | { |
| | | controller.SetConfig(config); |
| | | } |
| | | } |
| | | #endregion |
| | | } |