I am trying to get it so when the player is moving backwards the player object does not rotate to be the same as the movement vector but to have the facing vector opposite of the movement vector. See image below (circled in yellow):
Currently this is what I have for my player movement:
using UnityEngine;
public class PlayerControls : MonoBehaviour
{
[SerializeField]
private Transform _Camera;
private CharacterController controller;
private float verticalVelocity;
private float groundedTimer; // to allow jumping when going down ramps
private float playerSpeed = 5f;
private float jumpHeight = 1.0f;
private float gravityValue = 9.81f;
private float turnSmoothTime = 0.1f;
private float turnSmoothVelocity;
private Vector3 moveDir;
private Quaternion rotation;
private void Start()
{
// always add a controller
controller = gameObject.AddComponent<CharacterController>();
moveDir = new Vector3();
}
void Update()
{
bool groundedPlayer = controller.isGrounded;
if (groundedPlayer)
{
// cooldown interval to allow reliable jumping even whem coming down ramps
groundedTimer = 0.2f;
}
if (groundedTimer > 0)
{
groundedTimer -= Time.deltaTime;
}
// slam into the ground
if (groundedPlayer && verticalVelocity < 0)
{
// hit ground
verticalVelocity = 0f;
}
// apply gravity always, to let us track down ramps properly
verticalVelocity -= gravityValue * Time.deltaTime;
// gather lateral input control
Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
// scale by speed
move *= playerSpeed;
// Rotates the player based on input and camera via Quaterion;
float cameraAngle = _Camera.eulerAngles.y;
float playerMovementAngle = Mathf.Atan2(move.x, move.z) * Mathf.Rad2Deg;
float angle = playerMovementAngle + cameraAngle;
rotation = Quaternion.Euler(0f, angle, 0f);
// only align to motion if we are providing enough input
if (move.magnitude > 0.05f)
{
//Move player in the direction of the current rotation
moveDir = rotation * Vector3.forward;
move = moveDir;
}
// allow jump as long as the player is on the ground
if (Input.GetButtonDown("Jump"))
{
// must have been grounded recently to allow jump
if (groundedTimer > 0)
{
// no more until we recontact ground
groundedTimer = 0;
// Physics dynamics formula for calculating jump up velocity bas/Ded on height and gravity
verticalVelocity += Mathf.Sqrt(jumpHeight * 2 * gravityValue);
}
}
// inject Y velocity before we use it
move.y = verticalVelocity;
// call .Move() once only
controller.Move(move * Time.deltaTime * playerSpeed);
}
}
You want to use the dot product (gotten via Vector2.Dot() ) to find out if your player’s forward vector and the proposed move vector are facing the same hemisphere.
Dot returns positive numbers if the two vectors ARE in the same hemisphere, or negative if they are not, eg they are reverse.
So that means…
IF you have a valid nonzero input:
– get the dot product
– perform the movement
---- if positive, face the player the way the movement is
---- if negative, face the player the opposite way the movement is
I think I achieved this as a quick test in my Character controller here:
Magic sauce is lines 65 to line 83… see the comment for what it was
versus how it does what I list above.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// Originally from Unity examples at:
// https://docs.unity3d.com/ScriptReference/CharacterController.Move.html
//
// 3:55 PM 10/3/2020
//
// Reworked by @kurtdekker so that it jumps reliably in modern Unity versions.
//
// To use:
// - make your player shape about 1x2x1 in size
// - put this script on the root of it
//
// That's it.
public class UnityExampleCharMover : MonoBehaviour
{
private CharacterController controller;
private float verticalVelocity;
private float groundedTimer; // to allow jumping when going down ramps
private float playerSpeed = 2.0f;
private float jumpHeight = 1.0f;
private float gravityValue = 9.81f;
private void Start()
{
// always add a controller
controller = gameObject.AddComponent<CharacterController>();
}
void Update()
{
bool groundedPlayer = controller.isGrounded;
if (groundedPlayer)
{
// cooldown interval to allow reliable jumping even whem coming down ramps
groundedTimer = 0.2f;
}
if (groundedTimer > 0)
{
groundedTimer -= Time.deltaTime;
}
// slam into the ground
if (groundedPlayer && verticalVelocity < 0)
{
// hit ground
verticalVelocity = 0f;
}
// apply gravity always, to let us track down ramps properly
verticalVelocity -= gravityValue * Time.deltaTime;
// gather lateral input control
Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
// scale by speed
move *= playerSpeed;
// only align to motion if we are providing enough input
if (move.magnitude > 0.15f)
{
// this was formerly simply:
// gameObject.transform.forward = move;
// to align us to movement.
//
// 9:10 PM 6/15/2022 - special hack to let player walk backwards
//
Vector3 fwd = transform.forward;
// use the 0.5f through arccos for the 30-degree demarcation
// angle past which we will consider you moving backwards.
if (Vector3.Dot( move.normalized, fwd) < -0.5f)
{
// walking backwards
gameObject.transform.forward = -move;
}
else
{
gameObject.transform.forward = move;
}
}
// allow jump as long as the player is on the ground
// (Change this to Input.GetButton("Jump") if you do not
// want the user to need to re-press Jump each time.
// Doing so will allow you to hold the Jump button.)
if (Input.GetButtonDown("Jump"))
{
// must have been grounded recently to allow jump
if (groundedTimer > 0)
{
// no more until we recontact ground
groundedTimer = 0;
// Physics dynamics formula for calculating jump up velocity based on height and gravity
verticalVelocity += Mathf.Sqrt(jumpHeight * 2 * gravityValue);
}
}
// inject Y velocity before we use it
move.y = verticalVelocity;
// call .Move() once only
controller.Move(move * Time.deltaTime);
}
}[/code\]
No. You should still use Vector3.Dot. You where are already given example code for 3d character controller. Also I don’t see in your code any part that would actually change rotation of player. I assume that is done by different script.
Few minor unrelated issues that you might want to fix:
First multiplication by player speed is useless since you later overwrite it with normalized vector
Second multiplication by playerSpeed happens after adding vertical velocity. Which means that you will multiply falling speed by playerSpeed which is nonsense and mess up jump hight calculation.
Calculating player angle from vector just to turn it back to vector are unnecessary convoluted way of doing that. You can just rotate input vector by camera angle to achieve camera relative movement.
I realized when I uploaded it did not have the line transform.rotation = rotation; which is what currently turns the player based on where the camera is.
Just in case you did not realise, this line in Kurt’s code does rotate your player: gameObject.transform.forward = move;. Setting the forward axis is equivalent to