I am currently working on a sprint function for my first person controller.
My setup is a "PlayerMovementController", a "PlayerStatusController" and a Scriptable Object "PlayerStatusData" which the StatusController is getting information about the Player Stamina.
PlayerMovementController:
private void Update()
{
HandleMovementInput();
ApplyMovement();
}
private void HandleMovementInput()
{
if (Input.GetKey(sprintKey) && !isSprinting) // SPRINTING
{
if (OnCheckStamina != null && OnCheckStamina())
{
StartSprint();
OnStaminaDrain?.Invoke();
}
}
else // NOT SPRINTING
{
StopSprint();
OnStaminaRegen?.Invoke();
}
/*** SPRINTING ENDE ***/
currentInput = new Vector2(movementSpeed * Input.GetAxis("Vertical"), movementSpeed * Input.GetAxis("Horizontal"));
float moveDirectionY = moveDirection.y;
moveDirection = (transform.TransformDirection(Vector3.forward) * currentInput.x) + (transform.TransformDirection(Vector3.right) * currentInput.y);
moveDirection.y = moveDirectionY;
}
private void ApplyMovement()
{
if (!characterController.isGrounded)
moveDirection.y -= gravity * Time.deltaTime;
characterController.Move(moveDirection * Time.deltaTime);
}
private void StartSprint()
{
isSprinting = true;
movementSpeed = sprintSpeed;
}
private void StopSprint()
{
isSprinting = false;
movementSpeed = walkSpeed;
}
The Sprint itself works fine like this, but i have noticed on the Inspector the isSprinting boolean und movementSpeed is constantly changing between true and false and 3f to 1.8f. I can't believe that this is normal, there has to be a proper switch... right? In the gameview itself it does not seem like a problem but the Stamina System with those events is not really working. They are constantly switchting between "Regenerating Stamina" and "Draining Stamina"
PlayerStatusController:
public class PlayerStatusController : MonoBehaviour
{
[Header("Scriptable Object")]
public PlayerStatusData playerStatusData, pSD;
private void OnEnable()
{
PlayerMovementController.OnCheckStamina += HasStamina;
PlayerMovementController.OnStaminaDrain += DrainStamina;
PlayerMovementController.OnStaminaRegen += RegenerateStamina;
}
private void OnDisable()
{
PlayerMovementController.OnCheckStamina -= HasStamina;
PlayerMovementController.OnStaminaDrain -= DrainStamina;
PlayerMovementController.OnStaminaRegen -= RegenerateStamina;
}
private bool HasStamina()
{
return playerStatusData.currentStamina > 0;
}
private void DrainStamina()
{
if (playerStatusData.currentStamina != playerStatusData.minStamina)
{
playerStatusData.currentStamina -= playerStatusData.staminaDrain * Time.deltaTime;
playerStatusData.currentStamina = Mathf.Max(0, playerStatusData.currentStamina); // Ensure stamina doesn't go below 0
Debug.Log("DRAINING STAMINA");
}
}
private void RegenerateStamina()
{
if (playerStatusData.currentStamina < playerStatusData.maxStamina)
{
playerStatusData.currentStamina += playerStatusData.staminaRegen * Time.deltaTime;
playerStatusData.currentStamina = Mathf.Min(100, playerStatusData.currentStamina); // Ensure stamina doesn't go above 100
Debug.Log("REGENERATING STAMINA");
}
}
}
In my eyes the event system should work just fine... but i think there is a problem with the on/off toggling on the sprint script which is causing problems in the stamina system. Anyone have a fix for this?
It's because of your if statement logic: if (Input.GetKey(sprintKey) && !isSprinting)
. Once sprinting starts, !isSprinting
becomes false, meaning that the if statement will be false, causing StopSprinting()
to ALWAYS be called the next frame, even if the key is still held down.
Example frame sequence:
(GetKey [false] & !isSprinting [true]) == (false & true) == false
, StopSprint() is called(GetKey [true] & !isSprinting [true]) == (true & true) == true
, StartSprint() is called(GetKey [true] & !isSprinting [false]) == (true & false) == false
, StopSprint() is called (even though its still held down)(GetKey [true] & !isSprinting [true]) == (true & true) == true
, StartSprint() is called (again, even though it should have still been on from before)Notice how starting frame 2, GetKey
always returns true (simulating the user holding down the sprint key), but the !isSprinting
value keeps alternating.
Change the !isSprinting
check to be inside of the first if statement to fix this issue
if (Input.GetKey(sprintKey)) // SPRINTING
{
if (!isSprinting && OnCheckStamina != null && OnCheckStamina())
{
StartSprint();
OnStaminaDrain?.Invoke();
}
}
else // NOT SPRINTING
{
StopSprint();
OnStaminaRegen?.Invoke();
}