using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Permissions; using System.Timers; using BITFALL.Entities.Player.Movement.States; using BITFALL.Player.Equip; using BITFALL.Player.Movement; using BITKit; using BITKit.Entities; using BITKit.Entities.Movement; using BITKit.Entities.Physics; using BITKit.Entities.Player; using BITKit.PlayerCamera; using BITKit.UX; using Cysharp.Threading.Tasks; using Lightbug.CharacterControllerPro.Core; using Unity.Mathematics; using UnityEditor.TerrainTools; using UnityEngine; using UnityEngine.AI; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Interactions; using UnityEngine.UIElements; namespace BITFALL.Entities.Player.Movement { [CustomType(typeof(IEntityMovement))] [CustomType(typeof(IPlayerMovement))] public class PlayerCharacterController : StateBasedPlayerBehavior,IEntityMovement,IPlayerMovement,IEntityBinaryComponent { [Header(Constant.Header.Settings)] [SerializeField] internal Vector3 initialCameraPosition = new(0,0.11f,0.27f); [SerializeField] internal float initialSpeed; [Header(Constant.Header.Components)] [SerializeField] private CharacterActor actor; [SerializeField] private LocationAdditive locationAdditive; [SerializeField] private LocationAdditive centerAdditive; [Header(Constant.Header.Gameobjects)] [SerializeField] private Transform fpvPoint; [SerializeField] private Transform parachuteModel; [Header(Constant.Header.Input)] [SerializeField] private InputActionReference movementAction; [SerializeField] private InputActionReference viewAction; [SerializeField] private InputActionReference jumpAction; [SerializeField] private InputActionReference crouchAction; [SerializeField] private InputActionReference runAction; [Header(Constant.Header.Providers)] [SerializeReference,SubclassSelector] internal IClosePoint climbClosePoint; [SerializeReference, SubclassSelector] private IProvider adsProvider; public InputActionReference MovementAction => movementAction; public InputActionReference ViewAction => viewAction; public InputActionReference JumpAction => jumpAction; public InputActionReference CrouchAction => crouchAction; public InputActionReference RunAction => runAction; public Vector3 Position { get => actor.Position; set=>actor.Position = value; } public Quaternion Rotation { get => actor.Rotation; set => actor.Rotation = value; } public float ReferenceSpeed { get; internal set; } public Vector3 Forward => actor.Forward; public Vector3 ViewForward { get; set; } public Vector3 ViewCenter { get; private set; } public Vector3 FocusPoint { get; internal set; } public Quaternion ViewRotation { get; set; } public Vector3 LocomotionBasedVelocity { get;internal set; } public Vector3 Velocity => actor.Velocity; public Vector3 GroundVelocity => new Vector3(Velocity.x, 0, Velocity.z); public Vector3 AngularVelocity { get;private set; } public bool IsGrounded => actor.IsGrounded; public Vector3 MovementInput { get; private set; } public Vector2 LookInput{ get; private set; } public Vector2 LookInputDelta { get; private set; } public ExpectState ExpectRun; public ExpectState ExpectJump; public ExpectState ExpectParachute; public ExpectState ExpectCrouch; public ExpectState ExpectSprint; public bool RequestClimb { get;internal set; } public ExpectState ExpectClimb; public ExpectState CurrentCameraPosition; public OffMeshLink OffMeshLink { get; private set; } internal readonly ValidHandle allowMovement = new(); internal readonly ValidHandle allowRun = new(); internal readonly ValidHandle pauseRun = new(); internal readonly ValidHandle allowFocus = new(); internal IOptional limitSpeed =>new ReadOnlyOptional(limitSpeedDictionary.Count is not 0, limitSpeedDictionary.Count is 0 ? 0 : limitSpeedDictionary.Values.Min()); private readonly Dictionary limitSpeedDictionary = new(); private bool isDead; internal bool topBlocked; private Vector3 keepVelocity; private readonly DoubleBuffer gravityDamage = new(); private readonly DoubleBuffer gravityDamping = new(); private Vector3 cacheGravity; [Inject] private IEntityPhysics _physics; [Inject] private IHealth _health; [Inject] private InputActionGroup _inputActionGroup; [Inject] private IKnockdown _knockdown; [Inject] private IEntityOverride _override; [Inject] private IPlayerCameraService _playerCameraService; [Inject] private IUXPopup _uxPopup; public override void OnAwake() { base.OnAwake(); _health.OnSetAlive += OnSetAlive; _physics.OnSetPhysics += OnSetPhysics; LookInput = MathV.TransientRotationAxis(transform.rotation.eulerAngles); parachuteModel.gameObject.SetActive(false); _override.OnOverride += OnOverride; } private void OnOverride(bool obj) { actor.UseRootMotion = obj; if (obj is false) { locationAdditive.SetGlobalPosition(default); } } public override void OnStart() { base.OnStart(); foreach (var x in GetComponentsInChildren(true)) { actor.PhysicsComponent.IgnoreCollision(x.transform,true); } TransitionState(); _inputActionGroup.RegisterCallback(movementAction, OnMovement); _inputActionGroup.RegisterCallback(viewAction, OnView); _inputActionGroup.RegisterCallback(jumpAction, OnJump); _inputActionGroup.RegisterCallback(crouchAction, OnCrouch); _inputActionGroup.RegisterCallback(runAction, OnRun); } private void OnSetPhysics(bool obj) { if (obj is false) return; _physics.Velocity = keepVelocity; //_physics.Velocity = default; actor.Velocity = Vector3.zero; } private void OnSetAlive(bool obj) { allowMovement.SetElements(123,obj); allowRun.SetElements(123,obj); if (obj) { if (!isDead) return; //actor.Position = _physics.Center; isDead = false; } else { isDead = true; } Enabled = true; TransitionState(); } public void SyncMovement(Vector3 velocity, Vector3 position, Quaternion rotation, bool isGrounded) { throw new NotImplementedException(); } public void OnMovement(Vector3 relativeVector) { MovementInput = relativeVector; } public void OnMovement(InputAction.CallbackContext context) { MovementInput = context.ReadValue(); MovementInput = new Vector3(MovementInput.x,0,MovementInput.y); if (_playerCameraService.IsCameraActivated) { if (MovementInput.z > 0) { if (_inputActionGroup.GetAction(runAction).IsPressed()) { ExpectRun.shouldBe = true; } } else { ExpectRun.Reset(); ExpectSprint.Reset(); } }else if (MovementInput == default && _inputActionGroup.GetAction(runAction).IsPressed() is false) { ExpectRun.Reset(); ExpectSprint.Reset(); } else if (MovementInput == default) { ExpectRun.Reset(); ExpectSprint.Reset(); }else if (MovementInput.z > 0 && _inputActionGroup.GetAction(runAction).IsPressed()) { ExpectRun.shouldBe = true; } } public async void ExecuteCommand(T command=default) { foreach (var x in StateDictionary.Values) { x.ExecuteCommand(command); } switch (command) { case PlayerEnableRunCommand enableRunCommand: allowRun.RemoveDisableElements(enableRunCommand.Lock); ExpectRun.Reset(); ExpectSprint.Reset(); break; case PlayerDisableRunCommand disableRunCommand: allowRun.AddDisableElements(disableRunCommand.Lock); break; case PlayerCancelRunCommand: ExpectRun.Reset(); ExpectSprint.Reset(); break; case PlayerChangeVelocityCommand changeVelocityCommand: actor.Velocity = Vector3.Lerp(actor.Velocity, changeVelocityCommand.Velocity, 5 * Time.deltaTime); break; case PlayerAddGravityDampingCommand addGravityDampingCommand: gravityDamping.Release(addGravityDampingCommand.Damping); break; case PlayerPauseRunCommand pauseRunCommand: pauseRun.SetElements(123,pauseRunCommand.Pause); break; case PlayerFocusCommand focusCommand: allowFocus.SetElements(focusCommand.Sender,focusCommand.Focus); break; case PlayerLimitMoveSpeedCommand limitMoveSpeedCommand: if (limitMoveSpeedCommand.Limit) { limitSpeedDictionary.Set(limitMoveSpeedCommand.Id,limitMoveSpeedCommand.Speed); } else { limitSpeedDictionary.TryRemove(limitMoveSpeedCommand.Id); } break; case PlayerDisableMovementCommand disableMovementCommand: allowMovement.SetDisableElements(disableMovementCommand.LockFile,disableMovementCommand.Disable); if (string.IsNullOrEmpty(disableMovementCommand.Source) is false && disableMovementCommand.Disable) { _uxPopup.Popup(disableMovementCommand.Source,disableMovementCommand.Duration); } if (disableMovementCommand.Duration is not 0) { await UniTask.Delay(TimeSpan.FromSeconds(disableMovementCommand.Duration)); if (destroyCancellationToken.IsCancellationRequested) return; allowMovement.SetDisableElements(disableMovementCommand.LockFile,false); } break; } OnCommand?.Invoke(command); } public event Action OnCommand; public void OnView(InputAction.CallbackContext context) { if (BITAppForUnity.AllowCursor) return; var playerConfig = PlayerConfig.Singleton; var ads = adsProvider.Get(); if (ads is 0) ads = 1; var raw = context.ReadValue() * playerConfig.Sensitivity * playerConfig.M_Yaw * ads; LookInputDelta +=raw; var lookInput = LookInput; lookInput.x -= raw.y; lookInput.y += raw.x; lookInput.x = Mathf.Clamp(lookInput.x, -80, 80); LookInput = lookInput; var rotation = Quaternion.Euler(LookInput); if (LimitViewAngle is not 0) { float angleDifference = Quaternion.Angle(rotation, FpvRotation); // 如果角度差大于最大允许角度差,进行限制 if (angleDifference > LimitViewAngle) { //将当前旋转限制在最大允许角度差的范围内 rotation = Quaternion.Lerp( rotation, Quaternion.RotateTowards(FpvRotation,rotation , LimitViewAngle), 5 * Time.deltaTime ); //rotation = Quaternion.RotateTowards(FpvRotation, rotation, LimitViewAngle); LookInput = MathV.TransientRotationAxis(rotation.eulerAngles); } } AngularVelocity = new Vector3 ( raw.y, -raw.x ); centerAdditive.Rotation = rotation; } public void OnJump(InputAction.CallbackContext context) { if (_knockdown.IsKnockdown) return; RequestClimb = context switch { { interaction: PressInteraction, performed: true } => true, { interaction: PressInteraction, canceled: true } => false, _ => RequestClimb }; switch (context) { case {interaction: PressInteraction, performed: true}: break; default: return; } if (ExpectJump.shouldBe) return; switch (CurrentState) { case IPlayerLinkState: return; case IPlayerWalkState: case IPlayerRunState: case IPlayerSprintState: case IPlayerCrouchState: case IPlayerSlideState: foreach (var x in Physics.OverlapSphere(actor.Position, 1, LayerMask.GetMask("Dynamic"))) { if (!x.TryGetComponent(out var offMeshLink)) continue; switch (offMeshLink.area) { case 5: break; default: var toTarget = x.transform.position - transform.position; toTarget = Vector3.ProjectOnPlane(toTarget, Vector3.up); // 获取正前方的向量 var forward = actor.Forward; // 计算点积 var dotProduct = Vector3.Dot(toTarget.normalized, forward); if (dotProduct < 0.8f) continue; break; } OffMeshLink = offMeshLink; TransitionState(); return; } if (MovementInput.x is not 0 && MovementInput.z is 0) { if (_playerCameraService.IsCameraActivated && _stamina >0) { TransitionState(); return; } } if (climbClosePoint.TryGetClosePoint(out var closePoint) && topBlocked is false) { ExpectClimb.shouldBe = closePoint; TransitionState(); return; } switch (actor.IsGrounded) { case true: ExpectJump.shouldBe = true; break; case false when actor.VerticalVelocity.y<=-16f: ExpectParachute.shouldBe = true; return; } break; } ExpectCrouch.Reset(); } public void OnRun(InputAction.CallbackContext context) { if (allowRun.Allow is false) return; if (_knockdown.IsKnockdown) return; if (context.performed) { if (_playerCameraService.IsCameraActivated) { if (MovementInput.z is 0) return; } else { if(MovementInput.x is 0 && MovementInput.z is 0) return; } } var holdToRun = Data.Get(BITConstant.Environment.cl_hold_to_run); //Debug.Log(context); switch (context) { //case { interaction: PressInteraction, started: true }: case { interaction: PressInteraction, canceled:true } when holdToRun: ExpectRun.Reset(); break; case { interaction: PressInteraction, performed:true }: if (holdToRun) { ExpectRun.shouldBe = true; } else { if (ExpectRun.shouldBe && Stamina> 0) ExpectSprint.shouldBe = true; ExpectRun.shouldBe = true; ExpectCrouch.Reset(); } break; case {interaction:MultiTapInteraction,performed:true}: //ExpectSprint.shouldBe = ExpectRun.shouldBe = true; if (ExpectRun.shouldBe) { ExpectSprint.shouldBe = true; } else { ExpectRun.shouldBe = true; } ExpectCrouch.Reset(); break; } } public void OnCrouch(InputAction.CallbackContext context) { if (_knockdown.IsKnockdown) return; if (context.interaction is not null && context.started is false) return; if (CurrentState is Climb) { TransitionState(); } else { ExpectCrouch.shouldBe = !ExpectCrouch.shouldBe; ExpectRun.Reset(); } } public override void OnUpdate(float deltaTime) { CurrentState?.BeforeUpdateMovement(deltaTime); UpdateState(deltaTime); CurrentState?.AfterUpdateMovement(deltaTime); } public override void OnFixedUpdate(float deltaTime) { var currentVelocity = actor.Velocity; var currentRotation = actor.Rotation; if (Stamina is not 100 && staminaRecoveryInterval.AllowUpdateWithoutReset) { Stamina += 10 * deltaTime; } CurrentState?.UpdateVelocity(ref currentVelocity, deltaTime); CurrentState?.UpdateRotation(ref currentRotation, deltaTime); if (gravityDamping.TryGetRelease(out var damping) && currentVelocity.y < 0) { currentVelocity.y = Mathf.Clamp(currentVelocity.y + damping, float.MinValue, 0); } if (allowMovement && _health.IsAlive && actor.RigidbodyComponent.IsKinematic is false) { actor.Velocity = currentVelocity; actor.Rotation = currentRotation; //if (currentVelocity.sqrMagnitude > 0.01f) keepVelocity = currentVelocity; } else if(actor.RigidbodyComponent.IsKinematic is false) { actor.Velocity = default; } var localVelocity = transform.InverseTransformDirection(Velocity) / initialSpeed; localVelocity.y = 0; LocomotionBasedVelocity = localVelocity; if (actor.IsStable is false && actor.IsGrounded is false) { if (cacheGravity != default && (actor.VerticalVelocity - cacheGravity).sqrMagnitude < 4) { gravityDamage.Release(actor.VerticalVelocity); } cacheGravity = actor.VerticalVelocity; //Debug.Log($"IsGround:{actor.IsGrounded}\tIsStable:{actor.IsStable}\tVelocity:{actor.VerticalVelocity}"); } else if (actor.IsStable && gravityDamage.TryGetRelease(out var currentGravity)) { cacheGravity = default; var value = currentGravity.y; //Debug.Log($"Vector:{currentVelocity}\tMagnitude:{value}\tGravity:{currentGravity}"); //Debug.Log(value); switch (value) { case > 0: break; case < -16: UnityEntity.Invoke(new DamageMessage() { Damage = value < -30 ? int.MaxValue : (int)math.abs(value) * 2, DamageType = new GravityDamage(), Position = actor.Position, Rotation = actor.Rotation, Initiator = UnityEntity, Target = UnityEntity, }); break; } } var y = actor.ColliderComponent.Size.y; topBlocked = Physics.Raycast(actor.Position, actor.Up, out var hit, y * 1.5f, actor.stableLayerMask) ; } public override void OnLateUpdate(float deltaTime) { if (_override.IsOvering) { locationAdditive.SetGlobalPosition(fpvPoint.position); locationAdditive.SetGlobalRotation(fpvPoint.rotation); LookInput = MathV.TransientRotationAxis(((Quaternion)FpvRotation).eulerAngles) ; return; } //if (allowMovement.Allow is false) return; var rotation = Quaternion.Euler(LookInput); if (_knockdown.IsKnockdown) { rotation = Quaternion.Euler(LookInput.x, LookInput.y, -8); } if (LimitViewAngle is not 0) { float angleDifference = Quaternion.Angle(rotation, FpvRotation); // 如果角度差大于最大允许角度差,进行限制 if (angleDifference > LimitViewAngle) { // 将当前旋转限制在最大允许角度差的范围内 rotation = Quaternion.Lerp( rotation, Quaternion.RotateTowards(FpvRotation, rotation, LimitViewAngle), 5 * deltaTime ); LookInput = MathV.TransientRotationAxis(rotation.eulerAngles); } } locationAdditive.SetGlobalRotation(rotation); if (CurrentState is IPlayerFixedState) { } CurrentCameraPosition.being = Vector3.Lerp(CurrentCameraPosition.being, CurrentCameraPosition.shouldBe, 5 * deltaTime); locationAdditive.AddPosition(CurrentCameraPosition); ViewCenter = CurrentCameraPosition.being; centerAdditive.AddPosition(Vector3.up * ViewCenter.y); if (_playerCameraService.IsCameraActivated) { ViewRotation = rotation; //ViewForward = additiveTransform.position + Quaternion.Euler(LookInput) * Vector3.forward; ViewForward = ViewRotation * Vector3.forward; FocusPoint = Position + Rotation * ViewCenter + ViewForward * 8f; } switch (CurrentState) { case Sprint: case Run: case Walk: case Crouch: break; default: centerAdditive.SetGlobalRotation(rotation); break; } } private float _stamina = 100; public float Stamina { get=>_stamina; set { if (value < _stamina) { staminaRecoveryInterval.Reset(); } _stamina = Mathf.Clamp(value, 0, 100); OnStaminaChanged?.Invoke(_stamina); } } private readonly IntervalUpdate staminaRecoveryInterval = new(8); private int _id; public int LimitViewAngle { get; set; } public event Action OnStaminaChanged; public float3 FpvPosition => fpvPoint.position; public quaternion FpvRotation => fpvPoint.rotation; public float3 FpvLocalPosition =>fpvPoint.localPosition; public quaternion FpvLocalRotation => fpvPoint.localRotation; public void AddViewEuler(float2 euler) { LookInput = new Vector3 { x = LookInput.x + euler.x, y = LookInput.y + euler.y }; LookInput = MathV.TransientRotationAxis(LookInput); } public event Func TryOpenParachute; public event Action OnParachuteOpened; public event Action OnParachuteClosed; internal void InvokeOpenParachute() { OnParachuteOpened?.Invoke(); parachuteModel.gameObject.SetActive(true); } internal void InvokeCloseParachute() { OnParachuteClosed?.Invoke(); parachuteModel.gameObject.SetActive(false); } public int Id { get; } = new ConstantHash(nameof(IEntityMovement)); public void Serialize(BinaryWriter writer) { writer.WriteFloat3(actor.Position); writer.WriteFloat3(actor.Velocity); writer.Write(LookInput.y); writer.Write(LookInputDelta.y); writer.Write(actor.IsGrounded); LookInputDelta = default; } public void Deserialize(BinaryReader reader) { //reader.ReadFloat3(); //reader.ReadQuaternion(); } } }