BITFALL/Assets/Artists/Scripts/Player/CharacterControllerPro/PlayerCharacterStates.cs

369 lines
11 KiB
C#

using System;
using BITFALL.Entities.Player.Movement.States;
using BITFALL.Player.Movement;
using BITKit;
using BITKit.StateMachine;
using Lightbug.CharacterControllerPro.Core;
using UnityEngine;
// 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;
public override void BeforeUpdateMovement(float deltaTime)
{
characterController.CurrentCameraPosition.shouldBe = initialCameraPosition;
}
public override void AfterUpdateMovement(float deltaTime)
{
if (knockdown.IsKnockdown is false && characterController.ExpectParachute.shouldBe)
{
characterController.TransitionState<Parachute>();
characterController.ExpectParachute.Reset();
}
}
public override void UpdateVelocity(ref Vector3 currentVelocity,float deltaTime)
{
var rotation = Quaternion.Euler(0,characterController.LookInput.y,0);
var moveVelocity = rotation * characterController.MovementInput;
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
currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity, 1f - Mathf.Exp(-16 * deltaTime));
if (characterController.ExpectJump.shouldBe)
{
actor.ForceNotGrounded();
currentVelocity += Vector3.up * initialJumpForce;
characterController.ExpectJump.Reset();
characterController.ExecuteCommand<OnPlayerJumpCommand>();
}
}
else
{
if (moveVelocity.sqrMagnitude > 0f)
{
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)
{
currentRotation = Quaternion.Euler(0,characterController.LookInput.y,0);
}
}
[Serializable]
public sealed class Walk:BasicMovement,IPlayerWalkState
{
public override void OnStateEntry(IState old)
{
base.OnStateEntry(old);
characterController.ExpectRun.Reset();
characterController.ExpectSprint.Reset();
}
public override void AfterUpdateMovement(float deltaTime)
{
base.AfterUpdateMovement(deltaTime);
switch (characterController.ExpectRun.shouldBe,characterController.ExpectCrouch.shouldBe)
{
case (_,true):
characterController.TransitionState<Crouch>();
break;
case (true,false):
characterController.TransitionState<Run>();
break;
}
}
}
[Serializable]
public sealed class Parachute : PlayerCharacterState, IPlayerParachuteState
{
[SerializeField] private float moveDamping = 3f;
[SerializeField] private float damping = 1f;
public override void OnStateEntry(IState old)
{
characterController.InvokeOpenParachute();
}
public override void OnStateExit(IState old, IState newState)
{
characterController.InvokeCloseParachute();
}
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);
if (characterController.ExpectSprint.shouldBe)
{
characterController.TransitionState<Sprint>();
}
switch (characterController.ExpectRun.shouldBe,characterController.ExpectCrouch.shouldBe)
{
case (_,true):
characterController.TransitionState<Crouch>();
break;
case (false,false):
characterController.TransitionState<Walk>();
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;
_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)
{
characterController.Stamina -= staminaCost * deltaTime;
}
public override void AfterUpdateMovement(float deltaTime)
{
base.AfterUpdateMovement(deltaTime);
switch (characterController.ExpectRun.shouldBe,characterController.ExpectCrouch.shouldBe)
{
case (_,_) when characterController.Stamina is 0:
characterController.ExpectSprint.Reset();
characterController.TransitionState<Run>();
break;
case (_,true) when characterController.IsGrounded && characterController.Stamina >= slideCost:
characterController.Stamina -= slideCost;
characterController.TransitionState<Slide>();
break;
case (_,true):
characterController.TransitionState<Crouch>();
break;
case (false,false):
characterController.TransitionState<Walk>();
break;
}
}
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;
}
private void OnRevive()
{
if(Enabled)characterController.TransitionState<Walk>();
}
private void OnKnockdown()
{
characterController.TransitionState<Knockdown>();
}
}
}