2026-05-23 08:27:50 -04:00
|
|
|
|
using Cielonos.MainGame.Buffs.Character;
|
|
|
|
|
|
using Opsive.BehaviorDesigner.Runtime;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
using SLSUtilities.General;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.AI;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Cielonos.MainGame.Characters
|
|
|
|
|
|
{
|
2026-02-13 09:22:11 -05:00
|
|
|
|
public partial class AutomataLandMovementSubcontroller : LandMovementSubcontroller
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2026-05-10 11:47:55 -04:00
|
|
|
|
public NavMeshAgent navMeshAgent => (owner as Automata)!.behaviorSc.navMeshAgent;
|
2026-05-23 08:27:50 -04:00
|
|
|
|
public LerpFloat animatorMoveSpeedX;
|
|
|
|
|
|
public LerpFloat animatorMoveSpeedZ;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
|
|
|
|
|
public override void Initialize()
|
|
|
|
|
|
{
|
|
|
|
|
|
base.Initialize();
|
2026-01-03 18:19:39 -05:00
|
|
|
|
navMeshAgent.isStopped = true;
|
2026-05-23 08:27:50 -04:00
|
|
|
|
animatorMoveSpeedX = new LerpFloat(0f, 5f);
|
|
|
|
|
|
animatorMoveSpeedZ = new LerpFloat(0f, 5f);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-08 05:27:53 -05:00
|
|
|
|
private Vector3 lastDirection;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
|
|
|
|
|
|
protected override void Update()
|
|
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
base.Update();
|
2026-02-13 09:22:11 -05:00
|
|
|
|
HandleNavMeshState();
|
|
|
|
|
|
}
|
2026-01-12 03:22:16 -05:00
|
|
|
|
|
|
|
|
|
|
protected override void OnAnimatorMove()
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2026-03-20 12:07:44 -04:00
|
|
|
|
if (owner.selfTimeSm.DeltaTime == 0 || owner.statusSm.isDead)
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
if (navMeshAgent != null && navMeshAgent.enabled)
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
UpdateNavigationAnimParams();
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
base.OnAnimatorMove();
|
|
|
|
|
|
InitiativeMove();
|
2025-12-08 05:27:53 -05:00
|
|
|
|
lastDirection = transform.forward;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override void InitiativeMove()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (navMeshAgent.enabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
navMeshAgent.Move(finalMovementVelocity);
|
2026-03-20 12:07:44 -04:00
|
|
|
|
navMeshAgent.baseOffset += finalMovementVelocity.y;
|
|
|
|
|
|
navMeshAgent.baseOffset = Mathf.Max(navMeshAgent.baseOffset, 0f);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (owner.collisionSc.useCharacterController)
|
|
|
|
|
|
{
|
|
|
|
|
|
owner.collisionSc.characterController.Move(finalMovementVelocity);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
Vector3 startPosition = owner.collisionSc.mainRigidbody.position;
|
|
|
|
|
|
owner.collisionSc.mainRigidbody.MovePosition(startPosition + finalMovementVelocity);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-13 09:22:11 -05:00
|
|
|
|
|
2026-05-23 08:27:50 -04:00
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
// Navigation Animation
|
|
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
private void UpdateNavigationAnimParams()
|
2026-02-13 09:22:11 -05:00
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-02-13 09:22:11 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
// ════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// Hit Impact & Stun
|
|
|
|
|
|
// ════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
public partial class AutomataLandMovementSubcontroller
|
|
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
private const float DEFAULT_STUN_DURATION = 0.25f;
|
|
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
public bool stagnantFirstHalf;
|
|
|
|
|
|
public bool stagnantLastHalf;
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
/// <summary>当前是否处于击飞引发的眩晕状态(由落地检测解除)。</summary>
|
|
|
|
|
|
private bool isLaunchStunned;
|
|
|
|
|
|
|
|
|
|
|
|
private Automata Automata => owner as Automata;
|
|
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
/// <summary>
|
2026-05-23 08:27:50 -04:00
|
|
|
|
/// 处理受击位移(击退 / 击飞)。
|
2026-02-13 09:22:11 -05:00
|
|
|
|
/// </summary>
|
2026-05-23 08:27:50 -04:00
|
|
|
|
/// <param name="force">力的矢量(XZ = 击退方向和强度,Y = 击飞强度)。</param>
|
|
|
|
|
|
/// <param name="isLaunch">是否为"击飞"(无视抗性,自动眩晕)。</param>
|
|
|
|
|
|
/// <param name="isParabolic">Y 轴是否使用抛物线物理。仅 isLaunch 时有意义。</param>
|
|
|
|
|
|
/// <param name="duration">脉冲持续时间(秒),0 使用默认值。</param>
|
|
|
|
|
|
/// <param name="curve">速度衰减曲线,null 使用默认 EaseOut。</param>
|
|
|
|
|
|
/// <param name="applyStun">(仅击退)是否附加眩晕状态。击飞始终附加眩晕。</param>
|
|
|
|
|
|
/// <param name="stunDuration">(仅击退)眩晕持续时间(秒),0 跟随脉冲持续时间。</param>
|
|
|
|
|
|
/// <param name="gravityMultiplier">重力缩放倍率。0 = 浮空,1 = 正常(默认),大于 1 = 加速坠落。</param>
|
|
|
|
|
|
/// <param name="gravityDuration">重力修正持续时间(秒)。0 = 不施加重力修正。</param>
|
|
|
|
|
|
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)
|
2026-02-13 09:22:11 -05:00
|
|
|
|
{
|
|
|
|
|
|
if (isLaunch && navMeshAgent.enabled) navMeshAgent.enabled = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (!stagnantFirstHalf && !stagnantLastHalf)
|
|
|
|
|
|
{
|
|
|
|
|
|
stagnantFirstHalf = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-23 08:27:50 -04:00
|
|
|
|
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
|
|
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 通过 VerticalMoveModification Buff 施加重力修正。
|
|
|
|
|
|
/// multiplier 大于 1 时先清除已有浮空 Buff,确保 Finisher 坠落生效。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void ApplyGravityModifier(float multiplier, float duration)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (duration <= 0f) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (multiplier > 1f)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (owner.buffSm.TryGetBuff<VerticalMoveModification>(out var existingBuff))
|
|
|
|
|
|
{
|
|
|
|
|
|
existingBuff.Remove();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
new VerticalMoveModification(duration, multiplier).Apply(owner);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
// Stun Helpers
|
|
|
|
|
|
// ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 施加眩晕。无参数版为永久眩晕(击飞用),由落地检测解除;
|
|
|
|
|
|
/// 带 duration 版为定时眩晕(击退用),到期自动解除。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void ApplyStun(float duration = -1f)
|
|
|
|
|
|
{
|
|
|
|
|
|
Automata.statusSm.AddStatus(StatusType.Stun);
|
|
|
|
|
|
Automata.behaviorSc.mainBehaviorTree.StopBehavior(true);
|
|
|
|
|
|
|
|
|
|
|
|
if (duration > 0f)
|
|
|
|
|
|
{
|
|
|
|
|
|
owner.selfTimeSm.AddLocalTimer(duration, () => TryResumeFromStun());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 尝试解除击飞眩晕(落地时调用)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void TryRemoveLaunchStun()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isLaunchStunned) return;
|
|
|
|
|
|
isLaunchStunned = false;
|
|
|
|
|
|
TryResumeFromStun();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 移除一层 Stun,若所有 Stun 层归零则恢复行为树。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void TryResumeFromStun()
|
|
|
|
|
|
{
|
|
|
|
|
|
Automata.statusSm.RemoveStatus(StatusType.Stun);
|
|
|
|
|
|
|
|
|
|
|
|
if (!Automata.statusSm.HasStatus(StatusType.Stun))
|
2026-02-13 09:22:11 -05:00
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
Automata.behaviorSc.mainBehaviorTree.StartBehavior();
|
2026-02-13 09:22:11 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
}
|