262 lines
11 KiB
C#
262 lines
11 KiB
C#
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;
|
||
|
||
/// <summary>当前是否处于击飞引发的眩晕状态(由落地检测解除)。</summary>
|
||
private bool isLaunchStunned;
|
||
|
||
private Automata Automata => owner as Automata;
|
||
|
||
/// <summary>
|
||
/// 处理受击位移(击退 / 击飞)。
|
||
/// </summary>
|
||
/// <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)
|
||
{
|
||
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
|
||
// ────────────────────────────────────────────────────────────────────
|
||
|
||
/// <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))
|
||
{
|
||
Automata.behaviorSc.mainBehaviorTree.StartBehavior();
|
||
}
|
||
}
|
||
}
|
||
}
|