How to calculate players movement based on it's rotation?

Hi everybody.
I’m making a small game as part of the Junior Programmer pathway with a camera that is fixed above the player and it is not rotating.
I’ve made my character to look in the direction of the cursor. I’ve also made it move around the level, using world space (so no matter where my character looks - pressing down will always make the character go down).
I’ve downloaded the human model with some animations and I’ve configured them to move smoothly based on my horizontal/vertical input. And it all works well as long as my character looks forward. But when I rotate the character - it starts moonwalking (which is kinda funny, but that’s not what I’m looking for).

public class PlayerController : MonoBehaviour
{
    public float movementSpeed = 1;
    private Animator animator;
    // Start is called before the first frame update
    void Start()
    {
        animator = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        LookAtMouse();
        MovePlayer();
        //if(Input.GetKeyDown(KeyCode.Mouse0))
        //    Attack();
    }

    // gets input on axis and applies to players coordinates
    void MovePlayer()
    {
        //get input
        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        //apply input to move player
        transform.Translate(Vector3.forward * movementSpeed * verticalInput * Time.deltaTime, Space.World);
        transform.Translate(Vector3.right * movementSpeed * horizontalInput * Time.deltaTime, Space.World);
        //animation based on movement
        if (verticalInput != 0 || horizontalInput != 0)
        {
            animator.SetBool("Moving", true);
            animator.SetFloat("Horizontal", horizontalInput);
            animator.SetFloat("Vertical", verticalInput);
        }
        else if (verticalInput == 0 && horizontalInput == 0)
            animator.SetBool("Moving", false);
    }

    void LookAtMouse()
    {
        //creating a ray and detecting where it hits something
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        int layerMask = 1 << 3;//3rd layer is set to be ground
        if (Physics.Raycast(ray, out hit, 1000, layerMask)) //check if there is a spot to look at on the ground
        {
            Vector3 positionToLookAt = hit.point;//get the spot to look at
            transform.LookAt(positionToLookAt);//look at the spot
            transform.rotation = Quaternion.Euler(0, transform.rotation.eulerAngles.y, 0);//remove other rotations, so the player only rotates on the Y axis
        }
    }

As you can see at the lines 34 and 35 - I’m passing my input as parameters to my animator. But that input is always in the world space. I need to somehow calculate how much Horizontal and Vertical movement I should pass to the animator, so that the animations are played in correlation to the player’s rotation. How can I do that?
Or maybe there is a better way to play those animations, that I’m not aware of?
Thanks!

See lines 28 and 29?

See how you use Vector3.forward and Vector3.right?

Replace those with transform.forward and transform.right

It’s pretty spiffy-easy…

If you need to feed the same to the animator params, just assign each X/Z to a variable and use it.

But that would make my character simply walk in the direction of my cursor. Which would just make controls complicated for the player, since if the player would look down and would like to go down, they would need to press Up (since camera rotation is fixed), which is confusing…

I’m actually confused now about precisely what you’re seeing.

I suspect there is something more to your animation setup than I imagine.

You can actually print out (and stub out intentionally with a little test piece of code) the values you are sending into the animator.

I think you might want to do this, try sending (1,1) and seeing what animation it is doing. Then retry with (1, -1), etc.

Here’s my usual debugging notes:

Time to start debugging! Here is how you can begin your exciting new debugging adventures:

You must find a way to get the information you need in order to reason about what the problem is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

What is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is
  • you’re getting an error or warning and you haven’t noticed it in the console window

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the names of the GameObjects or Components involved?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as Debug.Log("Problem!",this);

If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

Visit Google for how to see console output from builds. If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer for iOS: How To - Capturing Device Logs on iOS or this answer for Android: How To - Capturing Device Logs on Android

If you are working in VR, it might be useful to make your on onscreen log output, or integrate one from the asset store, so you can see what is happening as you operate your software.

Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

If your problem is with OnCollision-type functions, print the name of what is passed in!

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

“When in doubt, print it out!™” - Kurt Dekker (and many others)

Note: the print() function is an alias for Debug.Log() provided by the MonoBehaviour class.

I’ll try to explain it once more… If this won’t help - I’ll try to record a video tomorrow (I’m away from my PC already).
Right. So…

  1. My camera is set to follow the player, but it never rotates. So whenever a player moves, the player would always stay at the center of the camera, but the camera will never rotate. That way that camera’s X axis is always aligned with world’s X axis.
  2. My character is always moving within the world space. Whenever I press Up - it goes forward, whenever I press right - it goes right. No matter the player rotation. That is the way I wanted it, so it would give the player some agility.
  3. My character always looks in the direction, of my cursor (so that it can attack and move in different directions). For example I can move my cursor below my character and press Up: this way the character would look down, but would still go up.
  4. I’m passing my input as parameters to my animator. So whenever I press Up, it starts playing “run forward” animation which is ok as long as character’s rotation is aligned with world’s rotation… But if I look down and press Up, the character looks down, goes up and plays the animation of going forward. And since character’s forward is not the same as world’s forward (where I’m actually moving) - it plays the animation as if player was walking down, when it actually moves up.

I hope I’ve explained it well enough and haven’t confused you even further…
And thanks for trying to help.

Another example:
If I look to the right and press Up the character would move in the world’s forward direction (which is simply up the screen), look to the world’s right and play the animation of going forward. And since forward for the player in this case is a world’s right: it looks like the player is moving to the right, when it actually moves forward…

Try this:

using UnityEngine;
   
public class PlayerController : MonoBehaviour
{
    public float movementSpeed = 1;
    private Animator animator;

    void Start()
    {
        animator = GetComponent<Animator>();
    }
   
    void Update()
    {
        MovePlayer();
    }
   
    void MovePlayer()
    {
        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        Vector3 worldDir=new Vector3(horizontalInput,0,verticalInput)*movementSpeed;
        transform.position+=worldDir*Time.deltaTime;
       
        Vector3 localDir=transform.InverseTransformDirection(worldDir.normalized);

        if (verticalInput != 0 || horizontalInput != 0)
        {
            animator.SetBool("Moving", true);
            animator.SetFloat("Horizontal", localDir.x);
            animator.SetFloat("Vertical", localDir.z);
        }
        else if (verticalInput == 0 && horizontalInput == 0)
            animator.SetBool("Moving", false);
       
        Vector3 direction=Input.mousePosition-new Vector3(Screen.width/2,Screen.height/2,0);
        transform.LookAt(transform.position+new Vector3(direction.x,0,direction.y));
    }
}
1 Like

Thank you!
Lines 23-32 are working great! That’s exactly what I’ve been looking for!

I’ve also noticed what you did to replace the LookAtMouse function. A neat idea, but it does not actually work for me. My camera is positioned under a certain angle, rather then straight above the player looking straight down, so using your code gives a bit bad precision, when aiming at some angles.

With that being said - your solution with animation helped me a lot. Thank you very much!

1 Like