655 lines
19 KiB
C#
655 lines
19 KiB
C#
using System;
|
|
using BITFALL.Entities.Player.Movement.States;
|
|
using BITFALL.Player.Equip;
|
|
using BITFALL.Player.Movement;
|
|
using BITKit;
|
|
using BITKit.Entities;
|
|
using BITKit.PlayerCamera;
|
|
using BITKit.StateMachine;
|
|
using Cysharp.Threading.Tasks;
|
|
using Lightbug.CharacterControllerPro.Core;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
|
|
// ReSharper disable InvertIf
|
|
// ReSharper disable RedundantJumpStatement
|
|
// ReSharper disable ConvertToConstant.Global
|
|
|
|
namespace BITFALL.Entities.Player.Movement.States
|
|
{
|
|
public abstract class BasicMovement : PlayerCharacterState, IPlayerMovementState
|
|
{
|
|
[SerializeField] protected Vector3 initialCameraPosition;
|
|
[SerializeField] protected float initialSpeed = 3f;
|
|
[SerializeField] protected float initialJumpForce = 5f;
|
|
[Inject] protected IKnockdown knockdown;
|
|
[Inject] protected IPlayerCameraService _cameraService;
|
|
|
|
public override void OnStateEntry(IState old)
|
|
{
|
|
base.OnStateEntry(old);
|
|
characterController.ReferenceSpeed = initialSpeed;
|
|
}
|
|
|
|
public override void BeforeUpdateMovement(float deltaTime)
|
|
{
|
|
characterController.CurrentCameraPosition.shouldBe = initialCameraPosition;
|
|
}
|
|
|
|
public override void AfterUpdateMovement(float deltaTime)
|
|
{
|
|
if (knockdown.IsKnockdown)
|
|
{
|
|
characterController.TransitionState<Knockdown>();
|
|
}
|
|
|
|
if (knockdown.IsKnockdown is false && characterController.ExpectParachute.shouldBe)
|
|
{
|
|
characterController.TransitionState<Parachute>();
|
|
characterController.ExpectParachute.Reset();
|
|
}
|
|
|
|
if (characterController.RequestClimb is false) return;
|
|
if(characterController.topBlocked)return;
|
|
|
|
// if (characterController.topBlocked is false && characterController.RequestClimb &&
|
|
// characterController.climbClosePoint.TryGetClosePoint(out var closePoint))
|
|
// {
|
|
// characterController.ExpectClimb.shouldBe = closePoint;
|
|
// characterController.TransitionState<Climb>();
|
|
// return;
|
|
// }
|
|
|
|
if (characterController.vaultPoint.TryGetClosePoint(out var closePoint))
|
|
{
|
|
characterController.ExpectClimb.shouldBe = closePoint;
|
|
characterController.TransitionState<Vault>();
|
|
characterController.ExpectJump.Reset();
|
|
characterController.RequestClimb = false;
|
|
return;
|
|
}
|
|
|
|
if (characterController.climbClosePoint.TryGetClosePoint(out closePoint))
|
|
{
|
|
characterController.ExpectClimb.shouldBe = closePoint;
|
|
characterController.TransitionState<Climb>();
|
|
characterController.ExpectJump.Reset();
|
|
characterController.RequestClimb = false;
|
|
return;
|
|
}
|
|
|
|
if (characterController.edgeClimbPoint.TryGetClosePoint(out closePoint))
|
|
{
|
|
characterController.ExpectClimb.shouldBe = closePoint;
|
|
characterController.TransitionState<EdgeClimb>();characterController.ExpectJump.Reset();
|
|
characterController.RequestClimb = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
|
|
{
|
|
var rotation = Quaternion.Euler(0, characterController.LookInput.y, 0);
|
|
var movementInput = characterController.MovementInput;
|
|
|
|
if (_cameraService?.IsCameraActivated is false)
|
|
{
|
|
rotation =Quaternion.LookRotation( _cameraService.CameraRotation * Vector3.forward,Vector3.up);
|
|
}
|
|
|
|
var moveVelocity = rotation * new Vector3(
|
|
movementInput.x * Mathf.Min(characterController.initialSpeed, initialSpeed),
|
|
0,
|
|
movementInput.z * initialSpeed
|
|
);
|
|
if (_cameraService?.IsCameraActivated is false)
|
|
{
|
|
moveVelocity = rotation * movementInput * initialSpeed;
|
|
}
|
|
|
|
if (characterController.limitSpeed.Allow)
|
|
{
|
|
switch (characterController.CurrentState)
|
|
{
|
|
case IPlayerWalkState:
|
|
case IPlayerCrouchState:
|
|
moveVelocity = Vector3.ClampMagnitude(moveVelocity, characterController.limitSpeed.Value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (characterController.IsGrounded)
|
|
{
|
|
var effectiveGroundNormal = actor.GroundStableNormal;
|
|
|
|
|
|
var inputRight = Vector3.Cross(moveVelocity, Vector3.up);
|
|
var reorientedInput = Vector3.Cross(effectiveGroundNormal, inputRight).normalized *
|
|
moveVelocity.magnitude;
|
|
var targetMovementVelocity = reorientedInput; //* initialSpeed;
|
|
|
|
// Smooth movement Velocity
|
|
|
|
var newVelocity =
|
|
Vector3.Lerp(currentVelocity, targetMovementVelocity, 1f - Mathf.Exp(-16 * deltaTime));
|
|
currentVelocity = newVelocity;
|
|
|
|
if (characterController.ExpectJump.shouldBe)
|
|
{
|
|
actor.ForceNotGrounded();
|
|
|
|
if (characterController.landFreeze.AllowUpdateWithoutReset is false)
|
|
{
|
|
currentVelocity = Vector3.Lerp(currentVelocity, default, 0.8f);
|
|
}
|
|
|
|
currentVelocity.y+= initialJumpForce;
|
|
|
|
|
|
characterController.ExpectJump.Reset();
|
|
|
|
characterController.ExecuteCommand<OnPlayerJumpCommand>();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (moveVelocity.sqrMagnitude > 0f && characterController.landFreeze.AllowUpdateWithoutReset)
|
|
{
|
|
var addedVelocity = moveVelocity * (3 * deltaTime);
|
|
|
|
var currentVelocityOnInputsPlane = Vector3.ProjectOnPlane(currentVelocity, Vector3.up);
|
|
|
|
// Limit air velocity from inputs
|
|
if (currentVelocityOnInputsPlane.magnitude < 5)
|
|
{
|
|
// clamp addedVel to make total vel not exceed max vel on inputs plane
|
|
Vector3 newTotal = Vector3.ClampMagnitude(currentVelocityOnInputsPlane + addedVelocity, 5);
|
|
addedVelocity = newTotal - currentVelocityOnInputsPlane;
|
|
}
|
|
else
|
|
{
|
|
// Make sure added vel doesn't go in the direction of the already-exceeding velocity
|
|
if (Vector3.Dot(currentVelocityOnInputsPlane, addedVelocity) > 0f)
|
|
{
|
|
addedVelocity =
|
|
Vector3.ProjectOnPlane(addedVelocity, currentVelocityOnInputsPlane.normalized);
|
|
}
|
|
}
|
|
|
|
// Prevent air-climbing sloped walls
|
|
if (actor.WallCollision)
|
|
{
|
|
if (Vector3.Dot(currentVelocity + addedVelocity, addedVelocity) > 0f)
|
|
{
|
|
var perpenticularObstructionNormal = Vector3
|
|
.Cross(Vector3.Cross(Vector3.up, actor.GroundContactNormal), Vector3.up).normalized;
|
|
addedVelocity = Vector3.ProjectOnPlane(addedVelocity, perpenticularObstructionNormal);
|
|
}
|
|
}
|
|
|
|
// Apply added velocity
|
|
currentVelocity += addedVelocity;
|
|
}
|
|
// Gravity
|
|
|
|
currentVelocity += -Vector3.up * (30 * deltaTime);
|
|
// Drag
|
|
currentVelocity *= (1f / (1f + (0.1f * deltaTime)));
|
|
}
|
|
}
|
|
|
|
public override void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
|
|
{
|
|
var newRotation = Quaternion.Euler(0, characterController.LookInput.y, 0);
|
|
|
|
|
|
|
|
if (_cameraService.IsCameraActivated is false)
|
|
{
|
|
if (Physics.Raycast(_cameraService.CameraPosition, _cameraService.CameraRotation * Vector3.forward,
|
|
out var hit, 256, LayerMask.GetMask("Default"), QueryTriggerInteraction.Ignore))
|
|
{
|
|
characterController.ViewForward = (hit.point - (characterController.Position +
|
|
characterController.Rotation *
|
|
characterController.ViewCenter)).normalized;
|
|
|
|
newRotation = Quaternion.LookRotation(characterController.ViewForward);
|
|
characterController.ViewRotation = newRotation;
|
|
|
|
characterController.FocusPoint = hit.point;
|
|
|
|
Debug.DrawLine(_cameraService.CameraPosition, hit.point, Color.green, 0.1f);
|
|
Debug.DrawLine(characterController.Position + characterController.ViewCenter,
|
|
characterController.Position +
|
|
characterController.ViewCenter + characterController.ViewRotation * Vector3.forward
|
|
, Color.blue, 0.1f);
|
|
}
|
|
else
|
|
{
|
|
|
|
|
|
characterController.ViewRotation = Quaternion.Euler(characterController.LookInput);;
|
|
|
|
characterController.FocusPoint = characterController.Position +
|
|
characterController.ViewRotation * Vector3.forward * 256;
|
|
|
|
Debug.DrawLine(_cameraService.CameraPosition, _cameraService.CameraRotation * Vector3.forward,
|
|
Color.red, 0.1f);
|
|
}
|
|
|
|
|
|
var rotationDirection = _cameraService.CameraRotation * characterController.MovementInput;
|
|
rotationDirection = Vector3.ProjectOnPlane(rotationDirection, Vector3.up);
|
|
if (rotationDirection.sqrMagnitude >= 0.16f)
|
|
{
|
|
var newPlayerRotation =Quaternion.LookRotation(rotationDirection) ;
|
|
currentRotation = Quaternion.Lerp(currentRotation, newPlayerRotation, 1f - Mathf.Exp(-16 * deltaTime));
|
|
}
|
|
|
|
if (characterController.allowFocus)
|
|
{
|
|
currentRotation =
|
|
Quaternion.LookRotation(_cameraService.CameraRotation * Vector3.forward, Vector3.up);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currentRotation = newRotation;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public sealed class Walk:BasicMovement,IPlayerWalkState
|
|
{
|
|
[Inject] private InputActionGroup _inputActionGroup;
|
|
|
|
public override void AfterUpdateMovement(float deltaTime)
|
|
{
|
|
base.AfterUpdateMovement(deltaTime);
|
|
if (characterController.CurrentState != this) return;
|
|
switch (characterController.ExpectRun.shouldBe,characterController.ExpectCrouch.shouldBe)
|
|
{
|
|
case (_,true):
|
|
characterController.TransitionState<Crouch>();
|
|
return;
|
|
case (true,false) when characterController.pauseRun.Allow is false && characterController.MovementInput.z>0:
|
|
characterController.TransitionState<Run>();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public sealed class Parachute : PlayerCharacterState, IPlayerParachuteState
|
|
{
|
|
[SerializeField] private float moveDamping = 3f;
|
|
[SerializeField] private float damping = 1f;
|
|
|
|
[Inject] private IEquipService _equipService;
|
|
public override void OnStateEntry(IState old)
|
|
{
|
|
base.OnStateEntry(old);
|
|
characterController.InvokeOpenParachute();
|
|
_equipService.AllowEquip.AddDisableElements(this);
|
|
}
|
|
|
|
public override void OnStateExit(IState old, IState newState)
|
|
{
|
|
base.OnStateExit(old, newState);
|
|
characterController.InvokeCloseParachute();
|
|
|
|
_equipService.AllowEquip.RemoveDisableElements(this);
|
|
}
|
|
|
|
public override void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
|
|
{
|
|
currentRotation = Quaternion.Euler(0,characterController.LookInput.y,0);
|
|
}
|
|
|
|
public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
|
|
{
|
|
var rotation = Quaternion.Euler(0,characterController.LookInput.y,0);
|
|
var moveVelocity = rotation * characterController.MovementInput;
|
|
currentVelocity.y =
|
|
Mathf.MoveTowards(
|
|
currentVelocity.y,
|
|
-8,
|
|
damping * deltaTime
|
|
);
|
|
currentVelocity+= moveVelocity.normalized * (moveDamping * deltaTime);
|
|
currentVelocity.x = Mathf.Clamp(currentVelocity.x, -moveDamping, moveDamping);
|
|
currentVelocity.z = Mathf.Clamp(currentVelocity.z, -moveDamping, moveDamping);
|
|
}
|
|
public override void AfterUpdateMovement(float deltaTime)
|
|
{
|
|
if (actor.IsGrounded)
|
|
{
|
|
characterController.TransitionState<Walk>();
|
|
}
|
|
characterController.ExpectParachute.Reset();
|
|
characterController.CurrentCameraPosition.shouldBe = characterController.FpvLocalPosition;
|
|
}
|
|
}
|
|
[Serializable]
|
|
public sealed class Run:BasicMovement,IPlayerRunState
|
|
{
|
|
public override void OnStateEntry(IState old)
|
|
{
|
|
characterController.ExpectRun.being = true;
|
|
}
|
|
|
|
public override void OnStateExit(IState old, IState newState)
|
|
{
|
|
characterController.ExpectRun.being = false;
|
|
}
|
|
|
|
public override void AfterUpdateMovement(float deltaTime)
|
|
{
|
|
base.AfterUpdateMovement(deltaTime);
|
|
|
|
switch (characterController.CurrentState)
|
|
{
|
|
case Walk:
|
|
case Run:
|
|
case Sprint:
|
|
case Crouch:
|
|
switch (characterController.ExpectRun.shouldBe, characterController.ExpectCrouch.shouldBe)
|
|
{
|
|
case (_, true):
|
|
characterController.TransitionState<Crouch>();
|
|
return;
|
|
case (false, false):
|
|
characterController.TransitionState<Walk>();
|
|
return;
|
|
}
|
|
|
|
if (characterController.MovementInput.z <= 0)
|
|
{
|
|
characterController.TransitionState<Walk>();
|
|
return;
|
|
}
|
|
|
|
if (characterController.pauseRun.Allow)
|
|
{
|
|
characterController.TransitionState<Walk>();
|
|
return;
|
|
}
|
|
|
|
if (characterController.ExpectSprint.shouldBe && characterController.MovementInput.z > 0)
|
|
{
|
|
characterController.TransitionState<Sprint>();
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override void ExecuteCommand<T>(T command)
|
|
{
|
|
if (Enabled is false) return;
|
|
if (command is PlayerCancelRunCommand)
|
|
{
|
|
characterController.TransitionState<Walk>();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public sealed class Slide : BasicMovement, IPlayerSlideState
|
|
{
|
|
[Header(nameof(Slide))]
|
|
[SerializeField] private float additiveSpeed = 1f;
|
|
[SerializeField] private float damping = 1;
|
|
[SerializeField] private float stopSpeed = 0.64f;
|
|
[SerializeField] private bool alwaysSlide;
|
|
private bool _addedVelocity;
|
|
public override void OnStateEntry(IState old)
|
|
{
|
|
characterController.ExpectCrouch.being = true;
|
|
characterController.ExpectCrouch.shouldBe = true;
|
|
characterController.ExecuteCommand<PlayerCancelRunCommand>();
|
|
_addedVelocity = false;
|
|
}
|
|
|
|
public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
|
|
{
|
|
if (alwaysSlide && actor.IsGrounded is false)
|
|
{
|
|
base.UpdateVelocity(ref currentVelocity, deltaTime);
|
|
}
|
|
else
|
|
{
|
|
if (_addedVelocity is false)
|
|
{
|
|
currentVelocity += currentVelocity.normalized * additiveSpeed;
|
|
_addedVelocity = true;
|
|
}
|
|
else
|
|
{
|
|
currentVelocity = Vector3.Lerp(currentVelocity, Vector3.zero, deltaTime * damping);
|
|
}
|
|
}
|
|
}
|
|
public override void AfterUpdateMovement(float deltaTime)
|
|
{
|
|
if (characterController.ExpectRun.shouldBe)
|
|
{
|
|
characterController.TransitionState<Run>();
|
|
return;
|
|
}
|
|
if (characterController.ExpectCrouch.shouldBe is false || (alwaysSlide is false && actor.IsGrounded is false))
|
|
{
|
|
characterController.TransitionState<Walk>();
|
|
return;
|
|
}
|
|
if (actor.Velocity.sqrMagnitude <= stopSpeed)
|
|
{
|
|
characterController.TransitionState<Crouch>();
|
|
}
|
|
characterController.CurrentCameraPosition.shouldBe = characterController.FpvLocalPosition;
|
|
}
|
|
}
|
|
[Serializable]
|
|
public sealed class Crouch:BasicMovement,IPlayerCrouchState
|
|
{
|
|
|
|
public override void OnStateEntry(IState old)
|
|
{
|
|
characterController.ExpectCrouch.being = true;
|
|
}
|
|
|
|
public override void OnStateExit(IState old, IState newState)
|
|
{
|
|
characterController.ExpectCrouch.being = false;
|
|
}
|
|
public override void AfterUpdateMovement(float deltaTime)
|
|
{
|
|
base.AfterUpdateMovement(deltaTime);
|
|
if (characterController.ExpectRun.shouldBe)
|
|
{
|
|
characterController.TransitionState<Run>();
|
|
return;
|
|
}
|
|
|
|
if (characterController.ExpectCrouch.shouldBe is false)
|
|
{
|
|
characterController.TransitionState<Walk>();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
[Serializable]
|
|
public sealed class Sprint:BasicMovement,IPlayerSprintState
|
|
{
|
|
[SerializeField] private int slideCost = 16;
|
|
[SerializeField] private int staminaCost = 1;
|
|
public override void OnStateEntry(IState old)
|
|
{
|
|
characterController.ExpectSprint.being = true;
|
|
}
|
|
public override void OnStateExit(IState old, IState newState)
|
|
{
|
|
characterController.ExpectSprint.being = false;
|
|
}
|
|
|
|
public override void OnStateUpdate(float deltaTime)
|
|
{
|
|
if (actor.IsGrounded && actor.Velocity.GetLength() > characterController.initialSpeed/2)
|
|
characterController.Stamina -= staminaCost * deltaTime;
|
|
}
|
|
|
|
public override void AfterUpdateMovement(float deltaTime)
|
|
{
|
|
base.AfterUpdateMovement(deltaTime);
|
|
if (characterController.pauseRun.Allow)
|
|
{
|
|
characterController.TransitionState<Walk>();
|
|
return;
|
|
}
|
|
switch (characterController.ExpectRun.shouldBe,characterController.ExpectCrouch.shouldBe)
|
|
{
|
|
case (_,_) when characterController.Stamina is 0:
|
|
characterController.ExpectSprint.Reset();
|
|
characterController.TransitionState<Run>();
|
|
return;
|
|
case (_,true) when characterController.IsGrounded && characterController.Stamina > 0:
|
|
characterController.Stamina -= slideCost;
|
|
characterController.TransitionState<Slide>();
|
|
return;
|
|
case (_,true):
|
|
characterController.TransitionState<Crouch>();
|
|
return;
|
|
case (true,false) when characterController.MovementInput.z <=0:
|
|
case (false,false):
|
|
characterController.TransitionState<Walk>();
|
|
return;
|
|
}
|
|
}
|
|
public override void ExecuteCommand<T>(T command)
|
|
{
|
|
if (Enabled is false) return;
|
|
if(command is PlayerCancelRunCommand)
|
|
characterController.TransitionState<Walk>();
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public sealed class Knockdown : BasicMovement, IPlayerKnockdownState
|
|
{
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
knockdown.OnKnockdown += OnKnockdown;
|
|
knockdown.OnRevive += OnRevive;
|
|
}
|
|
|
|
public override void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
|
|
{
|
|
base.UpdateVelocity(ref currentVelocity, deltaTime);
|
|
if (knockdown.IsPressured)
|
|
{
|
|
currentVelocity.x = 0;
|
|
currentVelocity.z = 0;
|
|
}
|
|
}
|
|
|
|
private void OnRevive()
|
|
{
|
|
if(Enabled)characterController.TransitionState<Walk>();
|
|
}
|
|
|
|
private void OnKnockdown()
|
|
{
|
|
characterController.TransitionState<Knockdown>();
|
|
}
|
|
}
|
|
[Serializable]
|
|
public sealed class Clip:PlayerCharacterState
|
|
{
|
|
[BITCommand]
|
|
public static void NoClip()
|
|
{
|
|
_clipAction?.Invoke();
|
|
}
|
|
private static Action _clipAction;
|
|
|
|
[SerializeField] private InputActionReference clipAction;
|
|
[SerializeReference, SubclassSelector] private IReference clipEnv;
|
|
[Inject] private IHealth _health;
|
|
[Inject] private InputActionGroup _inputActionGroup;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
_health.OnSetAlive += OnSetAlive;
|
|
|
|
Data.AddListener<bool>(clipEnv.Value, OnClip);
|
|
|
|
_clipAction = _ClipAction;
|
|
|
|
characterController.destroyCancellationToken.Register(() =>
|
|
{
|
|
Data.RemoveListender<bool>(clipEnv.Value, OnClip);
|
|
});
|
|
}
|
|
|
|
private void _ClipAction()
|
|
{
|
|
var clip = Data.Get<bool>(clipEnv.Value);
|
|
Data.Set(clipEnv.Value, !clip);
|
|
}
|
|
|
|
private async void OnClip(bool obj)
|
|
{
|
|
await UniTask.SwitchToMainThread();
|
|
if(_health.IsAlive is false) return;
|
|
if (obj && Enabled is false)
|
|
{
|
|
characterController.TransitionState<Clip>();
|
|
//BIT4Log.Log<Clip>("NoClip Enabled");
|
|
}else if (Enabled && obj is false)
|
|
{
|
|
characterController.TransitionState<Walk>();
|
|
//BIT4Log.Log<Clip>("NoClip Disabled");
|
|
}
|
|
}
|
|
|
|
private void OnSetAlive(bool obj)
|
|
{
|
|
if (Enabled && obj is false)
|
|
{
|
|
characterController.TransitionState<Walk>();
|
|
}
|
|
}
|
|
|
|
public override void OnStateEntry(IState old)
|
|
{
|
|
base.OnStateEntry(old);
|
|
actor.Velocity = default;
|
|
actor.ColliderComponent.enabled = false;
|
|
actor.alwaysNotGrounded = true;
|
|
}
|
|
|
|
public override void OnStateExit(IState old, IState newState)
|
|
{
|
|
base.OnStateExit(old, newState);
|
|
actor.ColliderComponent.enabled = true;
|
|
actor.alwaysNotGrounded = false;
|
|
}
|
|
|
|
public override void AfterUpdateMovement(float deltaTime)
|
|
{
|
|
base.AfterUpdateMovement(deltaTime);
|
|
actor.Position += characterController.ViewRotation * characterController.MovementInput * (
|
|
deltaTime * (_inputActionGroup.GetAction(characterController.RunAction).IsPressed()
|
|
? 8
|
|
: characterController.initialSpeed
|
|
));
|
|
actor.Rotation = Quaternion.Euler(0,characterController.LookInput.y,0);
|
|
}
|
|
}
|
|
}
|