3rd Person strafing and moving

Hey everyone, I’m working on a simple character controller but I’m running into a small issue, trying to make my character strafe properly. I’m working with state machines for the controller which is a much easier system for what I am trying to do, but also splits the code more than I’m used to. Here’s what I have so far:

override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        m_Movement.Set(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
        m_Camera.Set(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
        Vector3 direction = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"));
        if (m_Movement.sqrMagnitude >= 0.1)
        {
            characterController.transform.rotation = Quaternion.Euler(m_Camera.x, cam.eulerAngles.y, m_Camera.y);
            characterController.Move(animator.transform.forward * speed * Time.deltaTime);
        }
        if (Input.GetButtonDown("Fire1"))
        {
            characterController.transform.rotation = Quaternion.Euler(m_Camera.x, cam.eulerAngles.y, m_Camera.y);
        }

right now it works just fine, I can rotate the camera separately from the player and when I press the forward button my character spins to face the direction the camera is facing, then moves forward. The problem I have right now is that it doesn’t matter which direction I press (forward, backward, left, right) I still move forward. I need to make it so my player can strafe left/right compared to its local forward direction, and backwards based on the camera direction.

I know I could make 4 separate if statements for each direction, but I know that is a horribly inefficient way to run the controller. Does anyone know how to set this up within just the one Update function? I feel like it should only be a line or two long but I can’t find the answer online anywhere.

In line 5 you gather up your motion inputs and that is in world space cordinates.

All you need to do is multiply it by the heading of your player to make it be local to the way your player faces.

Assuming that the +Z (blue arrow) direction on the transform is your player, this would be what you would do immediately after line 5:

direction = characterController.transform.rotation * direction;  // convert to local space

Now I believe it should do what you want.

interesting!

I put it in my code like this:

Vector3 direction = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"));
        direction = characterController.transform.rotation * direction;

I am assuming this is how you were suggestion to put the lines in. I still seem to be moving the same as before. I still can’t strafe, but can move forward in relation to the camera direction

Oooh, I just noticed line 3… you are getting the inputs in multiple places, not just line 5… Naughty Naughty! :slight_smile:

Seriously though, you should:

  • gather horiz / vert input into local float variables in one step (first).

  • Put them into a Vector3 and do the rotation I noted above.

  • now use the components of that now-rotated direction in the 2 places you’re using them

Always centralize and collapse your data to the fewest number of paths.

The main reason for this is lets say in the future you want to add another source of input… now you have 2 (or more) places to add it in, leading to lots of error potential.

Haha yeah I’m still learning the best practices of coding. Learning a lot, but still a lot to learn.

Yes that did end up working for me!

override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 direction = new Vector3(horizontal, 0f, vertical);
        direction = characterController.transform.rotation * direction;

        m_Movement.Set(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
        m_Camera.Set(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));

        if (m_Movement.sqrMagnitude >= 0.1)
        {
            characterController.transform.rotation = Quaternion.Euler(m_Camera.x, cam.eulerAngles.y, m_Camera.y);
            characterController.Move(direction * speed * Time.deltaTime);
        }

this is what I am using right now. Hoping it is all I need, might have to fiddle with it but at least for right now it is working exactly as I need! Thanks for all the help

1 Like
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        float horizontal = Input.GetAxisRaw("Horizontal");
        float vertical = Input.GetAxisRaw("Vertical");
        Vector3 direction = new Vector3(horizontal, 0f, vertical);
        direction = characterController.transform.rotation * direction;
        m_Camera.Set(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
        if (direction.sqrMagnitude >= 0.1)
        {
            characterController.transform.rotation = Quaternion.Euler(m_Camera.x, cam.eulerAngles.y, m_Camera.y);
            characterController.Move(direction * speed * Time.deltaTime);
        }

with a little trimming I got it down to here, and it is working quite nicely!

1 Like

That is awesome. Glad you’re up and running again.

Always good to clean up code… and what I see above seems nice and tidy.

I highly recommend not going any further with trimming however: you want to do one thing per line for clarity, and in case you have a bug somewhere. A lotta people get carried away cramming as many things into one line as possible, thinking somehow that it will make the code faster (it won’t) and then they end up with hairy code and an exception and the code actually prevents them from fixing it because it is impossible to reason about where the error is.

That’s an understandable error, it takes a little getting used to keeping things organized the way coding likes it to be organized. I feels like more lines should be bad, but more lines it seems is usually good.

if you’ve got time, I added in some code to jump, and it mostly works! All with one tiny problem where if I am already moving I can not jump, but if I am stationary I can jump (and move in the air which I like) Feels like something that is a small, quick fix but I’m not exactly sure where it would be. If you can spare some help that would be awesome!

override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        float horizontal = Input.GetAxisRaw("Horizontal");
        animator.SetFloat("InputHorizontal", horizontal);
        float vertical = Input.GetAxisRaw("Vertical");
        animator.SetFloat("InputVertical", vertical);

        Vector3 direction = new Vector3(horizontal, 0f, vertical);
        direction = characterController.transform.rotation * direction;

        if (direction.sqrMagnitude >= 0.1)
        {
            characterController.transform.rotation = Quaternion.Euler(0, cam.eulerAngles.y, 0);
            characterController.Move(direction * speed * Time.deltaTime);
        }
        if (characterController.isGrounded)
        {
            if (Input.GetButtonDown("Jump"))
            {
                velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
            }
        }
        else
        {
            velocity.y += gravity * Time.deltaTime;
        }
        characterController.Move(velocity * Time.deltaTime);
    }

I am unfamiliar with the isGrounded property or what it should do.

Perhaps insert a Debug.Log() line right before line 16 to see if you are grounded when you think you are.

Also, lots of other Debug.Log() statements could tell you if line 20 is even running at all, as well as the values inside of velocity when they reach line 27, where you care about them.

The instant you have a problem, your first instinct should be to sprinkle LOTS of logging around… always. Most problems will immediately yield to this approach, and all problems ultimately will yield to this.

I’ll try that out with Debug.Log. I have found that if I am on uneven terrain I can sometimes jump while moving, and if standing still I can jump. Feels like the isGrounded check is either running at the wrong time, or wrong place, or doesn’t work as well as I thought it did.

Yeah its definitly the isGrounded check (the Debug.Log helped greatly!)

when I am moving in any direction the isGrounded becomes false, even if I am moving on a flat plane.

I predict Google will give you replies for that.

Yeah I may have to look around for that one. I added a bool to check for is grounded that I can watch while the game plays, its flipping between true and false super fast. So something is up.

Thanks for all the help though!