Hi all. I’m new to Unity, C# and programming in general. After spending a couple weeks running through tutorials, I thought I would try to make a small FPS on my own. I use the following function in a mouseLook script attached to the Main Camera to rotate the camera with the mouse:
void Update(){
// Get the direction the player is looking on the X axis
playerLookDirection.x += Input.GetAxis("Mouse X") * mouseSensitivity * (playerLookInvertX ? -1f : 1f) * Time.deltaTime;
// Get the direction the player is looking on the Y axis
playerLookDirection.y += Input.GetAxis("Mouse Y") * mouseSensitivity * (playerLookInvertY ? 1f : -1f) * Time.deltaTime;
// Clamp the camera's Y value so that it cannot look behind the player character
playerLookDirection.y = Mathf.Clamp(playerLookDirection.y, -80f, 70f);
// Move the camera to match the position of the mouse
playerCamera.localRotation = Quaternion.Euler(playerLookDirection.y, playerLookDirection.x, 0f);
// Move the player character's body to match the rotation of the camera
playerBody.localRotation = playerCamera.localRotation;
// Rotate the player character game object to match the camera
//playerCharacter.localRotation = Quaternion.Euler(playerLookDirection.y, playerLookDirection.x, 0f);
}
I can move the player character with this function in a script attached to the same Player object to which the Character Controller is attached:
void PlayerMove(){
// Set the move direction relative to the rotation of the player character
Vector3 playerMoveDirection = (transform.right * Input.GetAxis("Horizontal")) + (transform.forward * Input.GetAxis("Vertical"));
// Move the player in that direction at their speed across delta time
playerCharacterController.Move(playerMoveDirection * playerMoveSpeed * Time.deltaTime);
}
However, this moves the player character relative to the global XYZ, not relative to the direction the camera is looking. I understand that this is because moving the mouse rotates the Main Camera, and I set the rotation of the player character’s model to match; but I do not rotate the actual Player game object to which the character controller is attached.
I “fixed” this by commenting line 10 and uncommenting line 14 in the second script to rotate the entire player character instead of the camera. While this “works” in that I can move around the game world correctly, I can put the character’s head (and thus the camera) through walls by rotating on the X axis. I can also struggle against gravity by looking up and pressing forward. I’m sure this “solution” would also have other unintended side effects as the game gets more complex.
You can solve the “Struggling against gravity” bit by projecting your look direction (in this case, transform.forward) onto the plane of the ground, then normalizing:
void PlayerMove(){
// Set the move direction relative to the rotation of the player character
Vector3 flatForward = Vector3.ProjectOnPlane(transform.forward, Vector3.up);
Vector3 playerMoveDirection = (transform.right * Input.GetAxis("Horizontal")) + (flatForward * Input.GetAxis("Vertical"));
// Move the player in that direction at their speed across delta time
playerCharacterController.Move(playerMoveDirection * playerMoveSpeed * Time.deltaTime);
}
As for tilting the player character causing you to be able to lean through walls, I suggest creating a separate gameobject for the character’s head, and making that object a child of the body. You can put your main camera component on this object. Then you can have the up and down looking affect that child object’s x-axis rotation, and have the y axis roptation affect the body itself. You can do this by having one script that does only y rotation on the body and a separate script for only x rotation on the head. Also, if you do this, you can directly put your current PlayerMove script on the body, and it won’t be affected by the tilting of the head at all, so you won’t have to do the projection stuff I did in the above snippet.
Hi PraetorBlue. Your first solution helped me realize another problem with my PlayerMove function. In my version the player loses speed when looking at the floor because the character tries to move in a direction it cannot. In your version, looking up at the sky has the same effect because the player can no longer travel vertically. Ideally the player could move their legs at the same speed regardless of where their head is looking!
I also tried your second solution I don’t think I figured it out correctly. I’ve been pulling my hair out trying to make a character move for over 12 hours now so I’m taking a break from Unity till tomorrow.
Yep - the solution to this is to take the “flatForward” vector and normalize it. That will give you back a “unit length” vector so it will give the appropriate speed when you multiply by playerMovementSpeed and Time.deltaTime.
I managed to figure this out last week and PraetorBlue’s first solution is what I went with. Instead of rotating just the camera or just the player on both the X and Y axes, rotate the player on its X axis and the child camera on its Y axis. This way what the player game object considers “forward” and what the camera (and thus the player) perceive as “forward” are aligned—literally.
Later I decided to redo my controller using rigid bodies instead of the default Character Controller. While the rotation of player and camera are conceptually the same, you need to keep the following in mind:
Store your Vertical and Horizontal input in a Vector3 with a Y value of 0f. Normalize the vector, then use Vector3.Scale to scale that input vector by the camera’s forward transform (if you’re using the Main Camera, that will be Camera.main.transform.forward).
Use the scaled vector to AddRelativeForce to your rigid body game object.
I hope this helps anyone left in a similar predicament that I was.