using Entitas; using System.Collections.Generic; using UnityEngine; using Game; public class SettleSystem : IExecuteSystem, IInitializeSystem { private IGroup _entities; private readonly List _tempHitInfo = new List(); public void Initialize() { _entities = Util.GetGroup(GameMatcher.Skill); } public void Execute() { foreach (var entity in _entities) { SettleDebugDraw(entity); SettleSkill(entity); } } private static void SettleDebugDraw(GameEntity entity) { if (!entity.hasHp) return; Util.DrawShape(entity.hp.HitBoxShape, entity.Pos(), Vector3.right, new Color(0, 1, 0, 0.4f)); } private static void SettleSkill(GameEntity entity) { if (Util.IsPause(entity)) return; while (entity.skill.HitInfo.Count > 0) if (SettleHit(entity.skill.HitInfo.Dequeue())) break; } private static bool SettleHit(SkillHitInfo hitInfo) { var entity = Util.GetEntity(hitInfo.OwnerEntity); var target = Util.GetEntity(hitInfo.HitEntity); if (entity == null || target == null) { return false; } var damageResult = SettleDamage(hitInfo, entity, target); //伤害结算 var hitLevel = SettleHitLevel(hitInfo, damageResult, entity, target); //命中效果等级 var flowSpeed = SettleFlowSpeed(hitInfo, hitLevel, entity, target); //强制位移(击退、击飞、击落等) var pauseTime = SettlePause(hitInfo, hitLevel, entity, target); //卡帧 SettleSelfMoveForceScale(hitLevel, entity, target); //攻击者移动速度调整 SettleBreak(flowSpeed, hitLevel, damageResult, entity, target); //打断技能/动画 SettleBuff(hitLevel, entity, target); //添加硬直/击晕buff SettleTrigger(hitInfo, entity, target); //命中触发器 //纯表现 SettleHitText(hitLevel, damageResult, entity, target); //跳字 SettleHitEffect(flowSpeed, hitLevel, hitInfo.SkillParam.hitType, entity, target); //命中特效 SettleFlash(hitLevel, entity, target); //闪白 SettleShake(hitLevel, entity, target); //抖动 SettleScale(flowSpeed, entity, target); //抖动 SettleGlobalEffect(target, pauseTime); //全局特效 //投技中断 if (!(hitInfo.SkillParam.throwTimeline is null)) { entity.skill.ThrowTarget = target.ID(); TimelineManager.Instance.RunSkillTimeline(entity, hitInfo.SkillParam.throwTimeline.name); return true; } return !hitInfo.Continue; } public struct SettleDamageResult { public float Damage; //伤害 public bool HasShield; //是否结算前有护盾 public bool IsSkill; //是否技能造成伤害 public bool IsDead; //当次伤害造成死亡 public bool IsShieldBreak; //当次伤害破盾 public bool IsStun; //当次伤害眩晕 public bool IsFlowing; //是否浮空 } private static SettleDamageResult SettleDamage(SkillHitInfo hitInfo, GameEntity entity, GameEntity target) { var param = hitInfo.SkillParam; var targetHp = target.hp; var ret = new SettleDamageResult(); ret.IsSkill = !string.IsNullOrEmpty(hitInfo.SkillId); //伤害计算 { var basicAttack = Util.GetProperty(entity, EProperty.BasicAttack); ret.Damage = basicAttack * param.damageRate * hitInfo.AttackRate; ret.HasShield = targetHp.Shield.Value > 0; if (ret.HasShield) { ret.Damage = Mathf.Min(ret.Damage, targetHp.Shield.Value); targetHp.Shield.Value -= ret.Damage; ret.IsShieldBreak = targetHp.Shield.Value == 0; } else { ret.Damage = Mathf.Min(ret.Damage, targetHp.Hp.Value); targetHp.Hp.Value -= ret.Damage; } targetHp.IsDamaged.Value = true; //刷新护盾恢复cd target.hp.ShieldRecoverTime = target.hp.ShieldRecoverTimeMax; //刷新眩晕恢复cd target.hp.StunRecoverTime = target.hp.StunRecoverTimeMax; } //异常值积累 { if (targetHp.StunMax.Value > targetHp.Stun.Value) { //眩晕 var basicStun = Util.GetProperty(entity, EProperty.BasicStun); var damageStun = basicStun * param.stunRate * hitInfo.StunkRate; damageStun = Mathf.Min(damageStun, targetHp.StunMax.Value - targetHp.Stun.Value); targetHp.Stun.Value += damageStun; if (targetHp.Stun.Value >= targetHp.StunMax.Value) { ret.IsStun = true; targetHp.Stun.Value = 0; Util.AddStunBuff(target.ID()); } } } //浮空判定 目标不在地面 或者可以从地面浮空 if (!target.move.IsGround || param.isFlow) { ret.IsFlowing = true; } //实体死亡 if (targetHp.IsAlive && targetHp.Hp.Value == 0) { ret.IsDead = true; Util.EntityDie(target); } return ret; } private static EHitLevel SettleHitLevel(SkillHitInfo hitInfo, SettleDamageResult dmgResult, GameEntity entity, GameEntity target) { if (!dmgResult.IsSkill) { return EHitLevel.None; } if (dmgResult.IsDead) { return EHitLevel.Kill; } if (dmgResult.IsShieldBreak) { return EHitLevel.ShieldBreak; } if (dmgResult.IsStun) { return EHitLevel.Stun; } var staggerDef = Util.GetProperty(target, EProperty.StaggerDefLevel); if (dmgResult.HasShield) { staggerDef += Util.GetProperty(target, EProperty.ShieldStaggerDefLevel); } var sub = hitInfo.SkillParam.staggerLevel - staggerDef; sub = Mathf.Clamp(sub, 0, 3); var ret = EHitLevel.None; switch (sub) { case 0: ret = EHitLevel.Block; break; case 1: ret = EHitLevel.Stagger1; break; case 2: ret = EHitLevel.Stagger2; break; case 3: ret = EHitLevel.Stagger3; break; } if (ret > EHitLevel.Block) { if (dmgResult.IsFlowing) { return EHitLevel.Flowing; } if (target.hp.LastDamageLevel == EHitLevel.Stagger3) { //后仰升级为浮空 return EHitLevel.Flowing; } } return ret; } private static Vector3 SettleFlowSpeed(SkillHitInfo hitInfo, EHitLevel hitLevel, GameEntity entity, GameEntity target) { var param = hitInfo.SkillParam; //浮空数值 var flowSpeed = new Vector3(param.flowSpeed.x, param.flowSpeed.y, param.flowSpeed.z * GameRandom.RollSymbol(0.5f)); var rot = Quaternion.FromToRotation(Vector3.right, hitInfo.HitDir); flowSpeed = rot * flowSpeed; if (hitInfo.IsBreak) { //近距离推开 //根据实体xz距离调整xz浮空速度,距离为0时系数为1.5,距离大于0.5时系数为1 var flowSpeedXZ = new Vector3(flowSpeed.x, 0, flowSpeed.z); var dist = Util.EntityDistanceXZ(entity, target); flowSpeedXZ *= Mathf.Clamp(1.5f - dist, 1, 2); flowSpeed = new Vector3(flowSpeedXZ.x, flowSpeed.y, flowSpeedXZ.z); } if (hitLevel >= EHitLevel.Flowing) { target.move.IsFlowing = true; } else { flowSpeed = new Vector3(flowSpeed.x, 0, flowSpeed.z); } //根据命中等级调整系数 switch (hitLevel) { case EHitLevel.Block: flowSpeed *= 0.2f; break; case EHitLevel.Stagger1: flowSpeed *= 0.7f; break; case EHitLevel.Stagger2: flowSpeed *= 1f; break; case EHitLevel.Stagger3: flowSpeed *= 1.2f; break; } //记录受击方向 添加强制移动buff if (flowSpeed != Vector3.zero) { target.hp.LastDamageDir = flowSpeed; if (flowSpeed.y < 0) { //击落 Util.AddHitDownBuff(target.ID()); } //击退 Util.AddSetSpeedBuff(target.ID(), param.flowTime); } target.hp.LastDamageLevel = hitLevel; //命中瞬间、在pause之前生效部分位移 var move = target.move; var posAdd = flowSpeed * Time.deltaTime * 2; var targetPos = move.Position + posAdd; var startPos = move.Position - posAdd.normalized * 0.001f; RaycastHit raycastHit; if (Physics.Raycast(startPos, posAdd, out raycastHit, posAdd.magnitude, UtilPhysics.LayerMaskStatic)) { targetPos = raycastHit.point; } Util.SetEntityPos(target, targetPos); return flowSpeed; } private static float SettlePause(SkillHitInfo hitInfo, EHitLevel hitLevel, GameEntity entity, GameEntity target) { if (target.IsMaster()) { //主角受击不卡帧 return 0f; } var param = hitInfo.SkillParam; var pauseTime = 1f * param.pauseTime / GameConst.FPS; if (hitLevel >= EHitLevel.ShieldBreak) { pauseTime = Mathf.Max(pauseTime, 0.05f); //受击方向 var dir = target.hp.LastDamageDir; //受击方向与实体方向夹角 var angle = Vector3.Angle(dir, target.move.Transform.forward); //夹角越大,卡帧越长 pauseTime *= Mathf.Clamp(1.5f - angle / 180, 0.5f, 1); } //根据命中等级调整系数 switch (hitLevel) { case EHitLevel.ShieldBreak: pauseTime *= 1.5f; break; case EHitLevel.Block: pauseTime *= 1.2f; break; case EHitLevel.Kill: pauseTime *= 1.5f; break; } //单次攻击命中多目标:每2帧结算一次 var isLastHit = hitInfo.HitIndex == hitInfo.HitCount - 1; if (isLastHit) Util.EntityPause(entity, pauseTime, false); else Util.EntityPause(entity, 1f / GameConst.FPS, false); Util.EntityPause(target, pauseTime); return pauseTime; } private static void SettleSelfMoveForceScale(EHitLevel hitLevel, GameEntity entity, GameEntity target) { switch (hitLevel) { case EHitLevel.Stagger1: case EHitLevel.Stagger2: case EHitLevel.Stagger3: Util.EntitySetTempMoveForceScale(entity.ID(), -0.8f); break; case EHitLevel.Block: Util.EntitySetTempMoveForceScale(entity.ID(), -1.2f); break; } } private static void SettleBreak(Vector3 flowSpeed, EHitLevel hitLevel, SettleDamageResult dmgResult, GameEntity entity, GameEntity target) { //打断技能 if (hitLevel > EHitLevel.Block) { Util.EndSkillTimeline(target); } //强制转向 if (hitLevel > EHitLevel.Stagger2) { Util.EntityTurn(target, flowSpeed.x < 0); } //受击动画 if (dmgResult.IsFlowing) { if (hitLevel > EHitLevel.Block) { target.animation.AirHitMsg = true; } } else { if (Util.HasStunBuff(target.ID())) { if (hitLevel > EHitLevel.Stun) target.animation.AirHitMsg = true; else if (hitLevel > EHitLevel.Block) target.animation.StunHitMsg = true; } else { switch (hitLevel) { case EHitLevel.Stagger1: target.animation.HitMsg = true; break; case EHitLevel.Stagger2: target.animation.MHitMsg = true; break; case EHitLevel.Stagger3: target.animation.LHitMsg = true; break; case EHitLevel.ShieldBreak: case EHitLevel.Stun: target.animation.StunHitMsg = true; break; case EHitLevel.Flowing: case EHitLevel.Kill: target.animation.AirHitMsg = true; break; } } } } private static void SettleBuff(EHitLevel hitLevel, GameEntity entity, GameEntity target) { switch (hitLevel) { case EHitLevel.Stagger1: Util.AddStaggerBuff(target.ID(), 0.2f); break; case EHitLevel.Stagger2: Util.AddStaggerBuff(target.ID(), 0.5f); break; case EHitLevel.Stagger3: Util.AddStaggerBuff(target.ID(), 1f); break; case EHitLevel.Flowing: Util.AddStaggerBuff(target.ID(), -1f); break; case EHitLevel.ShieldBreak: case EHitLevel.Stun: Util.AddStunBuff(target.ID()); break; } } private static void SettleTrigger(SkillHitInfo hitInfo, GameEntity entity, GameEntity target) { //触发buffTrigger UtilBuff.BuffEffectAll(target.ID(), EBuffEffectTimingType.BeAttack); //触发SkillTrigger UtilSkillTrigger.Trigger(entity.ID(), target.ID(), hitInfo.SkillId, ESkillTriggerTimingType.Hit); } private static void SettleHitText(EHitLevel hitLevel, SettleDamageResult dmgResult, GameEntity entity, GameEntity target) { //受击事件广播 var hitMsg = new PEntityHit() { Entity = entity.ID(), Target = target.ID(), Dmg = (int)dmgResult.Damage, IsSkill = dmgResult.IsSkill, }; EventManager.Instance.SendEvent(EEvent.EntityHit, hitMsg); switch (hitLevel) { case EHitLevel.ShieldBreak: //破盾事件广播 var breakmsg = new PEntityHitText() { Target = target.ID(), Text = "Break", }; EventManager.Instance.SendEvent(EEvent.EntityHitText, breakmsg); break; // case EHitLevel.Block: // //格挡事件广播 // var blockmsg = new PEntityHitText() // { // target = target.ID(), // text = "Block", // }; // EventManager.instance.SendEvent(EEvent.EntityHitText, blockmsg); // break; // default: // //测试 // var msg = new PEntityHitText() // { // target = target.ID(), // text = hitLevel.ToString(), // }; // EventManager.instance.SendEvent(EEvent.EntityHitText, msg); // break; } } private static void SettleHitEffect(Vector3 flowSpeed, EHitLevel hitLevel, EHitType hitType, GameEntity entity, GameEntity target) { //受击特效 var effectRot = GameRandom.RandomRot(Util.Vec3ToRot(new Vector3(flowSpeed.x, 0, flowSpeed.y)), 45); switch (hitLevel) { case EHitLevel.Kill: Util.CastHitEffect(GameConst.EffectHitKill, target.ID(), effectRot); break; case EHitLevel.ShieldBreak: Util.CastHitEffect(GameConst.EffectHitShieldBreak, target.ID(), effectRot); break; case EHitLevel.Block: Util.CastHitEffect(GameConst.EffectHitShield, target.ID(), effectRot); break; default: switch (hitType) { case EHitType.Slash: Util.CastHitEffect(GameConst.EffectHitSlash, target.ID(), effectRot); break; case EHitType.Blunt: Util.CastHitEffect(GameConst.EffectHitBlunt, target.ID(), effectRot); break; default: Util.CastHitEffect(GameConst.EffectHitSlash, target.ID(), effectRot); break; } break; } } private static void SettleFlash(EHitLevel hitLevel, GameEntity entity, GameEntity target) { switch (hitLevel) { case EHitLevel.ShieldBreak: Util.EntityFlash(target, Color.red); break; case EHitLevel.Block: Util.EntityFlash(target, Color.yellow); break; default: Util.EntityFlash(target); break; } } private static void SettleShake(EHitLevel hitLevel, GameEntity entity, GameEntity target) { Util.EntityShake(target, 3); Util.EntityShake(entity, 1); } private static void SettleScale(Vector3 flowSpeed, GameEntity entity, GameEntity target) { var dir = new Vector3(flowSpeed.x * (target.move.IsRight ? 1 : -1), flowSpeed.y, 0); Util.EntityScale(target, dir); } private static void SettleGlobalEffect(GameEntity target, float pauseTime) { if (target.IsMaster()) { //主角受击 Util.SetTimeScaleEffect(0.2f, 1, 0.5f); Util.SetAberrationEffect(1, 0, 0.5f); Util.SetFovEffect(0.9f, 1, 0.5f); return; } if (pauseTime >= 0.1f) { Util.Shake(); var (aberrationFrom, aberrationTo) = (0.5f, 0); var (timeScaleFrom, timeScaleTo) = (0.2f, 1); var (fovFrom, fovTo) = (0.85f, 1); var effectTime = pauseTime + 0.2f; Util.SetAberrationEffect(aberrationFrom, aberrationFrom, pauseTime); Util.AppendAberrationEffect(aberrationFrom, aberrationTo, 0.3f); // Util.SetTimeScaleEffect(timeScaleFrom, timeScaleFrom, pauseTime); // Util.AppendTimeScaleEffect(timeScaleFrom, timeScaleTo, 0.5f); Util.SetFovEffect(fovFrom, fovFrom, pauseTime); Util.AppendFovEffect(fovFrom, fovTo, 0.5f); } else if (pauseTime > 0) { Util.SetAberrationEffect(0.2f, 0, 0.2f); Util.SetFovEffect(0.99f, 1, 0.2f); } else { Util.SetAberrationEffect(0.2f, 0, 0.2f); Util.SetFovEffect(0.99f, 1, 0.2f); } } }