Is there a better way to handle all of my different player speed variables?

Right now I have speed, walkSpeed, runSpeed, crawlSpeed and vaultSpeed variables for my character controller for my different movements.

Every time I want to either increase or decrease all of my speeds together, I need to manually change the value of each variable. So if I activate a speed boost I do:

if (speedBoostActive)
{
    speed = 10f;
    walkSpeed = 10f;
    sprintSpeed = 10f;
    crawlSpeed = 10f;
    vaultSpeed = 15f;
}
else
{
    speed = 3f;
    walkSpeed = 3f;
    sprintSpeed = 6f;
    crawlSpeed = 2f;
    vaultSpeed = 5f;
}

And I do this at least 5 times for other mechanics like when I am walking up steep slopes and I reduce my players speed, or I step on the opposite of a speed boost (slow area) so that my player speed is also reduced, etc.

I use a ternary condition for another part of my code but I don’t know if I can use that for 5 variables all together. The only other thing I can think of is putting them all together which I have done for a bool:

private bool CanDashForward() => !isSliding && !isCrawling && !isVaulting && !isSwimming;

You could write something like

[System.Serializable]
public class SpeedValues
{
    public float speed = 3f;
    public float walkSpeed = 3f;
    public float sprintSpeed = 6f;
    public float crawlSpeed = 2f;
    public float vaultSpeed = 5f;
}

public class PlayerMove : MonoBehaviour
{
    [SerializeField] SpeedValues defaultValues;

    public float speed = 3f;
    public float walkSpeed = 3f;
    public float sprintSpeed = 6f;
    public float crawlSpeed = 2f;
    public float vaultSpeed = 5f;

    public void Apply(SpeedValues values)
    {
        speed = values.speed;
        walkSpeed = values.walkSpeed;
        sprintSpeed = values.sprintSpeed;
        crawlSpeed = values.crawlSpeed;
        vaultSpeed = values.vaultSpeed;
    }
    public void RevertToDefault()
    {
        Apply(defaultValues);
    }
}

public class SpeedBoost : MonoBehaviour
{
    [SerializeField] PlayerMove playerMove;
    [SerializeField] SpeedValues values;

    public void Activate()
    {
        playerMove.Apply(values);
    }
    public void Deactivate()
    {
        playerMove.RevertToDefault();
    }
}

It’s still pretty much the same, it just avoids code-duplication.

You can set the SpeedValues in the inspector.

Thanks for taking the time to write up that code.

If I have other mechanics that alter my speed but with different values than my speed boost, will I need to create additional classes to handle them on top of the SpeedBoost class that you have created?

If you group all the variables together under one type, then you can add constructors and methods to the type that make it easier to make adjustments to all of the variables in one go.

public sealed struct Speeds
{
    public float normal;
    public float walk;
    public float sprint;
    public float crawl;
    public float vault;
 
    public Speeds(float normal, float walk, float sprint, float crawl, float vault)
    {
        this.normal = normal;
        this.walk = walk;
        this.sprint = sprint;
        this.crawl = crawl;
        this.vault = vault;
    }
 
    public void SetAll(float normal, float walk, float sprint, float crawl, float vault)
    {
        this.normal = normal;
        this.walk = walk;
        this.sprint = sprint;
        this.crawl = crawl;
        this.vault = vault;
    }
 
    public float AdjustAll(float amount)
    {
        normal += amount;
        walk += amount;
        sprint += amount;
        crawl += amount;
        vault += amount;
    }
}

Then you can simplify the code in your original class a lot.

private static readonly Speeds normalSpeeds = new(normal:3f, walk:3f, sprint:6f, crawl:2f, vault:5f);
private static readonly Speeds boostedSpeeds = new(normal:10f, walk:10f, sprint:10f, crawl:10f, vault:15f);
...
speeds = isSpeedBoostActive ? boostedSpeeds : normalSpeeds;

You could also use ScriptableObject assets if you want to configure the speed values using the Inspector and be able to easily tweak them in play mode for all instances simultaneously for testing purposes.

Also it sounds like your code might be a great fit for using the state machine pattern. So you could also go with something more like this:

public class MoveState
{
    public float speed;
 
    public MoveState(float speed) => this.speed = speed;

    public virtual void Enter() { } // can add code for setting animator trigger etc. here
    public virtual void Exit() { }
}

public sealed class MoveStateMachine
{
    private MoveState currentState;
    private readonly MoveState normalState = new(3f);
    private readonly MoveState walkState = new(3f);
    ...

    public void StartWalking() => SetCurrentState(walkState);

    private void SetCurrentState(MoveState state)
    {
        if(currentState == state)
        {
            return;
        }

        currentState?.Exit();
        currentState = state;
        state?.Enter();
    }
}

Commonly you’d have a “base speed” and then multiplicators for the other speeds.

basespeed = 5;
speedboostfactor = 1f; // 100% speed boost
walkspeedfactor = .9f; // 90% of base speed
crouchspeedfactor = 0.75f; // 75% of base speed

var walkspeed = basespeed * walkspeedfactor * speedboostfactor;
var crouchspeed = basespeed * crouchspeedfactor * speedboostfactor;
etc

This has the benefit that you can change the base speed to speed everything up or down all at once. Everything is relative to base speed, which is now the only concrete value, all others are “percentages”.

Not really, you can call it from any class you want, all you need is a reference to PlayerMove and the SpeedValues you want to set. You can have 3 different speed-settings in the same class for example. The “SpeedBoost” was just an example of how to use the other two classes.

Usually that’s correct, but in the OP example, the speeds didn’t scale up by the same amount.