[SOLVED] FPS Controller small running problem

Hi!

I have a small problem. I managed to set the movement speed to a certain value when the character is crawling, crouching, walking or running. The character only runs if its Standing, but if i hit Play the character doesn’t run for the first time i need to crouch or prone down then stand up again to be able to run. My question is, how do i incorporate a script in this that checks if the character is already standing when i hit the Play button in Unity? It might be the easiest thing in the world but somehow nothing worked that i tried out.
Thank you in advance for your answer. :smile:

public class PlayerMove : MonoBehaviour
{
    [SerializeField] private string horizontalInputName;
    [SerializeField] private string verticalInputName;

    [SerializeField] private float walkSpeed, runSpeed, crouchSpeed, proneSpeed;
    [SerializeField] private float runBuildUpSpeed;
    [SerializeField] private KeyCode runKey;

    private float movementSpeed;

    [SerializeField] private float slopeForce;
    [SerializeField] private float slopeForceRayLength;

    private CharacterController charController;

    [SerializeField] private AnimationCurve jumpFallOff;
    [SerializeField] private float jumpMultiplier;
    [SerializeField] private KeyCode jumpKey;

    [SerializeField] private KeyCode crouchKey;
    [SerializeField] private KeyCode proneKey;



    private bool isJumping;
    private bool isCrouching;
    private bool isProne;
    private bool isRunning;
    private bool isStanding;



    private void Awake()
    {
        charController = GetComponent<CharacterController>();
        charController.height = 2.0f;
    }



    private void Update()
    {
        PlayerMovement();
    }


    //MOVE


    private void PlayerMovement()
    {
        float horizInput = Input.GetAxis(horizontalInputName);
        float vertInput = Input.GetAxis(verticalInputName);

        Vector3 forwardMovement = transform.forward * vertInput;
        Vector3 rightMovement = transform.right * horizInput;

        charController.SimpleMove(Vector3.ClampMagnitude(forwardMovement + rightMovement, 1.0f) * movementSpeed);

        if ((vertInput != 0 || horizInput != 0) && OnSlope())
            charController.Move(Vector3.down * charController.height / 2 * slopeForce * Time.deltaTime);



        SetMovementSpeed();
        JumpInput();
        CrouchInput();
        ProneInput();
    }


    private void SetMovementSpeed()
    {

        if (Input.GetKey(runKey) && isStanding)
        {
            movementSpeed = runSpeed;
        }

        else if (isCrouching)
            movementSpeed = crouchSpeed;

        else if (isProne)
            movementSpeed = proneSpeed;

        else
            movementSpeed = walkSpeed;
    }



    //JUMP


    private bool OnSlope()
    {
        if (isJumping)
            return false;

        RaycastHit hit;

        if (Physics.Raycast(transform.position, Vector3.down, out hit, charController.height / 2 * slopeForceRayLength))
            if (hit.normal != Vector3.up)
                return true;
        return false;
    }


    private void JumpInput()
    {
        if (Input.GetKeyDown(jumpKey) && !isJumping)
        {
            isJumping = true;
            StartCoroutine(JumpEvent());
        }
    }

    private IEnumerator JumpEvent()
    {
        charController.slopeLimit = 90.0f;
        float timeInAir = 0.0f;


        do
        {
            float jumpForce = jumpFallOff.Evaluate(timeInAir);
            charController.Move(Vector3.up * jumpForce * jumpMultiplier * Time.deltaTime);
            timeInAir += Time.deltaTime;
            yield return null;
        } while (!charController.isGrounded && charController.collisionFlags != CollisionFlags.Above);

        charController.slopeLimit = 45.0f;

        isJumping = false;

    }


    //CROUCH


    public enum CharacterStance { Stand, Crouch, Prone };
    private CharacterStance currentStance = CharacterStance.Stand;

    private void CrouchInput()
    {
        if (Input.GetKeyDown(crouchKey))
        {
            if (currentStance != CharacterStance.Crouch)
            {
                currentStance = CharacterStance.Crouch;
            }
            else
            {
                currentStance = CharacterStance.Stand;
            }

            updateHeight();
        }
    }

    private void ProneInput()
    {
        if (Input.GetKeyDown(proneKey))
        {
            if (currentStance != CharacterStance.Prone)
            {
                currentStance = CharacterStance.Prone;
            }
            else
            {
                currentStance = CharacterStance.Stand;
            }

            updateHeight();
        }
    }

    private void updateHeight()
    {
        switch (currentStance)
        {
            case CharacterStance.Stand:
                charController.height = 2.0f;
                isCrouching = false;
                isProne = false;
                isStanding = true;
                break;
            case CharacterStance.Crouch:
                charController.height = 1.5f;
                isCrouching = true;
                isRunning = false;
                isStanding = false;
                break;
            case CharacterStance.Prone:
                charController.height = 1.0f;
                isProne = true;
                isRunning = false;
                isStanding = false;
                break;
        }
    }


}

If you dont instantiate a variable, it gets instantiated with its default value, which is “false” for booleans. So your “isStanding” is false, until something in your script sets it to true. Since you require “isStanding” to be true, in order to run, something must first happen which makes this condition true - and the only thing doing so is updateHeight(), which only gets called after you crouch or prone.

Instead of having tons of different booleans for jumping, couching, running, standing, … and so on, i would suggest you take a look at state machines. This makes it easier to visualize and implement what can actually be done in certain situations (game “states”). So, for example, the player jumps. This enters your “Jump” gamestate. Inside of jumping (depending on your game mechanics), nothing is possible, other than falling down and eventually entering the “Standing” state again. From Standing you can either Walk, Jump or Crouch and so on. From Walking you can Run. When Running, you can only go back to Walking and from there to Standing. And so on. This makes it a lot easier to design what happens in what states, and where you can go from there. It has the added benefit of making it way easier to add new actions, since now you only need to insert it at the right position and handle the logics from there, instead of adjusting every corner case for the other states based on their booleans again.
An example for how this would look on paper would be this (not mine):

In the above example, Diving is only possibly when you are jumping (thus in the air), while Ducking is only possible while standing, not for example when Jumping or Diving.
Your game states could be numbers, but for more convenient approaches, will most likely be enums. So you’d have an enum “GameState”, which contains the values “Standing”, “Walking”, “Running”, … and so on.
States are supposed to be exclusive, so make sure you can only be in one at a time!

1 Like

So, should i incorporate jumping, running and the different movement speeds of the character into the CharacterStance part of the script? I tired with the different movement speeds but it didn’t seem to work.

Just to make sure; the problem you opened this thread for is that you instantiate your variable as false, when it should rather be true, like i described at the beginning of my post. The rest is just advices on design.

And yes, instead of CharacterStance you’d probably want to name it CharacterState, and then include things like Jumping, Running and so on. Then your Update() method is basically a huge switch-case over the currently active states, where each state handles what happens in it (ie falling when jumping, moving forward when moving and so on), and also the conditional transition to (allowed) next states, based on the intended functionality (statemachine).

That way you separate the code more nicely and dont have to deal with tons of corner cases anymore, that may happen when you are… for example crouching and then jumping. Or maybe that’s something that’s allowed, which can rather easily be adjusted with a couple lines of code then.

1 Like

I put isStanding = true; in private void Awake(), so it works now. But i might reconstruct the script in the way you suggested (statemachine), thanks for the help and the advise. :smile: