I need to rewrite my entire character motor script!

I’m working on a character motor that includes drifting, acceleration, and deceleration and at some point I basically botched the entire thing as far as extensibility is concerned. Here’s the old script, which is a bit of a mess, but I think I’ve named everything well enough that it should be readable.

using UnityEngine;
using System.Collections;
using Rewired;

[RequireComponent(typeof(CharacterController))]
public class DrifterMotor : MonoBehaviour {

    public int playerID = 0;

    public float topSpeed = 10f;
    public float driftLength = 3f;
    public float turnSpeed = 60f;
    public float driftTurnSpeed = 120f;
    public float acceleration = 10f;
    public MovementStatus movementStatus;

    private Vector3 driftVelocity;
    private Vector3 driftVelocityInternal;
    private Vector3 velocity;
    private float verticalSpeed;
    private float driftLengthInternal;
    private float currentSpeed;

    private bool cancelDrift = false;

    private Player player;
    private CharacterController controller;

    private float moveVertical;
    private float moveHorizontal;

    public enum MovementStatus {
        driving,
        drifting,
        driftLock
    }

    void Start () {
        driftLengthInternal = driftLength;
        driftVelocityInternal = new Vector3 (0, 0, 0);
        player = ReInput.players.GetPlayer(playerID);
        controller = GetComponent<CharacterController> ();
    }

    float remap(float value, float low1, float high1, float low2, float high2) {
        return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
    }

    void Countdown() {
            driftLength -= Time.deltaTime;
        if (driftLength < 0f) {
            cancelDrift = true;
            driftLength = driftLengthInternal;
        }
    }

    void DriftMovement() {
        float value = remap (driftLength, 0f, driftLengthInternal, 1f, 0f);
        Vector3 newDirection = transform.forward * topSpeed * moveVertical;
        driftVelocity = Vector3.Lerp (driftVelocityInternal, newDirection, value);
        Countdown ();
        controller.Move (driftVelocity * Time.deltaTime);
        Vector2 velocityMagnitude = new Vector2 (driftVelocity.x, driftVelocity.z);
        currentSpeed = velocityMagnitude.magnitude;
    }

    void NormalMovement () {

        if (player.GetAxis ("MoveVertical") > 0) {
            currentSpeed += acceleration * Time.deltaTime * player.GetAxis ("MoveVertical");
        } else if (player.GetAxis ("MoveVertical") < 0) {
            currentSpeed -= acceleration * Time.deltaTime * player.GetAxis ("MoveVertical");
        } else if (player.GetAxis ("MoveVertical") == 0) {
            currentSpeed = 0f;
        }

        if (currentSpeed > topSpeed) {
            currentSpeed = topSpeed;
        }

        driftVelocityInternal = transform.forward * currentSpeed * player.GetAxis ("MoveVertical");
        controller.Move (transform.forward * currentSpeed * player.GetAxis ("MoveVertical") * Time.deltaTime);
    }

    void ApplyGravity() {
        verticalSpeed += Physics.gravity.y * Time.deltaTime;
        velocity.y = verticalSpeed;
        driftVelocity.y = verticalSpeed;
    }

    void Update () {
        moveVertical = player.GetAxis ("MoveVertical");
        moveHorizontal = player.GetAxis ("MoveHorizontal");

        if (movementStatus == MovementStatus.driving) {
            driftLength = driftLengthInternal;
        }

        if (player.GetButton ("Drift") && cancelDrift == false) {
            transform.Rotate(0, moveHorizontal * driftTurnSpeed * Time.deltaTime, 0);
            movementStatus = MovementStatus.drifting;
        } else {
            cancelDrift = false;
            transform.Rotate(0, moveHorizontal * turnSpeed * Time.deltaTime, 0);
            movementStatus = MovementStatus.driving;
        }

        ApplyGravity ();
        if (movementStatus == MovementStatus.driving) {
            NormalMovement ();
        } else {
            DriftMovement ();
        }

        controller.Move (velocity * Time.deltaTime);
    }
}

As you can probably see, when the player holds down the drift button, they enter drift mode, which allows them to turn faster for a limited amount of time (3 seconds) but also causes them to drift in the velocity they were previously travelling. However, I’m encountering a few problems here.

  • The player doesn’t stop drifting when the countdown timer reaches zero. Instead, the timer just resets. I should be able to figure out how to fix this, but I can’t.
  • When coming out of the drift at certain angles (basically anything where they haven’t come to a complete stop or facing roughly the same or opposite direction as they’re drifting in), the movement is sharp and sudden. I’d prefer having acceleration applied to this, but I can’t figure out how to do that, even though I have acceleration code in there. I think I need to find the velocity they’re traveling relative to the forward direction, but I have no idea how to do that. Vector math confuses the hell out of me, unfortunately.
  • I’d like it if it took some time to come to a complete stop, instead of stopping on a dime like it does now. I have no idea how to do this even though it should be the simplest thing in the world.

Here’s what I have now for version 2 of the script, though it’s woefully incomplete (although better commented):

using UnityEngine;
using System.Collections;
using Rewired;

[RequireComponent(typeof(CharacterController))]
public class DrifterMotor2 : MonoBehaviour {

    //Rewired Input Manager Variables
    public int playerID = 0;
    private Player player;
    private CharacterController controller;

    private float moveVertical;
    private float moveHorizontal;

    //Movement Variables
    [Tooltip("Player maximum speed")]
    public float maxSpeed = 10f;

    [Tooltip("How long the player can drift for in seconds")]
    public float driftLength = 3f;

    [Tooltip("How fast the player turns while driving")]
    public float turnSpeed = 60f;

    [Tooltip("How fast the player turns while drifting")]
    public float driftTurnSpeed = 120f;

    [Tooltip("How fast the player accelerates")]
    public float acceleration = 10f;

    [Tooltip("How fast the player decelerates")]
    public float deceleration = 3f;

    [Tooltip("Current movement state")]
    public MovementState movementState;
    public enum MovementState {
        driving,
        drifting,
    }

    //Private Movement Variables
    private Vector3 driftVelocity;
    private Vector3 driftVelocityInternal;
    private Vector3 velocity;
    private float verticalSpeed;
    private float driftLengthInternal;
    private float currentSpeed;

    //Used To Handle Ending Drift Status
    private bool disableDrift = false;

    //Simple Value Remap
    float remap(float value, float low1, float high1, float low2, float high2) {
        return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
    }

    //Initialisation
    void Start () {
        player = ReInput.players.GetPlayer(playerID);
        controller = GetComponent<CharacterController> ();
        driftLengthInternal = driftLength;
    }

    //Apply Gravity To Character Controller
    void ApplyGravity() {
        verticalSpeed += Physics.gravity.y * Time.deltaTime;
        velocity.y = verticalSpeed;
        driftVelocity.y = verticalSpeed;
    }

    //Countdown Timer
    void Countdown() {
        driftLength -= Time.deltaTime;
        if (driftLength < 0f) {
            disableDrift = true;
            driftLength = driftLengthInternal;
        }
    }

    //Calculate Movement While Drifting
    void DriftMovement() {
        //Remap Timer To 0 - 1
        float value = remap (driftLength, 0f, driftLengthInternal, 1f, 0f);
        Vector3 newDirection = transform.forward * maxSpeed * moveVertical;
        driftVelocity = Vector3.Lerp (driftVelocityInternal, newDirection, value);
        Countdown ();
        controller.Move (driftVelocity * Time.deltaTime);
        Vector2 velocityMagnitude = new Vector2 (driftVelocity.x, driftVelocity.z);
        currentSpeed = velocityMagnitude.magnitude;
    }

    //Calculate Normal Movement
    void NormalMovement() {
       
        //Set The Internal Drift Velocity
        driftVelocityInternal = transform.forward * currentSpeed * player.GetAxis ("MoveVertical");
    }

    void Update () {
        moveVertical = player.GetAxis ("MoveVertical");
        moveHorizontal = player.GetAxis ("MoveHorizontal");

        if (controller.isGrounded) {
            verticalSpeed = 0f;
            NormalMovement ();
        }

        ApplyGravity ();
        controller.Move (velocity * Time.deltaTime);
    }
}

Any help pointing me in the right direction to implement these features would be greatly appreciated.

Bump for the (morning/afternoon/whatever the heck it is I just woke up) crew

void NormalMovement() {
        if (moveVertical > 0) {
            currentSpeed += acceleration * Time.deltaTime * moveVertical;
            internalVelocity = transform.forward * currentSpeed * moveVertical;
        } else if (moveVertical < 0) {
            currentSpeed -= acceleration * Time.deltaTime * moveVertical;
            internalVelocity = transform.forward * currentSpeed * moveVertical;
        }

        //Put Decelleration Code In Here
        if (player.GetAxisRawTimeInactive ("MoveVertical") > 0) {
            if (player.GetAxisPrev ("MoveVertical") > 0) {
                decelerationState = DecelerationState.deceleratingForward;
            } else if (player.GetAxisPrev ("MoveVertical") < 0) {
                decelerationState = DecelerationState.deceleratingReverse;
            }

            if (decelerationState == DecelerationState.deceleratingForward) {
                currentSpeed -= deceleration * Time.deltaTime;
                if (currentSpeed < 0) {
                    currentSpeed = 0;
                }
                internalVelocity = transform.forward * currentSpeed;
            } else if (decelerationState == DecelerationState.deceleratingReverse) {
                currentSpeed += deceleration * Time.deltaTime;
                if (currentSpeed > 0) {
                    currentSpeed = 0;
                }
                internalVelocity = transform.forward * currentSpeed;
            }

        }

        if (currentSpeed > maxSpeed) {
            currentSpeed = maxSpeed;
        }

        //Set The Internal Drift Velocity
        driftVelocityInternal = transform.forward * currentSpeed * moveVertical;

        //Get On Up And Move Your Butt
        controller.Move (internalVelocity * Time.deltaTime);
    }

Okay, so I’ve made my new movement code and deceleration kinda works, in that it works perfectly when you’re decelerating while moving forward, but backward it still stops on a dime. What am I doing wrong here? The DecelerationState properly switches so I know it must be in here somewhere:

else if (decelerationState == DecelerationState.deceleratingReverse) {
                currentSpeed += deceleration * Time.deltaTime;
                if (currentSpeed > 0) {
                    currentSpeed = 0;
                }
                internalVelocity = transform.forward * currentSpeed;
            }

But for the life of me, I can’t figure out what’s going wrong here.

Here’s the entire code, for reference’s sake:

using UnityEngine;
using System.Collections;
using Rewired;

[RequireComponent(typeof(CharacterController))]
public class DrifterMotor2 : MonoBehaviour {

    //Rewired Input Manager Variables
    public int playerID = 0;
    private Player player;
    private CharacterController controller;

    private float moveVertical;
    private float moveHorizontal;

    //Movement Variables
    [Tooltip("Player maximum speed")]
    public float maxSpeed = 10f;

    [Tooltip("How long the player can drift for in seconds")]
    public float driftLength = 3f;

    [Tooltip("How fast the player turns while driving")]
    public float turnSpeed = 60f;

    [Tooltip("How fast the player turns while drifting")]
    public float driftTurnSpeed = 120f;

    [Tooltip("How fast the player accelerates")]
    public float acceleration = 10f;

    [Tooltip("How fast the player decelerates")]
    public float deceleration = 10f;

    [Tooltip("Current movement state")]
    public MovementState movementState;
    public enum MovementState {
        driving,
        drifting
    }

    [Tooltip("Current deceleration state")]
    public DecelerationState decelerationState;
    public enum DecelerationState {
        deceleratingForward,
        deceleratingReverse
    };

    //Private Movement Variables
    private Vector3 driftVelocity;
    private Vector3 driftVelocityInternal;
    private Vector3 velocity;
    private Vector3 internalVelocity;
    private float verticalSpeed;
    private float driftLengthInternal;
    private float currentSpeed;
    private float stopEpsilon = 0.05f;

    //Used To Handle Ending Drift Status
    private bool disableDrift = false;

    //Simple Value Remap
    float remap(float value, float low1, float high1, float low2, float high2) {
        return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
    }

    //Initialisation
    void Start () {
        player = ReInput.players.GetPlayer(playerID);
        controller = GetComponent<CharacterController> ();
        driftLengthInternal = driftLength;
    }

    //Apply Gravity To Character Controller
    void ApplyGravity() {
        verticalSpeed += Physics.gravity.y * Time.deltaTime;
        velocity.y = verticalSpeed;
        driftVelocity.y = verticalSpeed;
    }

    //Countdown Timer
    void Countdown() {
        driftLength -= Time.deltaTime;
        if (driftLength < 0f) {
            disableDrift = true;
            driftLength = driftLengthInternal;
        }
    }

    //Calculate Movement While Drifting
    void DriftMovement() {
        //Remap Timer To 0 - 1
        float value = remap (driftLength, 0f, driftLengthInternal, 1f, 0f);
        Vector3 newDirection = transform.forward * maxSpeed * moveVertical;
        driftVelocity = Vector3.Lerp (driftVelocityInternal, newDirection, value);
        Countdown ();
        controller.Move (driftVelocity * Time.deltaTime);
        Vector2 velocityMagnitude = new Vector2 (driftVelocity.x, driftVelocity.z);
        currentSpeed = velocityMagnitude.magnitude;
    }

    //Calculate Normal Movement
    void NormalMovement() {
        if (moveVertical > 0) {
            currentSpeed += acceleration * Time.deltaTime * moveVertical;
            internalVelocity = transform.forward * currentSpeed * moveVertical;
        } else if (moveVertical < 0) {
            currentSpeed -= acceleration * Time.deltaTime * moveVertical;
            internalVelocity = transform.forward * currentSpeed * moveVertical;
        }

        //Put Decelleration Code In Here
        if (player.GetAxisRawTimeInactive ("MoveVertical") > 0) {
            if (player.GetAxisPrev ("MoveVertical") > 0) {
                decelerationState = DecelerationState.deceleratingForward;
            } else if (player.GetAxisPrev ("MoveVertical") < 0) {
                decelerationState = DecelerationState.deceleratingReverse;
            }

            if (decelerationState == DecelerationState.deceleratingForward) {
                currentSpeed -= deceleration * Time.deltaTime;
                if (currentSpeed < 0) {
                    currentSpeed = 0;
                }
                internalVelocity = transform.forward * currentSpeed;
            } else if (decelerationState == DecelerationState.deceleratingReverse) {
                currentSpeed += deceleration * Time.deltaTime;
                if (currentSpeed > 0) {
                    currentSpeed = 0;
                }
                internalVelocity = transform.forward * currentSpeed;
            }

        }

        if (currentSpeed > maxSpeed) {
            currentSpeed = maxSpeed;
        }

        //Set The Internal Drift Velocity
        driftVelocityInternal = transform.forward * currentSpeed * moveVertical;

        //Get On Up And Move Your Butt
        controller.Move (internalVelocity * Time.deltaTime);
    }

    void Update () {
        moveVertical = player.GetAxis ("MoveVertical");
        moveHorizontal = player.GetAxis ("MoveHorizontal");

        if (controller.isGrounded) {
            NormalMovement ();

            verticalSpeed = 0f;
        } else {
            ApplyGravity ();
        }

        controller.Move (velocity * Time.deltaTime);
    }
}

This probably isn’t exactly what you want, but I imagine a good way to do this is by setting your direction then rotating the velocity to match direction. It would be the same code for both modes, just that the modes would use different values like for how far direction can deviate from velocity or how fast or far velocity will change to match direction. I would be liable to use fifty different animation curves for this, so that you get different rates at different speeds.

I’ve been working on something similar, just simpler, with sliding as a breaking action before the character takes off in the opposite direction.

Not to sound incredibly lazy but it seems like that’d lead to a lot of extra work for what I’m trying to accomplish, which is pretty much a simple accelerate/decelerate, not-drifting/drifting system. Hell, even the setup I’ve got now seems like it’s far too complex for what I’m trying to do, I just can’t really figure out how to make it simpler.

Taking some of RockoDyne’s advice kiiinda, I rolled my drifting code and movement code into one using a Vector3.lerp, I’ve managed to mostly replicate my old code in a considerably more readable format. I still need to figure out how to implement deceleration and drift lockout, but now it’s just as functional while having room for expansion.

using UnityEngine;
using System.Collections;
using Rewired;

[RequireComponent(typeof(CharacterController))]
public class DrifterMotor2 : MonoBehaviour {

    //Rewired Input Manager Variables
    public int playerID = 0;
    private Player player;
    private CharacterController controller;

    private float moveVertical;
    private float moveHorizontal;

    //Movement Variables
    [Tooltip("Player maximum speed")]
    public float maxSpeed = 10f;

    [Tooltip("How long the player can drift for in seconds")]
    public float driftLength = 3f;

    [Tooltip("How fast the player turns while driving")]
    public float turnSpeed = 60f;

    [Tooltip("How fast the player turns while drifting")]
    public float driftTurnSpeed = 120f;

    [Tooltip("How fast the player accelerates")]
    public float acceleration = 10f;

    [Tooltip("How fast the player decelerates")]
    public float deceleration = 10f;

    [Tooltip("How much momentum should be preserved")]
    [Range(0.0f, 1.0f)]
    public float momentumPreservation = 0;

    [Tooltip("Current movement state")]
    public MovementState movementState;
    public enum MovementState {
        driving,
        drifting,
        driftCancelled
    }

    //Private Movement Variables
    private Vector3 driftVelocityInternal;
    private Vector3 velocity;
    private Vector3 internalVelocity;
    private float verticalSpeed;
    private float driftLengthInternal;
    private float currentSpeed;
    private float turnSpeedInternal;
    private bool cancelDrift = false;

    //Simple Value Remap
    float remap(float value, float low1, float high1, float low2, float high2) {
        return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
    }

    //Initialisation
    void Start () {
        player = ReInput.players.GetPlayer(playerID);
        controller = GetComponent<CharacterController> ();

        driftLengthInternal = driftLength;
        turnSpeedInternal = turnSpeed;
        driftLengthInternal = driftLength;
    }

    //Apply Gravity To Character Controller
    void ApplyGravity() {
        verticalSpeed += Physics.gravity.y * Time.deltaTime;
        velocity.y = verticalSpeed;
    }

    //Countdown Timer
    void Countdown() {
        driftLength -= Time.deltaTime;
        if (driftLength < 0f) {
            driftLength = driftLengthInternal;
            cancelDrift = true;
        }
    }

    //Calculate  Movement
    void Movement() {
        transform.Rotate (0, moveHorizontal * turnSpeedInternal * Time.deltaTime, 0);

        if (player.GetButton ("Drift")) {
            movementState = MovementState.drifting;
        } else {
            movementState = MovementState.driving;
        }

        if (player.GetButtonUp ("Drift")) {
            driftLength = driftLengthInternal;
        }

        if (movementState == MovementState.drifting) {
            Countdown ();
            float value = remap (driftLength, 0f, driftLengthInternal, 0f, 1f);
            momentumPreservation = value;
            turnSpeedInternal = driftTurnSpeed;
        } else if (movementState == MovementState.driving) {
            turnSpeedInternal = turnSpeed;
        }

        currentSpeed += acceleration * Time.deltaTime;
        if (currentSpeed > maxSpeed) {
            currentSpeed = maxSpeed;
        }

        if (player.GetAxisTimeInactive("MoveVertical") > 0) {
            currentSpeed = 0;
        }

        if (player.GetAxisRawPrev("MoveVertical") != player.GetAxisRaw("MoveVertical")){
            currentSpeed = 0;
        }

        Vector3 newDirection = transform.forward * currentSpeed * moveVertical;
        internalVelocity = Vector3.Lerp (newDirection, driftVelocityInternal, momentumPreservation);

        controller.Move (internalVelocity * Time.deltaTime);
    }

    void Update () {
        moveVertical = player.GetAxis ("MoveVertical");
        moveHorizontal = player.GetAxis ("MoveHorizontal");

        if (movementState == MovementState.driving) {
            driftVelocityInternal = moveVertical * transform.forward * currentSpeed;
        }

        Movement ();

        if (controller.isGrounded) {
            verticalSpeed = 0f;
        } else {
            ApplyGravity ();
        }

        controller.Move (velocity * Time.deltaTime);
    }
}

edit: Duh, well that was simple:

if (player.GetButtonUp ("Drift")) {
            cancelDrift = false;
            driftLength = driftLengthInternal;
        }

        if (movementState == MovementState.drifting) {
            float value = remap (driftLength, 0f, driftLengthInternal, 0f, 1f);
            momentumPreservation = value;
            Countdown ();
            turnSpeedInternal = driftTurnSpeed;
        } else if (movementState == MovementState.driving) {
            turnSpeedInternal = turnSpeed;
        }

        if (player.GetButton ("Drift") && cancelDrift == false) {
            movementState = MovementState.drifting;
        } else {
            movementState = MovementState.driving;
        }

It’s a little glitchy coming out of the drift, but I’m sure I can manage that somehow.

So I’ve fixed the glitchiness coming out of drifts and made it so that the speed coming out of drifts is a bit more smooth, but I still have no idea how to implement the deceleration when the player lets go of/lowers the vertical axis. I don’t suppose anyone has any ideas?

using UnityEngine;
using System.Collections;
using Rewired;

[RequireComponent(typeof(CharacterController))]
public class DrifterMotor2 : MonoBehaviour {

    //Rewired Input Manager Variables
    public int playerID = 0;
    private Player player;
    private CharacterController controller;

    private float moveVertical;
    private float moveHorizontal;

    //Movement Variables
    [Tooltip("Player maximum speed")]
    public float maxSpeed = 10f;

    [Tooltip("How long the player can drift for in seconds")]
    public float driftLength = 3f;

    [Tooltip("How fast the player turns while driving")]
    public float turnSpeed = 60f;

    [Tooltip("How fast the player turns while drifting")]
    public float driftTurnSpeed = 120f;

    [Tooltip("How fast the player accelerates")]
    public float acceleration = 1f;

    [Tooltip("How fast the player decelerates")]
    public float deceleration = 1f;

    [Tooltip("How much momentum should be preserved")]
    [Range(0.0f, 1.0f)]
    public float momentumPreservation = 0;

    [Tooltip("Current movement state")]
    public MovementState movementState;
    public enum MovementState {
        driving,
        drifting
    }

    //Private Movement Variables
    private Vector3 driftVelocityInternal;
    private Vector3 velocity;
    private Vector3 internalVelocity;
    private Vector3 newDirection;
    private Vector2 velocityMagnitude;
    private float verticalSpeed = 0;
    private float currentSpeed = 0;
    private float driftLengthInternal;
    private float accelerationInternal;
    private float decelerationInternal;
    private float turnSpeedInternal;
    private bool cancelDrift = false;

    //Simple Value Remap
    float remap(float value, float low1, float high1, float low2, float high2) {
        return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
    }

    //Initialisation
    void Start () {
        player = ReInput.players.GetPlayer(playerID);
        controller = GetComponent<CharacterController> ();

        driftLengthInternal = driftLength;
        turnSpeedInternal = turnSpeed;
        driftLengthInternal = driftLength;
        accelerationInternal = acceleration;
        decelerationInternal = deceleration;
    }

    //Apply Gravity To Character Controller
    void ApplyGravity() {
        verticalSpeed += Physics.gravity.y * Time.deltaTime;
        velocity.y = verticalSpeed;
    }

    //Countdown Timer
    void DriftTimer() {
        driftLength -= Time.deltaTime;
        if (driftLength < 0f) {
            cancelDrift = true;
            driftLength = driftLengthInternal;
        }
    }

    void Drifting (){
        float value = remap (driftLength, 0f, driftLengthInternal, 0f, 1f);
        momentumPreservation = value;
        DriftTimer ();
        turnSpeedInternal = driftTurnSpeed;
        velocityMagnitude = new Vector2 (internalVelocity.x, internalVelocity.z);
        currentSpeed = velocityMagnitude.magnitude;
    }

    //Calculate  Movement
    void Movement() {
        transform.Rotate (0, moveHorizontal * turnSpeedInternal * Time.deltaTime, 0);
        newDirection = transform.forward * currentSpeed * moveVertical;
        internalVelocity = Vector3.Lerp (newDirection, driftVelocityInternal, momentumPreservation);

        if (player.GetButtonUp ("Drift")) {
            cancelDrift = false;
            driftLength = driftLengthInternal;
        }

        if (movementState == MovementState.drifting) {
            Drifting ();
        } else if (movementState == MovementState.driving) {
            turnSpeedInternal = turnSpeed;
        }

        if (player.GetButton ("Drift") && cancelDrift == false) {
            movementState = MovementState.drifting;
        } else {
            movementState = MovementState.driving;
        }

        currentSpeed += acceleration * Time.deltaTime;
        if (currentSpeed > maxSpeed) {
            currentSpeed = maxSpeed;
        }

        if (player.GetAxisRawPrev("MoveVertical") != player.GetAxisRaw("MoveVertical")) {
            currentSpeed = 0;
        } else if (player.GetAxisRawTimeInactive("MoveVertical") > 0) {
            currentSpeed = 0;
        }

        controller.Move (internalVelocity * Time.deltaTime);
    }

    void Update () {
        moveVertical = player.GetAxis ("MoveVertical");
        moveHorizontal = player.GetAxis ("MoveHorizontal");

        if (movementState == MovementState.driving) {
            driftVelocityInternal = moveVertical * transform.forward * currentSpeed;
        }

        Movement ();

        if (controller.isGrounded) {
            verticalSpeed = 0f;
        } else {
            ApplyGravity ();
        }

        controller.Move (velocity * Time.deltaTime);
    }
}

I’ve done some more refactoring and I nearly have everything working, though now I have a couple new problems.

  • When I attempt to decelerate while traveling backwards, the controller instead lurches forward and decelerates in that direction instead of decelerating in reverse.
  • Sometimes pressing back instead causes the controller to accelerate forwards. I’m not sure what’s causing this exactly. Same with the above.

Here’s the new code, for reference:

using UnityEngine;
using System.Collections;
using Rewired;

[RequireComponent(typeof(CharacterController))]
public class DrifterMotor3 : MonoBehaviour {

    //Rewired Input Manager Variables
    public int playerID = 0;
    private Player player;
    private CharacterController controller;

    private float moveVertical;
    private float moveHorizontal;

    //Movement Variables
    [Tooltip("Player maximum speed")]
    public float maxSpeed = 10f;

    [Tooltip("How long the player can drift for in seconds")]
    public float driftLength = 3f;

    [Tooltip("How fast the player turns while driving")]
    public float turnSpeed = 60f;

    [Tooltip("How fast the player turns while drifting")]
    public float driftTurnSpeed = 120f;

    [Tooltip("How fast the player accelerates")]
    public float acceleration = 1f;

    [Tooltip("How fast the player decelerates")]
    public float deceleration = 1f;

    [Tooltip("How much momentum should be preserved")]
    [Range(0.0f, 1.0f)]
    public float momentumPreservation = 0;

    [Tooltip("Current movement state")]
    public MovementState movementState;
    public enum MovementState {
        driving,
        drifting
    }

    [Tooltip("Current acceleration state")]
    public AccelerationState accelerationState;
    public enum AccelerationState {
        accelerating,
        decelerating,
        stopped
    }

    //Private Movement Variables
    private Vector3 driftVelocityInternal;
    private Vector3 velocity;
    private Vector3 internalVelocity;
    private Vector3 newDirection;
    private Vector2 velocityMagnitude;
    private float verticalSpeed = 0;
    private float currentSpeed = 0;
    private float driftLengthInternal;
    private float turnSpeedInternal;
    private bool cancelDrift = false;

    //Simple Value Remap
    float remap(float value, float low1, float high1, float low2, float high2) {
        return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
    }

    //Initialisation
    void Start () {
        player = ReInput.players.GetPlayer(playerID);
        controller = GetComponent<CharacterController> ();

        driftLengthInternal = driftLength;
        turnSpeedInternal = turnSpeed;
    }

    //Apply Gravity To Character Controller
    void ApplyGravity() {
        verticalSpeed += Physics.gravity.y * Time.deltaTime;
        velocity.y = verticalSpeed;
    }

    //Countdown Timer
    void DriftTimer() {
        driftLength -= Time.deltaTime;
        if (driftLength < 0f) {
            cancelDrift = true;
            driftLength = driftLengthInternal;
        }
    }

    void Drifting () {
        float value = remap (driftLength, 0f, driftLengthInternal, 0f, 1f);
        momentumPreservation = value;
        DriftTimer ();
        turnSpeedInternal = driftTurnSpeed;
        velocityMagnitude = new Vector2 (internalVelocity.x, internalVelocity.z);
        currentSpeed = velocityMagnitude.magnitude;
    }

    void DriftControl () {
        if (player.GetButtonUp ("Drift")) {
            cancelDrift = false;
            driftLength = driftLengthInternal;
        }

        if (movementState == MovementState.drifting) {
            Drifting ();
        } else if (movementState == MovementState.driving) {
            turnSpeedInternal = turnSpeed;
        }

        if (player.GetButton ("Drift") && cancelDrift == false) {
            movementState = MovementState.drifting;
        } else {
            movementState = MovementState.driving;
        }
    }

    //Calculate  Movement
    void Movement() {
        if (player.GetAxisRawTimeActive("MoveVertical") > 0) {
            accelerationState = AccelerationState.accelerating;
        } else if (player.GetAxisRawTimeInactive("MoveVertical") > 0) {
            accelerationState = AccelerationState.decelerating;
        }

        if (accelerationState == AccelerationState.accelerating) {
            transform.Rotate (0, moveHorizontal * turnSpeedInternal * Time.deltaTime, 0);

            currentSpeed += acceleration * Time.deltaTime;

            if (currentSpeed > maxSpeed) {
                currentSpeed = maxSpeed;
            }

            newDirection = transform.forward * currentSpeed * moveVertical;
            internalVelocity = Vector3.Lerp (newDirection, driftVelocityInternal, momentumPreservation);

            DriftControl ();

        } else if (accelerationState == AccelerationState.decelerating) {
            transform.Rotate (0, moveHorizontal * turnSpeedInternal * Time.deltaTime, 0);

            currentSpeed -= deceleration * Time.deltaTime;

            if (currentSpeed < 0) {
                currentSpeed = 0;
                accelerationState = AccelerationState.stopped;
            }

            newDirection = transform.forward * currentSpeed;
            internalVelocity = Vector3.Lerp (newDirection, driftVelocityInternal, momentumPreservation);

            DriftControl ();
        } else if (accelerationState == AccelerationState.stopped) {
            transform.Rotate (0, moveHorizontal * turnSpeedInternal * Time.deltaTime, 0);

            currentSpeed = 0;
        }

        controller.Move (internalVelocity * Time.deltaTime);
    }

    void Update () {
        moveVertical = player.GetAxis ("MoveVertical");
        moveHorizontal = player.GetAxis ("MoveHorizontal");

        if (movementState == MovementState.driving) {
            driftVelocityInternal = transform.forward * currentSpeed;
        }

        Movement ();

        if (controller.isGrounded) {
            verticalSpeed = 0f;
        } else {
            ApplyGravity ();
        }

        controller.Move (velocity * Time.deltaTime);
        Debug.Log (currentSpeed);
    }
}

edit: Fixed the reverse deceleration problem, now I just need to fix drifting in reverse and the weird backwards making you go forward thing.

using UnityEngine;
using System.Collections;
using Rewired;

[RequireComponent(typeof(CharacterController))]
public class DrifterMotor3 : MonoBehaviour {

    //Rewired Input Manager Variables
    public int playerID = 0;
    private Player player;
    private CharacterController controller;

    private float moveVertical;
    private float moveHorizontal;

    //Movement Variables
    [Tooltip("Player maximum speed")]
    public float maxSpeed = 10f;

    [Tooltip("How long the player can drift for in seconds")]
    public float driftLength = 3f;

    [Tooltip("How fast the player turns while driving")]
    public float turnSpeed = 60f;

    [Tooltip("How fast the player turns while drifting")]
    public float driftTurnSpeed = 120f;

    [Tooltip("How fast the player accelerates")]
    public float acceleration = 1f;

    [Tooltip("How fast the player decelerates")]
    public float deceleration = 1f;

    [Tooltip("How much momentum should be preserved")]
    [Range(0.0f, 1.0f)]
    public float momentumPreservation = 0;

    [Tooltip("Current movement state")]
    public MovementState movementState;
    public enum MovementState {
        driving,
        drifting
    }

    [Tooltip("Current acceleration state")]
    public AccelerationState accelerationState;
    public enum AccelerationState {
        accelerating,
        decelerating,
        stopped
    }

    //Private Movement Variables
    private Vector3 driftVelocityInternal;
    private Vector3 velocity;
    private Vector3 internalVelocity;
    private Vector3 newDirection;
    private Vector2 velocityMagnitude;
    private float verticalSpeed = 0;
    private float currentSpeed = 0;
    private float driftLengthInternal;
    private float turnSpeedInternal;
    private float forwardDirection;
    private bool cancelDrift = false;

    //Simple Value Remap
    float remap(float value, float low1, float high1, float low2, float high2) {
        return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
    }

    //Initialisation
    void Start () {
        player = ReInput.players.GetPlayer(playerID);
        controller = GetComponent<CharacterController> ();

        driftLengthInternal = driftLength;
        turnSpeedInternal = turnSpeed;
    }

    //Apply Gravity To Character Controller
    void ApplyGravity() {
        verticalSpeed += Physics.gravity.y * Time.deltaTime;
        velocity.y = verticalSpeed;
    }

    //Countdown Timer
    void DriftTimer() {
        driftLength -= Time.deltaTime;
        if (driftLength < 0f) {
            cancelDrift = true;
            driftLength = driftLengthInternal;
        }
    }

    void Drifting () {
        float value = remap (driftLength, 0f, driftLengthInternal, 0f, 1f);
        momentumPreservation = value;
        DriftTimer ();
        turnSpeedInternal = driftTurnSpeed;
        velocityMagnitude = new Vector2 (internalVelocity.x, internalVelocity.z);
        currentSpeed = velocityMagnitude.magnitude;
    }

    void DriftControl () {
        if (player.GetButtonUp ("Drift")) {
            cancelDrift = false;
            driftLength = driftLengthInternal;
        }

        if (movementState == MovementState.drifting) {
            Drifting ();
        } else if (movementState == MovementState.driving) {
            turnSpeedInternal = turnSpeed;
        }

        if (player.GetButton ("Drift") && cancelDrift == false) {
            movementState = MovementState.drifting;
        } else {
            movementState = MovementState.driving;
        }
    }

    //Calculate  Movement
    void Movement() {
        if (player.GetAxisRawTimeActive("MoveVertical") > 0) {
            accelerationState = AccelerationState.accelerating;
        } else if (player.GetAxisRawTimeInactive("MoveVertical") > 0) {
            accelerationState = AccelerationState.decelerating;
        }

        DriftControl ();

        if (accelerationState == AccelerationState.accelerating) {
            transform.Rotate (0, moveHorizontal * turnSpeedInternal * Time.deltaTime, 0);

            currentSpeed += acceleration * Time.deltaTime;

            if (currentSpeed > maxSpeed) {
                currentSpeed = maxSpeed;
            }

            if (player.GetAxisRawPrev("MoveVertical") != player.GetAxisRaw("MoveVertical")) {
                currentSpeed = 0;
            }

            newDirection = transform.forward * currentSpeed * moveVertical;
            internalVelocity = Vector3.Lerp (newDirection, driftVelocityInternal, momentumPreservation);
        } else if (accelerationState == AccelerationState.decelerating) {
            transform.Rotate (0, moveHorizontal * turnSpeedInternal * Time.deltaTime, 0);

            currentSpeed -= deceleration * Time.deltaTime;

            if (currentSpeed < 0) {
                currentSpeed = 0;
                accelerationState = AccelerationState.stopped;
            }

            newDirection = (forwardDirection * transform.forward) * currentSpeed;

            internalVelocity = Vector3.Lerp (newDirection, driftVelocityInternal, momentumPreservation);
        } else if (accelerationState == AccelerationState.stopped) {
            transform.Rotate (0, moveHorizontal * turnSpeedInternal * Time.deltaTime, 0);

            currentSpeed = 0;
        }

        controller.Move (internalVelocity * Time.deltaTime);
    }

    void Update () {
        moveVertical = player.GetAxis ("MoveVertical");
        moveHorizontal = player.GetAxis ("MoveHorizontal");

        if (movementState == MovementState.driving) {
            driftVelocityInternal = transform.forward * currentSpeed;
        }

        Movement ();

        if (controller.isGrounded) {
            verticalSpeed = 0f;
        } else {
            ApplyGravity ();
        }

        forwardDirection = Mathf.Sign (transform.InverseTransformDirection (controller.velocity).z);
        controller.Move (velocity * Time.deltaTime);
    }
}

It turns out I was just overcomplicating things and with a bit of work I got everything to work!

using UnityEngine;
using System.Collections;
using Rewired;

[RequireComponent(typeof(CharacterController))]
public class DrifterMotor4 : MonoBehaviour {

    //Rewired Input Manager Variables
    public int playerID = 0;
    private Player player;
    private CharacterController controller;

    private float moveVertical;
    private float moveHorizontal;

    //Movement Variables
    [Tooltip("Player maximum speed")]
    public float maxSpeed = 10f;

    [Tooltip("How long the player can drift for in seconds")]
    public float driftLength = 3f;

    [Tooltip("How fast the player turns while driving")]
    public float turnSpeed = 60f;

    [Tooltip("How fast the player turns while drifting")]
    public float driftTurnSpeed = 120f;

    [Tooltip("How fast the player accelerates")]
    public float acceleration = 1f;

    [Tooltip("How fast the player decelerates")]
    public float deceleration = 1f;

    [Tooltip("How much momentum should be preserved")]
    [Range(0.0f, 1.0f)]
    public float momentumPreservation = 0;

    [Tooltip("Current movement state")]
    public MovementState movementState;
    public enum MovementState {
        driving,
        drifting
    }

    [Tooltip("Current acceleration state")]
    public AccelerationState accelerationState;
    public enum AccelerationState {
        accelerating,
        decelerating,
    }

    //Private Movement Variables
    private Vector3 driftVelocityInternal;
    private Vector3 velocity;
    private Vector3 internalVelocity;
    private Vector3 newDirection;
    private Vector2 velocityMagnitude;
    private float verticalSpeed = 0;
    private float currentSpeed = 0;
    private float driftLengthInternal;
    private float turnSpeedInternal;
    private bool cancelDrift = false;

    //Simple Value Remap
    float remap(float value, float low1, float high1, float low2, float high2) {
        return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
    }

    //Initialisation
    void Start () {
        player = ReInput.players.GetPlayer(playerID);
        controller = GetComponent<CharacterController> ();

        driftLengthInternal = driftLength;
        turnSpeedInternal = turnSpeed;
    }

    //Apply Gravity To Character Controller
    void ApplyGravity() {
        verticalSpeed += Physics.gravity.y * Time.deltaTime;
        velocity.y = verticalSpeed;
    }

    //Countdown Timer
    void DriftTimer() {
        driftLength -= Time.deltaTime;
        if (driftLength < 0f) {
            cancelDrift = true;
            driftLength = driftLengthInternal;
        }
    }

    void Drifting () {
        float value = remap (driftLength, 0f, driftLengthInternal, 0f, 1f);
        momentumPreservation = value;
        DriftTimer ();
        turnSpeedInternal = driftTurnSpeed;
        velocityMagnitude = new Vector2 (internalVelocity.x, internalVelocity.z);
        currentSpeed = velocityMagnitude.magnitude;
    }

    void DriftControl () {
        if (player.GetButtonUp ("Drift")) {
            cancelDrift = false;
            driftLength = driftLengthInternal;
        }

        if (movementState == MovementState.drifting) {
            Drifting ();
        } else if (movementState == MovementState.driving) {
            turnSpeedInternal = turnSpeed;
        }

        if (player.GetButton ("Drift") && cancelDrift == false) {
            movementState = MovementState.drifting;
        } else {
            movementState = MovementState.driving;
        }
    }

    //Calculate  Movement
    void Movement() {
        if (player.GetAxisRawTimeActive("MoveVertical") > 0) {
            accelerationState = AccelerationState.accelerating;
        } else if (player.GetAxisRawTimeInactive("MoveVertical") > 0) {
            accelerationState = AccelerationState.decelerating;
        }

        transform.Rotate (0, moveHorizontal * turnSpeedInternal * Time.deltaTime, 0);

        if (moveVertical > 0) {
            currentSpeed += acceleration * Time.deltaTime;
        } else if (moveVertical < 0) {
            currentSpeed -= acceleration * Time.deltaTime;
        }

        if (moveVertical == 0 && Mathf.Sign(currentSpeed) == 1) {
            currentSpeed -= deceleration * Time.deltaTime;
            currentSpeed = Mathf.Clamp (currentSpeed, 0, maxSpeed);
        } else if (moveVertical == 0 && Mathf.Sign(currentSpeed) == -1) {
            currentSpeed += deceleration * Time.deltaTime;
            currentSpeed = Mathf.Clamp (currentSpeed, -maxSpeed, 0);
        }

        currentSpeed = Mathf.Clamp (currentSpeed, -maxSpeed, maxSpeed);


        newDirection = transform.forward * currentSpeed;
        internalVelocity = Vector3.Lerp (newDirection, driftVelocityInternal, momentumPreservation);
        DriftControl ();
        controller.Move (internalVelocity * Time.deltaTime);
    }

    void Update () {
        moveVertical = player.GetAxis ("MoveVertical");
        moveHorizontal = player.GetAxis ("MoveHorizontal");

        if (movementState == MovementState.driving) {
            driftVelocityInternal = transform.forward * currentSpeed;
        }

        Movement ();

        if (controller.isGrounded) {
            verticalSpeed = 0f;
        } else {
            ApplyGravity ();
        }

        controller.Move (velocity * Time.deltaTime);
    }
}