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 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(); } else { movement.TransitionState(); } } 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; } 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(); return; } if (clip) { if (_state.Time > clip.length) { movement.TransitionState(); 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 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(); } } 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(); return; } if(movement.colliders.Any(LocalDestinationIsBlocked)) { } else { movement.TransitionState(); } } 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(); return; } if (agent.isOnOffMeshLink) { movement.TransitionState(); return; } //if(colliders.Any(Blocked.IsBlocked)) if (movement.colliders.Any(LocalDestinationIsBlocked)) { movement.TransitionState(); 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; _spline = agent.currentOffMeshLinkData.offMeshLink.TryGetComponent(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(); } } 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(); } } public override void OnStateExit(IState old, IState newState) { base.OnStateExit(old, newState); agent.CompleteOffMeshLink(); } } }