using Cielonos.MainGame.Buffs.Character; using Opsive.BehaviorDesigner.Runtime; using SLSUtilities.General; using UnityEngine; using UnityEngine.AI; namespace Cielonos.MainGame.Characters { public partial class AutomataLandMovementSubcontroller : LandMovementSubcontroller { public NavMeshAgent navMeshAgent => (owner as Automata)!.behaviorSc.navMeshAgent; public LerpFloat animatorMoveSpeedX; public LerpFloat animatorMoveSpeedZ; public override void Initialize() { base.Initialize(); navMeshAgent.isStopped = true; animatorMoveSpeedX = new LerpFloat(0f, 5f); animatorMoveSpeedZ = new LerpFloat(0f, 5f); } private Vector3 lastDirection; protected override void Update() { base.Update(); HandleNavMeshState(); } protected override void OnAnimatorMove() { if (owner.selfTimeSm.DeltaTime == 0 || owner.statusSm.isDead) { return; } if (navMeshAgent != null && navMeshAgent.enabled) { UpdateNavigationAnimParams(); } base.OnAnimatorMove(); InitiativeMove(); lastDirection = transform.forward; } protected override void InitiativeMove() { if (navMeshAgent.enabled) { navMeshAgent.Move(finalMovementVelocity); navMeshAgent.baseOffset += finalMovementVelocity.y; navMeshAgent.baseOffset = Mathf.Max(navMeshAgent.baseOffset, 0f); } else { if (owner.collisionSc.useCharacterController) { owner.collisionSc.characterController.Move(finalMovementVelocity); } else { Vector3 startPosition = owner.collisionSc.mainRigidbody.position; owner.collisionSc.mainRigidbody.MovePosition(startPosition + finalMovementVelocity); } } } // ──────────────────────────────────────────────────────────────────── // Navigation Animation // ──────────────────────────────────────────────────────────────────── private void UpdateNavigationAnimParams() { if (!is8WayMovement) { animatorMoveSpeedZ.targetValue = navMeshAgent.isStopped ? 0f : navMeshAgent.velocity.magnitude + Vector3.Angle(lastDirection, transform.forward) / owner.selfTimeSm.DeltaTime * 0.02f; animatorMoveSpeedZ.Update(0.2f, 1); animator.SetFloat("MoveSpeedZ", animatorMoveSpeedZ.currentValue); } else { if (!navMeshAgent.isStopped) { Vector3 velocity = navMeshAgent.velocity; animatorMoveSpeedX.targetValue = Vector3.Dot(velocity, transform.right); animatorMoveSpeedZ.targetValue = Vector3.Dot(velocity, transform.forward); } else { animatorMoveSpeedX.targetValue = 0f; animatorMoveSpeedZ.targetValue = 0f; } animatorMoveSpeedX.Update(0.2f, 1); animatorMoveSpeedZ.Update(0.2f, 1); animator.SetFloat("MoveSpeedX", animatorMoveSpeedX.currentValue); animator.SetFloat("MoveSpeedZ", animatorMoveSpeedZ.currentValue); } } } // ════════════════════════════════════════════════════════════════════════ // Hit Impact & Stun // ════════════════════════════════════════════════════════════════════════ public partial class AutomataLandMovementSubcontroller { private const float DEFAULT_STUN_DURATION = 0.25f; public bool stagnantFirstHalf; public bool stagnantLastHalf; /// 当前是否处于击飞引发的眩晕状态(由落地检测解除)。 private bool isLaunchStunned; private Automata Automata => owner as Automata; /// /// 处理受击位移(击退 / 击飞)。 /// /// 力的矢量(XZ = 击退方向和强度,Y = 击飞强度)。 /// 是否为"击飞"(无视抗性,自动眩晕)。 /// Y 轴是否使用抛物线物理。仅 isLaunch 时有意义。 /// 脉冲持续时间(秒),0 使用默认值。 /// 速度衰减曲线,null 使用默认 EaseOut。 /// (仅击退)是否附加眩晕状态。击飞始终附加眩晕。 /// (仅击退)眩晕持续时间(秒),0 跟随脉冲持续时间。 /// 重力缩放倍率。0 = 浮空,1 = 正常(默认),大于 1 = 加速坠落。 /// 重力修正持续时间(秒)。0 = 不施加重力修正。 public void ApplyHitImpact(Vector3 force, bool isLaunch, bool isParabolic, float duration = 0f, AnimationCurve curve = null, bool applyStun = false, float stunDuration = 0f, float gravityMultiplier = 1f, float gravityDuration = 0f) { if (isLaunch && navMeshAgent.enabled) navMeshAgent.enabled = false; if (!stagnantFirstHalf && !stagnantLastHalf) { stagnantFirstHalf = true; } impulseSm.ApplyImpulse(force, isLaunch, duration, curve, isParabolic); if (isLaunch) { ApplyStun(); isLaunchStunned = true; } else if (applyStun) { float effectiveDuration = stunDuration > 0f ? stunDuration : (duration > 0f ? duration : DEFAULT_STUN_DURATION); ApplyStun(effectiveDuration); } ApplyGravityModifier(gravityMultiplier, gravityDuration); } // ──────────────────────────────────────────────────────────────────── // NavMesh State Machine // ──────────────────────────────────────────────────────────────────── private void HandleNavMeshState() { if (navMeshAgent == null) return; if (stagnantFirstHalf && !isOnGround && finalMovementVelocity.y < 0) { stagnantFirstHalf = false; stagnantLastHalf = true; } if (stagnantLastHalf && isOnGround) { impulseSm.ClearLaunch(); TryRemoveLaunchStun(); if (!navMeshAgent.enabled) { navMeshAgent.enabled = true; navMeshAgent.Warp(transform.position); } stagnantFirstHalf = false; stagnantLastHalf = false; } } // ──────────────────────────────────────────────────────────────────── // Gravity Modifier // ──────────────────────────────────────────────────────────────────── /// /// 通过 VerticalMoveModification Buff 施加重力修正。 /// multiplier 大于 1 时先清除已有浮空 Buff,确保 Finisher 坠落生效。 /// private void ApplyGravityModifier(float multiplier, float duration) { if (duration <= 0f) return; if (multiplier > 1f) { if (owner.buffSm.TryGetBuff(out var existingBuff)) { existingBuff.Remove(); } } new VerticalMoveModification(duration, multiplier).Apply(owner); } // ──────────────────────────────────────────────────────────────────── // Stun Helpers // ──────────────────────────────────────────────────────────────────── /// /// 施加眩晕。无参数版为永久眩晕(击飞用),由落地检测解除; /// 带 duration 版为定时眩晕(击退用),到期自动解除。 /// private void ApplyStun(float duration = -1f) { Automata.statusSm.AddStatus(StatusType.Stun); Automata.behaviorSc.mainBehaviorTree.StopBehavior(true); if (duration > 0f) { owner.selfTimeSm.AddLocalTimer(duration, () => TryResumeFromStun()); } } /// /// 尝试解除击飞眩晕(落地时调用)。 /// private void TryRemoveLaunchStun() { if (!isLaunchStunned) return; isLaunchStunned = false; TryResumeFromStun(); } /// /// 移除一层 Stun,若所有 Stun 层归零则恢复行为树。 /// private void TryResumeFromStun() { Automata.statusSm.RemoveStatus(StatusType.Stun); if (!Automata.statusSm.HasStatus(StatusType.Stun)) { Automata.behaviorSc.mainBehaviorTree.StartBehavior(); } } } }