2023-12-26 20:07:19 +08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections;
|
2023-12-30 17:37:48 +08:00
|
|
|
|
using System.Collections.Concurrent;
|
2023-12-26 20:07:19 +08:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using Animancer;
|
|
|
|
|
using AYellowpaper.SerializedCollections;
|
2023-12-30 17:37:48 +08:00
|
|
|
|
using BITFALL.Scene;
|
2023-12-26 20:07:19 +08:00
|
|
|
|
using BITKit;
|
|
|
|
|
using BITKit.Entities;
|
|
|
|
|
using BITKit.StateMachine;
|
2023-12-27 02:24:00 +08:00
|
|
|
|
using Unity.Mathematics;
|
2023-12-26 20:07:19 +08:00
|
|
|
|
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;
|
2023-12-30 17:37:48 +08:00
|
|
|
|
|
2023-12-26 20:07:19 +08:00
|
|
|
|
|
|
|
|
|
protected Vector3 MotionVelocity { get; private set; }
|
2023-12-27 02:24:00 +08:00
|
|
|
|
protected Vector3 MotionAngularVelocity { get; private set; }
|
|
|
|
|
protected Quaternion MotionDeltaRotation { get; private set; }
|
2023-12-26 20:07:19 +08:00
|
|
|
|
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;
|
2023-12-27 02:24:00 +08:00
|
|
|
|
MotionAngularVelocity = animancerComponent.Animator.angularVelocity;
|
|
|
|
|
MotionDeltaRotation = animancerComponent.Animator.deltaRotation;
|
2023-12-26 20:07:19 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[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>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
public sealed class IdleTurn : MotionBasedState
|
|
|
|
|
{
|
|
|
|
|
[SerializeField] private AnimationClip clip;
|
|
|
|
|
private AnimancerState _state;
|
|
|
|
|
|
|
|
|
|
private Quaternion entryRotation;
|
|
|
|
|
Quaternion targetRotation;
|
2023-12-30 17:37:48 +08:00
|
|
|
|
|
2023-12-26 20:07:19 +08:00
|
|
|
|
public override void OnStateEntry(IState old)
|
|
|
|
|
{
|
|
|
|
|
base.OnStateEntry(old);
|
2023-12-30 17:37:48 +08:00
|
|
|
|
|
|
|
|
|
if (clip)
|
|
|
|
|
_state = animancerComponent.Play(clip, 0.2f);
|
|
|
|
|
|
2023-12-26 20:07:19 +08:00
|
|
|
|
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;
|
|
|
|
|
}
|
2023-12-30 17:37:48 +08:00
|
|
|
|
|
2023-12-26 20:07:19 +08:00
|
|
|
|
_state.Events.OnEnd += movement.TransitionState<Idle>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnStateExit(IState old, IState newState)
|
|
|
|
|
{
|
|
|
|
|
base.OnStateExit(old, newState);
|
|
|
|
|
agent.transform.rotation = targetRotation;
|
2023-12-30 17:37:48 +08:00
|
|
|
|
_state = null;
|
2023-12-26 20:07:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnStateUpdate(float deltaTime)
|
|
|
|
|
{
|
|
|
|
|
base.OnStateUpdate(deltaTime);
|
2023-12-30 17:37:48 +08:00
|
|
|
|
if (_state is null)
|
|
|
|
|
{
|
|
|
|
|
if (Quaternion.Angle(movement.Rotation, targetRotation) < 1)
|
|
|
|
|
movement.TransitionState<Idle>();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (!(_state.Time > clip.length)) return;
|
|
|
|
|
movement.TransitionState<Idle>();
|
|
|
|
|
}
|
2023-12-26 20:07:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
currentRotation = Quaternion.Lerp(entryRotation, targetRotation, _state.NormalizedTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-30 17:37:48 +08:00
|
|
|
|
|
|
|
|
|
[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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2023-12-26 20:07:19 +08:00
|
|
|
|
[Serializable]
|
|
|
|
|
public sealed class Walk : MotionBasedState
|
|
|
|
|
{
|
2023-12-27 02:24:00 +08:00
|
|
|
|
[SerializeField] private LinearMixerTransition _state;
|
|
|
|
|
|
|
|
|
|
private AnimancerState _playingState;
|
|
|
|
|
private float rot = 0;
|
2023-12-26 20:07:19 +08:00
|
|
|
|
public override void OnStateEntry(IState old)
|
|
|
|
|
{
|
|
|
|
|
base.OnStateEntry(old);
|
2023-12-27 02:24:00 +08:00
|
|
|
|
_playingState = animancerComponent.Play(_state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnStateExit(IState old, IState newState)
|
|
|
|
|
{
|
|
|
|
|
base.OnStateExit(old, newState);
|
|
|
|
|
_playingState = null;
|
2023-12-26 20:07:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnStateUpdate(float deltaTime)
|
|
|
|
|
{
|
|
|
|
|
base.OnStateUpdate(deltaTime);
|
|
|
|
|
if (agent.remainingDistance < 0.1f)
|
|
|
|
|
{
|
|
|
|
|
movement.TransitionState<Idle>();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-12-30 17:37:48 +08:00
|
|
|
|
|
2023-12-26 20:07:19 +08:00
|
|
|
|
if (agent.isOnOffMeshLink)
|
|
|
|
|
{
|
|
|
|
|
movement.TransitionState<OffMeshLink>();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-12-30 17:37:48 +08:00
|
|
|
|
|
|
|
|
|
//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);
|
|
|
|
|
}
|
2023-12-26 20:07:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2023-12-27 02:24:00 +08:00
|
|
|
|
|
|
|
|
|
var pathRotation = currentRotation;
|
|
|
|
|
var lerpRotation = currentRotation;
|
|
|
|
|
|
2023-12-26 20:07:19 +08:00
|
|
|
|
var direction = agent.steeringTarget - agent.transform.position;
|
|
|
|
|
if(direction.sqrMagnitude>0.1f)
|
|
|
|
|
{
|
|
|
|
|
direction = Vector3.ProjectOnPlane(direction, Vector3.up);
|
2023-12-27 02:24:00 +08:00
|
|
|
|
pathRotation = Quaternion.LookRotation(direction);
|
|
|
|
|
lerpRotation =
|
2023-12-26 20:07:19 +08:00
|
|
|
|
Quaternion.RotateTowards(
|
|
|
|
|
currentRotation,
|
2023-12-27 02:24:00 +08:00
|
|
|
|
pathRotation,
|
2023-12-26 20:07:19 +08:00
|
|
|
|
360 * deltaTime
|
|
|
|
|
)
|
|
|
|
|
;
|
|
|
|
|
}
|
2023-12-27 02:24:00 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2023-12-26 20:07:19 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|