Review my Character Controller script, please.

This is my second successful attempt at making a movement script with Character Controller. Only problem at this point is that the jumps can go rather jittery. Especially when you’re moving.

And I get the feeling it could’ve been much less cluttered if I knew how to.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player_Motor : MonoBehaviour
{
    public CharacterController playerControl;

    public float playerSpeed = 0.2f;
    public float playerSprint = 0.4f;
    public float playerDuck = 0.1f;
    public float playerJumpForce = 1f;
    public float playerGravity = -0.5f;

    public float playerJumpTimer = 0.07f;

    public bool playerJumping = false;

    //Current state of some of the stats defined above.
    private float actPlayerGravity;
    private float actPlayerSpeed;

    private bool groundCheckOn = true;

    private Vector3 playerMotionVector;



    private void ApplyGravity()
    {
        playerControl.Move(new Vector3(0, actPlayerGravity, 0));
    }

    private void GetInput()
    {
        //Get input from WASD keys.
        playerMotionVector = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));


        //LeftCtrl to sprint. LeftShift to crouch.
        if (Input.GetAxis("Sprint") != 0)
        {
            actPlayerSpeed = playerSprint;
        }
        if (Input.GetAxis("Duck") != 0)
        {
            actPlayerSpeed = playerDuck;
        }
        if (Input.GetAxis("Sprint") == 0 && Input.GetAxis("Duck") == 0)
        {
            actPlayerSpeed = playerSpeed;
        }
    }

    private void Movement()
    {
        playerControl.Move(playerMotionVector * actPlayerSpeed);


        if (Input.GetAxis("Jump") != 0 && playerJumping == false)
        {
            StartCoroutine("JumpMove");
        }
    }

    private IEnumerator JumpMove()
    {
        //This is probably more complicated than it needs to be... But I didn't know how else to make it jump in a smooth arc.
        groundCheckOn = false;
        playerJumping = true;
        actPlayerGravity = playerJumpForce;
        yield return new WaitForSeconds(playerJumpTimer);
        actPlayerGravity = playerJumpForce / 2;
        yield return new WaitForSeconds(playerJumpTimer);
        actPlayerGravity = 0;
        yield return new WaitForSeconds(playerJumpTimer);
        actPlayerGravity = playerGravity / 2;
        yield return new WaitForSeconds(playerJumpTimer);
        actPlayerGravity = playerGravity;
        groundCheckOn = true;
    }

    private IEnumerator GroundCheck()
    {
        //I need a variable to check if I can check if I can jump. Lol.
        if (groundCheckOn == true)
        {
            if (playerControl.isGrounded)
            {
                yield return new WaitForSeconds(playerJumpTimer * 2);
                playerJumping = false;
                yield break;
            }
            if (!playerControl.isGrounded)
            {
                playerJumping = true;
                yield break;
            }
        }
    }

    private void Start()
    {
        actPlayerGravity = playerGravity;
        actPlayerSpeed = playerSpeed;
    }

    private void Update()
    {
        ApplyGravity();
        GetInput();
        StartCoroutine("GroundCheck");
        Movement();
    }
}

This is not clutter not anything to worry about. Using a decent IDE is best like visual studio (it’s not overwhelming once learned) and can collapse all your code and allow good visualisation of it.

This is not something you should focus on. The code looks fine. The focus is: does it work fine?

Well, like I said, the jumps can sometimes look jittery. Like the character is running up an invisible staircase.

Is there anything I can do to fix that? Other than that, it seems to work just fine.

That sounds like it could be a physics issue. Does your character have a rigidbody attached or not attached?

I already use visual studio. I didn’t have too much trouble with it.

And while I’m glad to hear that this isn’t as bad as I think, I’m still amused by the need for a boolean to check if I’m on the ground, and then another boolean to check if I can keep checking the first boolean.

No rigidbody. Just CharacterController.Move and a constant downward force for gravity(that changes to an upward force during the jump.)

Hmm, you could try multiplying by Time.deltaTime in your movement function like this:

  • playerControl.Move(playerMotionVector * actPlayerSpeed * Time.deltaTime);

You would have to adjust the player speed accordingly. That might account for the time inbetween frames.

Another options is to actually experiment with attaching a rigidbody to your character and maybe try setting the rigidbody to interpolate – but adding a rigidbody opens up a host of other things that would have to be accounted for. I’d try simply multiplying by Time.deltaTime first.

Thank you. That does seem to help. Although I don’t think it’s completely gone, and I just noticed a new problem. The height of the jump seems to vary quite a bit.

I tried multiplying by Time.deltaTime in the coroutines as well, I tried switching from Update to FixedUpdate. Neither fixed it. Maybe I should try what you said with the rigidbody.

Ok, ya, I think your JumpMove() function might be the problem. It is, as you commented probably way more “hackish” and unnecessary than it needs to be. Although if you want to go that route, by controlling how the character actually arcs manually – I think you need to insert some “lerp” behavior into your code. Where you linearly interpolate your gravity and/or time. The way you’re doing it now would seem to fit with the choppy behavior.

Does your game allow for simply letting the physics system do it’s thing, or do you have to control it manually like you’re trying to do in your current code? You could even try setting the current gravity during the jump as a ratio of your playerJumpTimer, and update that every update in the Update() function.

They way you’re doing it now is a lot harder than it probably needs to be lol.

I’m not sure what you’re talking about with my game not allowing physics. I’m just trying to figure out how to use the character controller.

How would I make actPlayerGravity a ratio of playerJumpTimer?

Well now we’re getting into controlling the arc of a physics jump, which honestly is pretty difficult. I may not be able to help you there unfortunately, sorry.

Do you need to adjust the gravity during the jump at all though? Can you just apply the force by itself and let Unity’s default physics do what it does?

For example, just do:

  • private JumpMove()

  • {

  • //This is probably more complicated than it needs to be… But I didn’t know how else to make it jump in a smooth arc.

  • groundCheckOn = false;

  • playerJumping = true;

  • actPlayerGravity = playerJumpForce;

  • groundCheckOn = true;

  • }

With no waiting for seconds and then abruptly adjusting the gravity at certain intervals.

I tried your code and my character flew off like Superman. : P

I need the gravity to change back to normal at the end of the jump. And without the waitforseconds timer inbetween, the jump doesn’t do anything. I got it down to only one waitforseconds timer, but that made it even more jagged.

And I’m still not sure what you mean by default physics and applying the force on it’s own. The only upward force I’m applying IS the inverted gravity. Do you mean the default gravity applied when using SimpleMove?

Okay, based on what you said, I went for a much simpler route and made this.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player_Motor : MonoBehaviour
{
    public CharacterController playerControl;

    public float playerSpeed = 50f;
    public float playerSprint = 100f;
    public float playerDuck = 25f;
    public float playerJumpForce = 3f;


    private float actPlayerSpeed;

    private Vector3 playerMotionVector;


    private void GetInput()
    {
        //Get input from WASD keys.
        playerMotionVector = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));


        //LeftCtrl to sprint. LeftShift to crouch.
        if (Input.GetAxis("Sprint") != 0)
        {
            actPlayerSpeed = playerSprint;
        }
        if (Input.GetAxis("Duck") != 0)
        {
            actPlayerSpeed = playerDuck;
        }
        if (Input.GetAxis("Sprint") == 0 && Input.GetAxis("Duck") == 0)
        {
            actPlayerSpeed = playerSpeed;
        }
    }

    private void Movement()
    {
        playerControl.SimpleMove(playerMotionVector * actPlayerSpeed * Time.deltaTime);


        if (Input.GetAxis("Jump") != 0 && playerControl.isGrounded)
        {
            playerControl.Move(new Vector3(0, playerJumpForce, 0));
        }
    }

    private void Start()
    {
        actPlayerSpeed = playerSpeed;
    }

    private void Update()
    {
        GetInput();
        Movement();
    }
}

Except… Now the player doesn’t really jump. He just teleports upward.

This works, but I’m not sure why.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player_Motor : MonoBehaviour
{
    public CharacterController playerControl;

    public float playerSpeed = 100f;
    public float playerSprint = 200f;
    public float playerDuck = 25f;
    public float playerJumpForce = 15f;


    private float actPlayerSpeed;

    private Vector3 playerMotionVector;


    private void GetInput()
    {
        //Get input from WASD keys.
        playerMotionVector = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));


        //LeftCtrl to sprint. LeftShift to crouch.
        if (Input.GetAxis("Sprint") != 0)
        {
            actPlayerSpeed = playerSprint;
        }
        if (Input.GetAxis("Duck") != 0)
        {
            actPlayerSpeed = playerDuck;
        }
        if (Input.GetAxis("Sprint") == 0 && Input.GetAxis("Duck") == 0)
        {
            actPlayerSpeed = playerSpeed;
        }
    }

    private void Movement()
    {
        playerControl.SimpleMove(playerMotionVector * actPlayerSpeed * Time.deltaTime);


        if (Input.GetAxis("Jump") != 0)
        {
            playerControl.Move(new Vector3(0, playerJumpForce, 0) * Time.deltaTime);
        }
    }

    private void Start()
    {
        actPlayerSpeed = playerSpeed;
    }

    private void FixedUpdate()
    {
        GetInput();
        Movement();
    }
}

I just took the isGrounded variable off of the Jump function, and… I guess the thing does that part on it’s own and trying to implement it again messed it up somehow?

So you got it to work then? I actually had to implement a jumping mechanic into a game I’m working on yesterday, and you’re right, it’s a bit tricky. If it’s working for you that’s great. :slight_smile:

Well, it’s kinda working. I just don’t like how the player has to hold down the jump button to get a full jump. If they let go in the middle of the jump arc, they fall like a rock.

Maybe if I get the force to apply until the player is grounded again…

Okay, this almost works perfectly. Two issues. One, the player jumps as soon as the level starts. Two, the player jumps any time they fall off of something.

private void Movement()
    {
        if (Input.GetAxis("Jump") != 0 || !playerControl.isGrounded)
        {
            playerControl.Move(new Vector3(0, playerJumpForce, 0) * Time.deltaTime);
            playerControl.SimpleMove(playerMotionVector * actPlayerSpeed * Time.deltaTime);
        }
        else
        {
            playerControl.SimpleMove(playerMotionVector * actPlayerSpeed * Time.deltaTime);
        }
    }

And this one worked perfect. I changed the movement during the jump so you can change your direction and momentum in midair, like so many video game characters can do somehow.

Also I made it so that you can’t sprint in midair, and halved the movement speed in midair. Both easily changed if you want.

Side note: There have been other changes made to accommodate other features. I’m just focusing on the jumping here.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player_Motor : MonoBehaviour
{
    public CharacterController playerControl;

    public float playerSpeed = 250f;
    public float playerSprint = 500f;
    public float playerDuck = 25f;
    public float playerJumpForce = 25f;

    public float playerRotateSpeed = 1f;


    private float actPlayerSpeed;

    private Vector3 playerMotionInput;
    private Vector3 playerMotionVector;

    private bool jumping = false;

    public CameraController cameraController;
   

    private void GetInput()
    {
        //Get input from WASD keys.
        playerMotionInput = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
        playerMotionVector = transform.TransformDirection(playerMotionInput);


        //LeftCtrl to sprint. LeftShift to crouch.
        if (Input.GetAxis("Sprint") != 0 && playerControl.isGrounded)
        {
            actPlayerSpeed = playerSprint;
        }
        else
        {
            actPlayerSpeed = playerSpeed;
        }
        //if (Input.GetAxis("Duck") != 0)
        //{
        //    actPlayerSpeed = playerDuck;
        //}
        //if (Input.GetAxis("Sprint") == 0 && Input.GetAxis("Duck") == 0)
        //{
        //    actPlayerSpeed = playerSpeed;
        //}
    }

    private void Movement()
    {
        if (jumping)
        {
            playerControl.Move(new Vector3(playerMotionVector.x * (actPlayerSpeed / 2) * Time.deltaTime, playerJumpForce, playerMotionVector.z * (actPlayerSpeed / 2) * Time.deltaTime) * Time.deltaTime);
            playerControl.SimpleMove(Vector3.zero * actPlayerSpeed * Time.deltaTime);
        }
        else
        {
        playerControl.SimpleMove(playerMotionVector * actPlayerSpeed * Time.deltaTime);
        }
    }

    private void Rotate()
    {
        if (Input.GetAxis("Duck") == 0)
        {
            transform.Rotate(0, Input.GetAxisRaw("Mouse X") * (cameraController.mouseSensitivity * 5) * Time.deltaTime, 0);
        }
       
    }

    private void Start()
    {
        actPlayerSpeed = playerSpeed;
    }

    private void FixedUpdate()
    {
        if (Input.GetAxis("Jump") != 0 && playerControl.isGrounded)
        {
            jumping = true;
        }
        else if (playerControl.isGrounded)
        {
            jumping = false;
        }
        GetInput();
        Movement();
        Rotate();
    }
}

Free to use, if anyone wants it.