470 lines
12 KiB
C#
470 lines
12 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Concurrent;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Animancer;
|
||
using AYellowpaper.SerializedCollections;
|
||
using BITFALL.Scene;
|
||
using BITKit;
|
||
using BITKit.Entities;
|
||
using BITKit.StateMachine;
|
||
using Unity.Mathematics;
|
||
using UnityEngine;
|
||
using UnityEngine.AI;
|
||
using UnityEngine.Splines;
|
||
|
||
namespace BITFALL.Movement.MotionBased.States
|
||
{
|
||
public abstract class MotionBasedState:IMotionBasedState
|
||
{
|
||
[Inject] protected NavMeshAgent agent;
|
||
[Inject] protected MotionBasedMovement movement;
|
||
[Inject] protected AnimancerComponent animancerComponent;
|
||
[Inject] protected IHealth health;
|
||
|
||
|
||
protected Vector3 MotionVelocity { get; private set; }
|
||
protected Vector3 MotionAngularVelocity { get; private set; }
|
||
protected Quaternion MotionDeltaRotation { get; private set; }
|
||
public virtual bool Enabled { get; set; }
|
||
public virtual void Initialize()
|
||
{
|
||
|
||
}
|
||
|
||
public virtual void OnStateEntry(IState old)
|
||
{
|
||
}
|
||
|
||
public virtual void OnStateUpdate(float deltaTime)
|
||
{
|
||
|
||
}
|
||
|
||
public virtual void OnStateExit(IState old, IState newState)
|
||
{
|
||
}
|
||
|
||
public virtual void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
|
||
{
|
||
}
|
||
|
||
public virtual void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
|
||
{
|
||
}
|
||
|
||
public virtual void BeforeUpdateMovement(float deltaTime)
|
||
{
|
||
}
|
||
|
||
public virtual void AfterUpdateMovement(float deltaTime)
|
||
{
|
||
}
|
||
|
||
public virtual void ExecuteCommand<T>(T command)
|
||
{
|
||
}
|
||
|
||
public virtual void OnAnimatorMove()
|
||
{
|
||
MotionVelocity = animancerComponent.Animator.velocity;
|
||
MotionAngularVelocity = animancerComponent.Animator.angularVelocity;
|
||
MotionDeltaRotation = animancerComponent.Animator.deltaRotation;
|
||
}
|
||
}
|
||
|
||
[Serializable]
|
||
public sealed class Empty : MotionBasedState
|
||
{
|
||
public override void OnStateEntry(IState old)
|
||
{
|
||
base.OnStateEntry(old);
|
||
animancerComponent.Layers[0].Stop();
|
||
}
|
||
}
|
||
[Serializable]
|
||
public sealed class Idle : MotionBasedState
|
||
{
|
||
[SerializeField] private AnimationClip clip;
|
||
public override void OnStateEntry(IState old)
|
||
{
|
||
base.OnStateEntry(old);
|
||
animancerComponent.Play(clip,0.2f);
|
||
}
|
||
|
||
public override void OnStateUpdate(float deltaTime)
|
||
{
|
||
base.OnStateUpdate(deltaTime);
|
||
|
||
var transform = agent.transform;
|
||
|
||
if (!(agent.remainingDistance > 0.1f)) return;
|
||
if(MathV.IsForward(transform.position, transform.forward, agent.steeringTarget))
|
||
{
|
||
movement.TransitionState<Walk>();
|
||
}
|
||
else
|
||
{
|
||
movement.TransitionState<IdleTurn>();
|
||
}
|
||
}
|
||
public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
|
||
{
|
||
base.UpdateVelocity(ref currentVelocity, deltaTime);
|
||
currentVelocity = default;
|
||
}
|
||
}
|
||
|
||
[Serializable]
|
||
public sealed class IdleTurn : MotionBasedState
|
||
{
|
||
[SerializeField] private AnimationClip clip;
|
||
private AnimancerState _state;
|
||
|
||
private Quaternion entryRotation;
|
||
Quaternion targetRotation;
|
||
|
||
public override void OnStateEntry(IState old)
|
||
{
|
||
base.OnStateEntry(old);
|
||
|
||
if (clip)
|
||
_state = animancerComponent.Play(clip, 0.2f);
|
||
|
||
entryRotation = movement.Rotation;
|
||
var direction = agent.steeringTarget - movement.Position;
|
||
|
||
if (direction.sqrMagnitude > 0.1f)
|
||
{
|
||
targetRotation =
|
||
Quaternion.LookRotation(
|
||
Vector3.ProjectOnPlane(agent.steeringTarget - agent.transform.position, Vector3.up)
|
||
);
|
||
}
|
||
else
|
||
{
|
||
targetRotation = entryRotation;
|
||
}
|
||
|
||
if (clip)
|
||
_state.Events.OnEnd += movement.TransitionState<Idle>;
|
||
}
|
||
|
||
public override void OnStateExit(IState old, IState newState)
|
||
{
|
||
base.OnStateExit(old, newState);
|
||
agent.transform.rotation = targetRotation;
|
||
_state = null;
|
||
}
|
||
|
||
public override void OnStateUpdate(float deltaTime)
|
||
{
|
||
base.OnStateUpdate(deltaTime);
|
||
if (Quaternion.Angle(movement.Rotation, targetRotation) < 1)
|
||
{
|
||
movement.TransitionState<Idle>();
|
||
return;
|
||
}
|
||
|
||
if (clip)
|
||
{
|
||
if (_state.Time > clip.length)
|
||
{
|
||
movement.TransitionState<Idle>();
|
||
return;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
|
||
{
|
||
base.UpdateVelocity(ref currentVelocity, deltaTime);
|
||
currentVelocity = default;
|
||
}
|
||
|
||
public override void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
|
||
{
|
||
base.UpdateRotation(ref currentRotation, deltaTime);
|
||
if (clip)
|
||
{
|
||
currentRotation = Quaternion.Lerp(entryRotation, targetRotation, _state.NormalizedTime);
|
||
}
|
||
else
|
||
{
|
||
currentRotation = Quaternion.RotateTowards(currentRotation, targetRotation, 720 * deltaTime);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
[Serializable]
|
||
public sealed class Blocked : MotionBasedState
|
||
{
|
||
[RuntimeInitializeOnLoadMethod]
|
||
private static void Reload()
|
||
{
|
||
BlockAreas.Clear();
|
||
}
|
||
internal static readonly ConcurrentDictionary<int, ISceneBlockArea> BlockAreas = new();
|
||
|
||
internal static bool IsBlocked(Collider x)
|
||
{
|
||
var instanceId = x.GetInstanceID();
|
||
var area = BlockAreas.GetOrAdd(instanceId, GetBlockArea);
|
||
return area switch
|
||
{
|
||
not null => area.IsBlocked,
|
||
_ => false,
|
||
};
|
||
ISceneBlockArea GetBlockArea(int id)
|
||
{
|
||
return x.GetComponent<ISceneBlockArea>();
|
||
}
|
||
}
|
||
|
||
internal static bool DestinationIsBlocked(Collider x, Vector3 initialPosition,Vector3 direction)
|
||
{
|
||
if (IsBlocked(x) is false) return false;
|
||
var block = BlockAreas[x.GetInstanceID()];
|
||
var dir = Vector3.ProjectOnPlane(direction, Vector3.up).normalized;
|
||
return block.InRange(initialPosition, dir);
|
||
}
|
||
|
||
|
||
[SerializeField] private AnimationClip clip;
|
||
|
||
private Vector3 initialPosition;
|
||
public override void OnStateEntry(IState old)
|
||
{
|
||
animancerComponent.Play(clip);
|
||
initialPosition = movement.Position + movement.ViewCenter;
|
||
}
|
||
|
||
public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
|
||
{
|
||
base.UpdateVelocity(ref currentVelocity, deltaTime);
|
||
currentVelocity = default;
|
||
}
|
||
|
||
public override void OnStateUpdate(float deltaTime)
|
||
{
|
||
base.OnStateUpdate(deltaTime);
|
||
if(movement.colliders.Length is 0)
|
||
{
|
||
movement.TransitionState<Idle>();
|
||
return;
|
||
}
|
||
if(movement.colliders.Any(LocalDestinationIsBlocked))
|
||
{
|
||
|
||
}
|
||
else
|
||
{
|
||
movement.TransitionState<Idle>();
|
||
}
|
||
}
|
||
private bool LocalDestinationIsBlocked(Collider x)
|
||
{
|
||
var direction = agent.nextPosition - initialPosition;
|
||
return DestinationIsBlocked(x, initialPosition, direction.normalized);
|
||
}
|
||
|
||
|
||
}
|
||
[Serializable]
|
||
public sealed class Walk : MotionBasedState
|
||
{
|
||
[SerializeField] private LinearMixerTransition _state;
|
||
|
||
private AnimancerState _playingState;
|
||
private float rot = 0;
|
||
public override void OnStateEntry(IState old)
|
||
{
|
||
base.OnStateEntry(old);
|
||
_playingState = animancerComponent.Play(_state);
|
||
}
|
||
|
||
public override void OnStateExit(IState old, IState newState)
|
||
{
|
||
base.OnStateExit(old, newState);
|
||
_playingState = null;
|
||
}
|
||
|
||
public override void OnStateUpdate(float deltaTime)
|
||
{
|
||
base.OnStateUpdate(deltaTime);
|
||
if (agent.remainingDistance < 0.1f)
|
||
{
|
||
movement.TransitionState<Idle>();
|
||
return;
|
||
}
|
||
|
||
if (agent.isOnOffMeshLink)
|
||
{
|
||
movement.TransitionState<OffMeshLink>();
|
||
return;
|
||
}
|
||
|
||
//if(colliders.Any(Blocked.IsBlocked))
|
||
if (movement.colliders.Any(LocalDestinationIsBlocked))
|
||
{
|
||
movement.TransitionState<Blocked>();
|
||
return;
|
||
|
||
}
|
||
return;
|
||
bool LocalDestinationIsBlocked(Collider x)
|
||
{
|
||
var direction = agent.nextPosition - movement.Position;
|
||
return Blocked.DestinationIsBlocked(x, movement.Position+movement.ViewCenter, direction.normalized);
|
||
}
|
||
}
|
||
|
||
public override void AfterUpdateMovement(float deltaTime)
|
||
{
|
||
base.AfterUpdateMovement(deltaTime);
|
||
movement.transform.position = agent.nextPosition;
|
||
}
|
||
|
||
public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
|
||
{
|
||
base.UpdateVelocity(ref currentVelocity, deltaTime);
|
||
|
||
currentVelocity = MotionVelocity;
|
||
}
|
||
|
||
public override void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
|
||
{
|
||
base.UpdateRotation(ref currentRotation, deltaTime);
|
||
|
||
var pathRotation = currentRotation;
|
||
var lerpRotation = currentRotation;
|
||
|
||
var direction = agent.steeringTarget - agent.transform.position;
|
||
if(direction.sqrMagnitude>0.1f)
|
||
{
|
||
direction = Vector3.ProjectOnPlane(direction, Vector3.up);
|
||
pathRotation = Quaternion.LookRotation(direction);
|
||
lerpRotation =
|
||
Quaternion.RotateTowards(
|
||
currentRotation,
|
||
pathRotation,
|
||
360 * deltaTime
|
||
)
|
||
;
|
||
}
|
||
|
||
|
||
if (_playingState is not null)
|
||
{
|
||
var dir = pathRotation * Vector3.forward; //位置差,方向
|
||
var dot = Vector3.Dot(movement.Forward, dir.normalized);//点乘判断前后:dot >0在前,<0在后
|
||
var dot1 = Vector3.Dot(movement.transform.right, dir.normalized);//点乘判断左右: dot1>0在右,<0在左
|
||
var angle = Mathf.Acos(Vector3.Dot(movement.Forward.normalized, dir.normalized)) * Mathf.Rad2Deg;//通过点乘求出
|
||
|
||
angle = dot1 > 0 ? angle : -angle;
|
||
|
||
var clamp = Mathf.Clamp(angle/45, -1, 1);
|
||
|
||
rot = Mathf.MoveTowards(rot, clamp, 8 * deltaTime);
|
||
|
||
_state.State.Parameter =rot ;
|
||
|
||
//Debug.Log($"angle:{angle} dot:{dot} dot1:{dot1} clamp:{clamp}");
|
||
|
||
}
|
||
currentRotation = lerpRotation;
|
||
}
|
||
}
|
||
|
||
[Serializable]
|
||
public sealed class OffMeshLink : MotionBasedState
|
||
{
|
||
[SerializeField] public AnimationClip[] clips;
|
||
private AnimancerState _state;
|
||
|
||
private Vector3 startPos;
|
||
private Vector3 endPos;
|
||
private Spline _spline;
|
||
|
||
private (float progress,float increment) splineProgress;
|
||
public override void OnStateEntry(IState old)
|
||
{
|
||
base.OnStateEntry(old);
|
||
if (clips.TryGetElementAt(agent.currentOffMeshLinkData.offMeshLink.area, out var clip) && clip)
|
||
{
|
||
agent.autoTraverseOffMeshLink = false;
|
||
_state = animancerComponent.Play(clip);
|
||
_state.Events.OnEnd += movement.TransitionState<Idle>;
|
||
|
||
_spline = agent.currentOffMeshLinkData.offMeshLink.TryGetComponent<SplineContainer>(out var splineContainer) ? splineContainer[0] : null;
|
||
|
||
var currentOffMeshLinkData = agent.currentOffMeshLinkData;
|
||
startPos = currentOffMeshLinkData.startPos;
|
||
endPos = currentOffMeshLinkData.endPos;
|
||
|
||
if (_spline is not null)
|
||
{
|
||
var offsetTransform= splineContainer.transform;
|
||
var isStart =
|
||
Vector3.Distance(
|
||
movement.Position,
|
||
offsetTransform.TransformPoint(splineContainer.Splines[0].Knots.First().Position)
|
||
) <
|
||
Vector3.Distance(
|
||
movement.Position,
|
||
offsetTransform.TransformPoint(splineContainer.Splines[0].Knots.Last().Position)
|
||
);
|
||
splineProgress = isStart ? (0, 1) : (1, -1);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_state = null;
|
||
agent.autoTraverseOffMeshLink = true;
|
||
movement.TransitionState<Idle>();
|
||
}
|
||
}
|
||
|
||
public override void OnStateUpdate(float deltaTime)
|
||
{
|
||
base.OnStateUpdate(deltaTime);
|
||
try
|
||
{
|
||
if (_spline is not null)
|
||
{
|
||
splineProgress.progress += splineProgress.increment * deltaTime;
|
||
var link = agent.currentOffMeshLinkData.offMeshLink.transform;
|
||
movement.transform.position =
|
||
link.position + link.rotation *
|
||
_spline.EvaluatePosition(splineProgress.progress);
|
||
}
|
||
else
|
||
{
|
||
var currentOffMeshLinkData = agent.currentOffMeshLinkData;
|
||
movement.transform.position = Vector3.Lerp(
|
||
currentOffMeshLinkData.startPos,
|
||
currentOffMeshLinkData.endPos,
|
||
_state.NormalizedTime
|
||
);
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogException(e);
|
||
movement.TransitionState<Idle>();
|
||
}
|
||
|
||
}
|
||
|
||
public override void OnStateExit(IState old, IState newState)
|
||
{
|
||
base.OnStateExit(old, newState);
|
||
agent.CompleteOffMeshLink();
|
||
}
|
||
}
|
||
}
|
||
|