| | |
| | | |
| | | public const string BATTLE_END = "BATTLE_END";//战斗结束 |
| | | public const string RECORDPLAYER_END = "RECORDPLAYER_END"; //战斗小片段结束 |
| | | |
| | | public const string BATTLE_TIANZI_REFRESH_HP = "BATTLE_TIANZI_REFRESH_HP";//天子考验 刷新血量 |
| | | } |
| New file |
| | |
| | | using UnityEngine; |
| | | using System.Collections; |
| | | |
| | | // B4 19 对象最新生命刷新 #tagSCObjHPRefresh |
| | | |
| | | public class DTCB419_tagSCObjHPRefresh : DtcBasic { |
| | | public override void Done(GameNetPackBasic vNetPack) { |
| | | base.Done(vNetPack); |
| | | HB419_tagSCObjHPRefresh vNetData = vNetPack as HB419_tagSCObjHPRefresh; |
| | | |
| | | BattleField battleField = BattleManager.Instance.GetBattleField(vNetData.packUID); |
| | | if (null != battleField) |
| | | { |
| | | battleField.OnRefreshObjHP(vNetData); |
| | | battleField.DistributeNextPackage(); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 3d2361817f6a81a4ea0d05eaf7b8f59e |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | using UnityEngine; |
| | | using System.Collections; |
| | | |
| | | // B4 19 对象最新生命刷新 #tagSCObjHPRefresh |
| | | |
| | | public class HB419_tagSCObjHPRefresh : GameNetPackBasic { |
| | | public uint ObjID; |
| | | public uint HP; // 当前血量,求余20亿部分 |
| | | public uint HPEx; // 当前血量,整除20亿部分 |
| | | public uint MaxHP; // 最大血量,求余20亿部分 |
| | | public uint MaxHPEx; // 最大血量,整除20亿部分 |
| | | |
| | | public HB419_tagSCObjHPRefresh () { |
| | | _cmd = (ushort)0xB419; |
| | | } |
| | | |
| | | public override void ReadFromBytes (byte[] vBytes) { |
| | | TransBytes (out ObjID, vBytes, NetDataType.DWORD); |
| | | TransBytes (out HP, vBytes, NetDataType.DWORD); |
| | | TransBytes (out HPEx, vBytes, NetDataType.DWORD); |
| | | TransBytes (out MaxHP, vBytes, NetDataType.DWORD); |
| | | TransBytes (out MaxHPEx, vBytes, NetDataType.DWORD); |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 4d172d7090e7f3b42958b6ce5bf20dc3 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | { |
| | | return null; |
| | | } |
| | | |
| | | public void OnRefreshObjHP(HB419_tagSCObjHPRefresh vNetData) |
| | | { |
| | | BattleObject battleObj = battleObjMgr.GetBattleObject((int)vNetData.ObjID); |
| | | if (null != battleObj) |
| | | { |
| | | battleObj.teamHero.curHp = GeneralDefine.GetFactValue(vNetData.HP, vNetData.HPEx); |
| | | battleObj.teamHero.maxHp = GeneralDefine.GetFactValue(vNetData.MaxHP, vNetData.MaxHPEx); |
| | | EventBroadcast.Instance.Broadcast<string, BattleObject>( |
| | | EventName.BATTLE_TIANZI_REFRESH_HP, guid, battleObj); |
| | | |
| | | //battleObj.teamHero.curHp, battleObj.teamHero.maxHp |
| | | } |
| | | |
| | | |
| | | } |
| | | } |
| | |
| | | |
| | | public virtual void Hurt(List<long> damageValues, long _totalDamage, |
| | | HB427_tagSCUseSkill.tagSCUseSkillHurt hurt, SkillConfig skillConfig, int hitIndex, |
| | | BattleDrops battleDrops, HB422_tagMCTurnFightObjDead deadPack) |
| | | BattleDrops battleDrops, HB422_tagMCTurnFightObjDead deadPack, |
| | | long fromHp, long toHp) |
| | | { |
| | | bool isLastHit = hitIndex >= skillConfig.DamageDivide.Length - 1; |
| | | bool firstHit = hitIndex == 0; |
| | | BattleDmgInfo dmgInfo = PopDamage(damageValues, _totalDamage, hurt, skillConfig, isLastHit); |
| | | BattleDmgInfo dmgInfo = PopDamage(damageValues, _totalDamage, hurt, skillConfig, isLastHit, fromHp, toHp); |
| | | |
| | | |
| | | // 这里 |
| | |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | | public void SuckHp(uint suckHP, SkillConfig skillConfig) |
| | | { |
| | | teamHero.curHp = Math.Min(teamHero.maxHp, teamHero.curHp + (int)suckHP); |
| | | // teamHero.curHp = Math.Min(teamHero.maxHp, teamHero.curHp + (int)suckHP); |
| | | } |
| | | |
| | | public void HurtByReflect(uint bounceHP, SkillConfig skillConfig) |
| | | { |
| | | teamHero.curHp = Math.Max(0, teamHero.curHp - (int)bounceHP); |
| | | // teamHero.curHp = Math.Max(0, teamHero.curHp - (int)bounceHP); |
| | | } |
| | | |
| | | |
| | |
| | | } |
| | | |
| | | // 伤害还要看 是否闪避 暴击 and so on 需要有一个DamageType 服务器应该会给 |
| | | protected virtual BattleDmgInfo PopDamage(List<long> damageValues, long _totalDamage, HB427_tagSCUseSkill.tagSCUseSkillHurt hurt, SkillConfig skillConfig, bool isLastHit) |
| | | protected virtual BattleDmgInfo PopDamage(List<long> damageValues, long _totalDamage, |
| | | HB427_tagSCUseSkill.tagSCUseSkillHurt hurt, SkillConfig skillConfig, bool isLastHit, |
| | | long fromHp, long toHp) |
| | | { |
| | | BattleDmgInfo battleDmgInfo = new BattleDmgInfo(battleField.guid, damageValues, this, hurt, skillConfig, isLastHit); |
| | | |
| | | int currentHurtHp = 0; |
| | | for (int i = 0; i < damageValues.Count; i++) |
| | | { |
| | | currentHurtHp += (int)damageValues[i]; |
| | | } |
| | | |
| | | bool isRecovery = battleDmgInfo.IsType(DamageType.Recovery); |
| | | |
| | | long toHp = Math.Max(0, teamHero.curHp + (isRecovery ? currentHurtHp : -currentHurtHp)); |
| | | |
| | | heroInfoBar.UpdateHP(teamHero.curHp, toHp, teamHero.maxHp); |
| | | teamHero.curHp = toHp; |
| | | // 使用传入的 fromHp 和 toHp 更新血条显示 |
| | | heroInfoBar.UpdateHP(fromHp, toHp, teamHero.maxHp); |
| | | |
| | | // YYL TODO 是否需要挂在在自身的follow点上 |
| | | EventBroadcast.Instance.Broadcast(EventName.BATTLE_DAMAGE_TAKEN, battleDmgInfo); |
| | |
| | | /// </summary> |
| | | public static List<long> DivideDamageToList(int[] damageDivide, long totalDamage) |
| | | { |
| | | if (damageDivide == null || damageDivide.Length == 0) |
| | | { |
| | | Debug.LogError("damageDivide 为空或长度为0"); |
| | | return new List<long> { totalDamage }; |
| | | } |
| | | |
| | | List<long> fixedDamageList = new List<long>(); |
| | | long accumulatedDamage = 0; // 累计已分配的伤害 |
| | | |
| | | for (int i = 0; i < damageDivide.Length; i++) |
| | | { |
| | | float fixedDamage = (float)totalDamage * (float)damageDivide[i] / 10000f; |
| | | fixedDamageList.Add((int)fixedDamage); |
| | | long damage; |
| | | |
| | | // 最后一次分配:用总伤害减去已分配的伤害,确保总和精确 |
| | | if (i == damageDivide.Length - 1) |
| | | { |
| | | damage = totalDamage - accumulatedDamage; |
| | | } |
| | | else |
| | | { |
| | | // 计算当前分段伤害(向下取整) |
| | | damage = (long)((float)totalDamage * (float)damageDivide[i] / 10000f); |
| | | accumulatedDamage += damage; |
| | | } |
| | | |
| | | fixedDamageList.Add(damage); |
| | | } |
| | | |
| | | |
| | | return fixedDamageList; |
| | | } |
| | | |
| | | public static HB419_tagSCObjHPRefresh FindObjHPRefreshPack(List<GameNetPackBasic> packList) |
| | | { |
| | | for (int i = 0; i < packList.Count; i++) |
| | | { |
| | | var pack = packList[i]; |
| | | if (pack is HB419_tagSCObjHPRefresh hpRefreshPack) |
| | | { |
| | | return hpRefreshPack; |
| | | } |
| | | else if (pack is CustomHB426CombinePack) |
| | | { |
| | | break; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public static List<HB422_tagMCTurnFightObjDead> FindDeadPack(List<GameNetPackBasic> packList) |
| | | { |
| | |
| | | protected Spine.Skeleton skeleton; |
| | | protected float defaultMixDuration = 0f; |
| | | private Spine.TrackEntry currentTrack; |
| | | private Dictionary<int, Spine.TrackEntry> activeSkillTracks = new Dictionary<int, Spine.TrackEntry>(); // 改为字典:trackIndex -> TrackEntry |
| | | private Dictionary<int, Spine.TrackEntry> activeSkillTracks = new Dictionary<int, Spine.TrackEntry>(); |
| | | |
| | | // 子技能轨道池管理(在Init中初始化,不要在这里初始化) |
| | | private Queue<int> availableSubTracks; |
| | | private Dictionary<SkillBase, int> subSkillTrackMap = new Dictionary<SkillBase, int>(); |
| | | |
| | | private SkeletonIllusionShadow illusionShadow; |
| | | private bool playingSkillAnim = false; |
| | | |
| | |
| | | if (animState != null) |
| | | animState.Data.DefaultMix = defaultMixDuration; |
| | | |
| | | // 初始化子技能轨道池 |
| | | availableSubTracks = new Queue<int>(); |
| | | for (int i = 1; i <= 8; i++) |
| | | availableSubTracks.Enqueue(i); |
| | | |
| | | PlayAnimation(MotionName.idle, true); |
| | | SetupAnimationHandlers(); |
| | | |
| | |
| | | { |
| | | trackEntryCallbacks.Clear(); |
| | | activeSkillTracks.Clear(); |
| | | availableSubTracks?.Clear(); |
| | | subSkillTrackMap.Clear(); |
| | | if (animState != null) |
| | | { |
| | | animState.Complete -= OnAnimationComplete; |
| | |
| | | return null; |
| | | } |
| | | |
| | | // 子技能强制使用无动画模式,或者如果没有动画名称 |
| | | if (isSubSkill || string.IsNullOrEmpty(skillConfig.SkillMotionName)) |
| | | // 如果没有动画名称,使用无动画模式 |
| | | if (string.IsNullOrEmpty(skillConfig.SkillMotionName)) |
| | | { |
| | | PlaySkillNoAnim(skillConfig, skillBase, onComplete, isSubSkill); |
| | | return null; |
| | |
| | | int frameCount = activeFrames.Length; |
| | | float recoveryFrame = skillConfig.RecoveryFrames; |
| | | |
| | | // 主技能用 track 0,子技能用 track 1 |
| | | int trackIndex = isSubSkill ? 1 : 0; |
| | | // 轨道分配策略:主技能用 track 0,子技能从轨道池分配 |
| | | int trackIndex = 0; |
| | | if (isSubSkill) |
| | | { |
| | | if (availableSubTracks != null && availableSubTracks.Count > 0) |
| | | { |
| | | trackIndex = availableSubTracks.Dequeue(); |
| | | subSkillTrackMap[skillBase] = trackIndex; |
| | | } |
| | | else |
| | | { |
| | | // 轨道池耗尽或未初始化,回退到无动画模式 |
| | | Debug.LogWarning($"子技能轨道池已满或未初始化,技能{skillConfig.SkillID}使用无动画模式"); |
| | | PlaySkillNoAnim(skillConfig, skillBase, onComplete, isSubSkill); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | Spine.TrackEntry skillTrack = null; |
| | | |
| | | if (hasAnim) |
| | | { |
| | | skillTrack = animState.SetAnimation(trackIndex, targetAnim, false); |
| | | if (null == skillTrack) |
| | | { |
| | | Debug.LogError($"技能 {skillConfig.SkillID} 动画设置失败"); |
| | | // 如果是子技能且分配了轨道,需要回收 |
| | | if (isSubSkill && subSkillTrackMap.ContainsKey(skillBase)) |
| | | { |
| | | if (availableSubTracks != null) |
| | | availableSubTracks.Enqueue(subSkillTrackMap[skillBase]); |
| | | subSkillTrackMap.Remove(skillBase); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | // 只有主技能才更新 currentTrack |
| | | if (!isSubSkill) |
| | |
| | | { |
| | | if (activeSkillTracks[trackIndex] == skillTrack) |
| | | activeSkillTracks.Remove(trackIndex); |
| | | } |
| | | |
| | | // 回收子技能轨道 |
| | | if (isSubSkill && subSkillTrackMap.ContainsKey(skillBase)) |
| | | { |
| | | if (availableSubTracks != null) |
| | | availableSubTracks.Enqueue(subSkillTrackMap[skillBase]); |
| | | subSkillTrackMap.Remove(skillBase); |
| | | } |
| | | |
| | | // 只有当没有其他活跃技能时才复位 playingSkillAnim |
| | |
| | | |
| | | // 清理并确保状态复位 |
| | | RemoveAction(frameHandler); |
| | | |
| | | // 回收子技能轨道 |
| | | if (isSubSkill && subSkillTrackMap.ContainsKey(skillBase)) |
| | | { |
| | | if (availableSubTracks != null) |
| | | availableSubTracks.Enqueue(subSkillTrackMap[skillBase]); |
| | | subSkillTrackMap.Remove(skillBase); |
| | | } |
| | | |
| | | if (activeSkillTracks.Count == 0) |
| | | playingSkillAnim = false; |
| | | return; |
| | |
| | | RemoveAction(frameHandler); |
| | | if (activeSkillTracks.ContainsKey(trackIndex)) |
| | | activeSkillTracks.Remove(trackIndex); |
| | | |
| | | // 回收子技能轨道 |
| | | if (isSubSkill && subSkillTrackMap.ContainsKey(skillBase)) |
| | | { |
| | | if (availableSubTracks != null) |
| | | availableSubTracks.Enqueue(subSkillTrackMap[skillBase]); |
| | | subSkillTrackMap.Remove(skillBase); |
| | | } |
| | | |
| | | if (activeSkillTracks.Count == 0) |
| | | playingSkillAnim = false; |
| | | return; |
| | |
| | | { |
| | | if (activeSkillTracks[trackIndex] == skillTrack) |
| | | activeSkillTracks.Remove(trackIndex); |
| | | } |
| | | |
| | | // 回收子技能轨道 |
| | | if (isSubSkill && subSkillTrackMap.ContainsKey(skillBase)) |
| | | { |
| | | if (availableSubTracks != null) |
| | | availableSubTracks.Enqueue(subSkillTrackMap[skillBase]); |
| | | subSkillTrackMap.Remove(skillBase); |
| | | } |
| | | |
| | | // 只有当没有其他活跃技能时才复位 playingSkillAnim |
| | |
| | | trackEntryCallbacks.Clear(); |
| | | runningActions.Clear(); |
| | | activeSkillTracks.Clear(); |
| | | |
| | | // 重置子技能轨道池 |
| | | if (availableSubTracks == null) |
| | | availableSubTracks = new Queue<int>(); |
| | | else |
| | | availableSubTracks.Clear(); |
| | | |
| | | subSkillTrackMap.Clear(); |
| | | for (int i = 1; i <= 8; i++) |
| | | availableSubTracks.Enqueue(i); |
| | | |
| | | playingSkillAnim = false; |
| | | PlayAnimation(MotionName.idle, true); |
| | | } |
| | |
| | | public void OnSkillStart() |
| | | { |
| | | HandleDead(); |
| | | |
| | | skillEffect = SkillEffectFactory.CreateSkillEffect(caster, skillConfig, tagUseSkillAttack); |
| | | skillEffect.Play(OnHitTargets); |
| | | foreach (var subSkillPack in tagUseSkillAttack.subSkillList) |
| | |
| | | // 技能后摇结束回调:通知技能效果处理后摇结束 |
| | | public virtual void OnFinalFrameEnd() |
| | | { |
| | | |
| | | skillEffect?.OnFinalFrameEnd(); // 修复:添加空值检查 |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | // 处理单个目标被命中:应用伤害和施法者效果 |
| | | // 处理单个目标被命中:应用伤害和施法者效果 |
| | | protected virtual void OnHitEachTarget(int _hitIndex, BattleObject target, HB427_tagSCUseSkill.tagSCUseSkillHurt hurt) |
| | | { |
| | | // ============ 第一步:计算伤害分布 ============ |
| | | List<int> damageDivide = new List<int>(); |
| | | if (_hitIndex == 0 && skillConfig.DamageDivide.Length <= 0) |
| | | { |
| | |
| | | } |
| | | } |
| | | |
| | | // 伤害分布计算和应用 |
| | | // 计算总伤害和分段伤害列表 |
| | | long totalDamage = GeneralDefine.GetFactValue(hurt.HurtHP, hurt.HurtHPEx); |
| | | List<long> damageList = BattleUtility.DivideDamageToList(damageDivide.ToArray(), totalDamage); |
| | | |
| | | // 获取临时数据并应用伤害 |
| | | // ============ 第二步:刷新实际血量 ============ |
| | | long fromHp = target.teamHero.curHp; |
| | | |
| | | // 计算当前这一击的实际伤害(所有分段伤害之和) |
| | | long currentHitDamage = 0; |
| | | foreach (long dmg in damageList) |
| | | { |
| | | currentHitDamage += dmg; |
| | | } |
| | | |
| | | long toHp = Math.Max(0, fromHp - currentHitDamage); |
| | | |
| | | // 更新目标血量 |
| | | target.teamHero.curHp = toHp; |
| | | |
| | | #if UNITY_EDITOR |
| | | BattleDebug.LogError( |
| | | (caster.Camp == BattleCamp.Red ? "【红方行动】" : "【蓝方行动】") + "\n" + |
| | | $"攻击者: {caster.teamHero.name}\n" + |
| | | $"目标: {target.teamHero.name}\n" + |
| | | $"技能: {skillConfig.SkillName} (第{_hitIndex}击)\n" + |
| | | $"伤害: {currentHitDamage} (总伤害: {totalDamage})\n" + |
| | | $"血量变化: {fromHp} -> {toHp}" |
| | | ); |
| | | #endif |
| | | |
| | | // 只在最后一击时同步HP刷新包 |
| | | bool isLastHit = _hitIndex >= skillConfig.DamageDivide.Length - 1; |
| | | if (isLastHit) |
| | | { |
| | | HandleRefreshHP(hurt); |
| | | } |
| | | |
| | | // ============ 第三步:获取临时数据(掉落、死亡等) ============ |
| | | int objID = (int)target.ObjID; |
| | | tempDropList.TryGetValue(objID, out BattleDrops battleDrops); |
| | | tempDeadPackList.TryGetValue(objID, out HB422_tagMCTurnFightObjDead deadPack); |
| | | target.Hurt(damageList, totalDamage, hurt, skillConfig, _hitIndex, battleDrops, deadPack); |
| | | |
| | | // 处理施法者相关效果 |
| | | // ============ 第四步:执行表现(飘字、动画等) ============ |
| | | target.Hurt(damageList, totalDamage, hurt, skillConfig, _hitIndex, battleDrops, deadPack, fromHp, toHp); |
| | | |
| | | // ============ 第五步:处理施法者相关效果 ============ |
| | | caster.SuckHp(hurt.SuckHP, skillConfig); |
| | | caster.HurtByReflect(hurt.BounceHP, skillConfig); |
| | | } |
| | | |
| | | // 处理HP刷新包(简化逻辑) |
| | | private void HandleRefreshHP(HB427_tagSCUseSkill.tagSCUseSkillHurt hurt) |
| | | { |
| | | // 查找HP刷新包 |
| | | HB419_tagSCObjHPRefresh refreshPack = BattleUtility.FindObjHPRefreshPack(packList); |
| | | |
| | | if (refreshPack != null) |
| | | { |
| | | // 分发HP刷新包 |
| | | PackageRegedit.Distribute(refreshPack); |
| | | packList.Remove(refreshPack); |
| | | } |
| | | } |
| | | |
| | | // 处理死亡相关逻辑:分配掉落和经验 |
| | | protected void HandleDead() |
| | | { |
| | |
| | | { |
| | | KillTween(ref hpTween); |
| | | |
| | | float fromValue = (float)fromHp / (float)maxHp; |
| | | float targetValue = (float)toHp / (float)maxHp; |
| | | |
| | | if (tween) |
| | | { |
| | | hpTween = sliderHp.DOValue(targetValue, 0.3f); |
| | | // 关键修复:先设置起始值,再播放动画到目标值 |
| | | sliderHp.value = fromValue; // ← 这行是关键! |
| | | hpTween = sliderHp.DOValue(targetValue, 0.3f).SetAutoKill(false); |
| | | battleObject.battleField.battleTweenMgr.OnPlayTween(hpTween); |
| | | } |
| | | else |
| | |
| | | { |
| | | KillTween(ref xpTween); |
| | | |
| | | float fromValue = (float)fromXp / (float)maxXp; |
| | | float targetValue = (float)toXp / (float)maxXp; |
| | | |
| | | if (tween) |
| | | { |
| | | xpTween = sliderXp.DOValue(targetValue, 0.2f); |
| | | // 同样的修复 |
| | | sliderXp.value = fromValue; // ← 这行是关键! |
| | | xpTween = sliderXp.DOValue(targetValue, 0.2f).SetAutoKill(false); |
| | | battleObject.battleField.battleTweenMgr.OnPlayTween(xpTween); |
| | | } |
| | | else |