2023-08-23 01:59:40 +08:00
|
|
|
using System;
|
2023-08-27 02:58:19 +08:00
|
|
|
using BITFALL.Entities.Player.Movement.States;
|
2023-08-23 01:59:40 +08:00
|
|
|
using BITFALL.Player.Movement;
|
2023-10-20 19:31:12 +08:00
|
|
|
using BITKit;
|
2023-08-23 01:59:40 +08:00
|
|
|
using BITKit.StateMachine;
|
|
|
|
using Lightbug.CharacterControllerPro.Core;
|
|
|
|
using UnityEngine;
|
|
|
|
// ReSharper disable InvertIf
|
|
|
|
// ReSharper disable RedundantJumpStatement
|
|
|
|
// ReSharper disable ConvertToConstant.Global
|
|
|
|
|
2023-08-27 02:58:19 +08:00
|
|
|
namespace BITFALL.Entities.Player.Movement.States
|
2023-08-23 01:59:40 +08:00
|
|
|
{
|
|
|
|
public abstract class BasicMovement : PlayerCharacterState,IPlayerMovementState
|
|
|
|
{
|
|
|
|
[SerializeField] protected Vector3 initialCameraPosition;
|
|
|
|
[SerializeField] protected float initialSpeed = 3f;
|
|
|
|
[SerializeField] protected float initialJumpForce = 5f;
|
2023-10-20 19:31:12 +08:00
|
|
|
[Inject] protected IKnockdown knockdown;
|
|
|
|
|
2023-08-23 01:59:40 +08:00
|
|
|
public override void BeforeUpdateMovement(float deltaTime)
|
|
|
|
{
|
2023-10-02 23:24:56 +08:00
|
|
|
characterController.CurrentCameraPosition.shouldBe = initialCameraPosition;
|
2023-08-23 01:59:40 +08:00
|
|
|
}
|
|
|
|
|
2023-10-20 19:31:12 +08:00
|
|
|
public override void AfterUpdateMovement(float deltaTime)
|
|
|
|
{
|
|
|
|
if (knockdown.IsKnockdown is false && characterController.ExpectParachute.shouldBe)
|
|
|
|
{
|
|
|
|
characterController.TransitionState<Parachute>();
|
|
|
|
characterController.ExpectParachute.Reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-23 01:59:40 +08:00
|
|
|
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));
|
2023-09-01 14:33:54 +08:00
|
|
|
|
|
|
|
if (characterController.ExpectJump.shouldBe)
|
|
|
|
{
|
|
|
|
actor.ForceNotGrounded();
|
|
|
|
currentVelocity += Vector3.up * initialJumpForce;
|
|
|
|
characterController.ExpectJump.Reset();
|
|
|
|
characterController.ExecuteCommand<OnPlayerJumpCommand>();
|
|
|
|
}
|
2023-08-23 01:59:40 +08:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
2023-10-20 19:31:12 +08:00
|
|
|
base.AfterUpdateMovement(deltaTime);
|
2023-08-23 01:59:40 +08:00
|
|
|
switch (characterController.ExpectRun.shouldBe,characterController.ExpectCrouch.shouldBe)
|
|
|
|
{
|
|
|
|
case (_,true):
|
|
|
|
characterController.TransitionState<Crouch>();
|
|
|
|
break;
|
|
|
|
case (true,false):
|
|
|
|
characterController.TransitionState<Run>();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-10-20 19:31:12 +08:00
|
|
|
|
|
|
|
[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;
|
|
|
|
}
|
|
|
|
}
|
2023-08-23 01:59:40 +08:00
|
|
|
[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)
|
|
|
|
{
|
2023-10-20 19:31:12 +08:00
|
|
|
base.AfterUpdateMovement(deltaTime);
|
2023-08-23 01:59:40 +08:00
|
|
|
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>();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-10-20 19:31:12 +08:00
|
|
|
|
|
|
|
[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;
|
|
|
|
}
|
|
|
|
}
|
2023-08-23 01:59:40 +08:00
|
|
|
[Serializable]
|
|
|
|
public sealed class Crouch:BasicMovement,IPlayerCrouchState
|
|
|
|
{
|
2023-10-20 19:31:12 +08:00
|
|
|
|
2023-08-23 01:59:40 +08:00
|
|
|
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)
|
|
|
|
{
|
2023-10-20 19:31:12 +08:00
|
|
|
base.AfterUpdateMovement(deltaTime);
|
2023-08-23 01:59:40 +08:00
|
|
|
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
|
|
|
|
{
|
2023-10-20 19:31:12 +08:00
|
|
|
[SerializeField] private int slideCost = 16;
|
|
|
|
[SerializeField] private int staminaCost = 1;
|
2023-08-23 01:59:40 +08:00
|
|
|
public override void OnStateEntry(IState old)
|
|
|
|
{
|
|
|
|
characterController.ExpectSprint.being = true;
|
|
|
|
}
|
|
|
|
public override void OnStateExit(IState old, IState newState)
|
|
|
|
{
|
|
|
|
characterController.ExpectSprint.being = false;
|
|
|
|
}
|
2023-10-20 19:31:12 +08:00
|
|
|
public override void OnStateUpdate(float deltaTime)
|
|
|
|
{
|
|
|
|
characterController.Stamina -= staminaCost * deltaTime;
|
|
|
|
}
|
2023-08-23 01:59:40 +08:00
|
|
|
public override void AfterUpdateMovement(float deltaTime)
|
|
|
|
{
|
2023-10-20 19:31:12 +08:00
|
|
|
base.AfterUpdateMovement(deltaTime);
|
2023-08-23 01:59:40 +08:00
|
|
|
switch (characterController.ExpectRun.shouldBe,characterController.ExpectCrouch.shouldBe)
|
|
|
|
{
|
2023-10-20 19:31:12 +08:00
|
|
|
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;
|
2023-08-23 01:59:40 +08:00
|
|
|
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>();
|
|
|
|
}
|
|
|
|
}
|
2023-10-20 19:31:12 +08:00
|
|
|
|
|
|
|
[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>();
|
|
|
|
}
|
|
|
|
}
|
2023-08-23 01:59:40 +08:00
|
|
|
}
|