'Unity - Make character look at mouse with New input system/Character Controller
Been following this guide https://youtu.be/bXNFxQpp2qk?t=1280 for Character Controller. This got me getting basic movement working and at time 21:20 he creates a way to rotate the player.
https://youtu.be/b0AQg5ZTpac and this video for the player facing the mouse position. At the 7:15 mark she explains how to get the mousePosition in which I stored it in positionToLookAt
I want my character's rotation to be done with the mouse and have a field of view like here: https://youtu.be/rQG9aUWarwE
Been trying to to get it so that the player is facing the direction of the mouse position, but haven't been getting the result. With here showing https://imgur.com/gallery/mPPWogi the video results and my PlayerInputs.
Some peers mentioned replacing
Quaternion targetRotation = Quaternion.LookRotation(positionToLookAt);
by
Quaternion targetRotation = Quaternion.LookRotation(positionToLookAt - transform.position);
But it doesn't work and produced the same results in the imgur.
Can anyone help me out here? I'm stuck and I have no idea how to make this character look at the mouse instead.
here's the snippet of the method
void handleRotation()
{
Vector3 positionToLookAt;
// get mouse position
Vector2 mousePosition = playerInput.CharacterControls.MousePosition.ReadValue<Vector2>();
mousePosition = Camera.main.ScreenToWorldPoint(mousePosition);
// insert mouse position to looking position
positionToLookAt.x = currentMovement.x;
//positionToLookAt.x = mousePosition.x;
positionToLookAt.y = 0.0f;
positionToLookAt.z = currentMovement.z;
//positionToLookAt.z = mousePosition.y;
Quaternion currentRotation = transform.rotation;
if (isMovementPressed)
{
Quaternion targetRotation = Quaternion.LookRotation(positionToLookAt);
transform.rotation = Quaternion.Slerp(currentRotation, targetRotation, rotationFactorPerFrame * Time.deltaTime);
}
}
And if anyone wants my full code, here.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class AnimationAndMovementController : MonoBehaviour
{
PlayerInput playerInput;
UnityEngine.CharacterController characterController;
Animator animator;
int isWalkingHash;
int isRunningHash;
Vector2 currentMovementInput;
Vector3 currentMovement;
Vector3 currentRunMovement;
bool isMovementPressed;
bool isRunPressed;
float rotationFactorPerFrame = 15.0f;
float runMultiplier = 3.0f;
//Debug.Log(context.ReadValue<Vector2>());
void Start()
{
}
void Awake()
{
playerInput = new PlayerInput();
characterController = GetComponent<UnityEngine.CharacterController>();
animator = GetComponent<Animator>();
isWalkingHash = Animator.StringToHash("isWalking");
isRunningHash = Animator.StringToHash("isRunning");
playerInput.CharacterControls.Move.started += onMovementInput;
playerInput.CharacterControls.Move.canceled += onMovementInput;
playerInput.CharacterControls.Move.performed += onMovementInput;
playerInput.CharacterControls.Run.started += onRun;
playerInput.CharacterControls.Run.canceled += onRun;
}
void onRun(InputAction.CallbackContext context)
{
isRunPressed = context.ReadValueAsButton();
}
void onMovementInput (InputAction.CallbackContext context)
{
currentMovementInput = context.ReadValue<Vector2>();
currentMovement.x = currentMovementInput.x;
currentMovement.z = currentMovementInput.y;
currentRunMovement.x = currentMovementInput.x * runMultiplier;
currentRunMovement.z = currentMovementInput.y * runMultiplier;
isMovementPressed = currentMovementInput.x != 0 || currentMovementInput.y != 0;
}
void handleRotation()
{
Vector3 positionToLookAt;
Vector2 mousePosition = playerInput.CharacterControls.MousePosition.ReadValue<Vector2>();
mousePosition = Camera.main.ScreenToWorldPoint(mousePosition);
positionToLookAt.x = currentMovement.x;
//positionToLookAt.x = mousePosition.x;
positionToLookAt.y = 0.0f;
positionToLookAt.z = currentMovement.z;
//positionToLookAt.z = mousePosition.y;
Quaternion currentRotation = transform.rotation;
if (isMovementPressed)
{
Quaternion targetRotation = Quaternion.LookRotation(positionToLookAt);
transform.rotation = Quaternion.Slerp(currentRotation, targetRotation, rotationFactorPerFrame * Time.deltaTime);
}
}
void handleAnimation()
{
bool isWalking = animator.GetBool(isWalkingHash);
bool isRunning = animator.GetBool(isRunningHash);
if (isMovementPressed && !isWalking) {
animator.SetBool(isWalkingHash, true);
}
else if (!isMovementPressed && isWalking){
animator.SetBool(isWalkingHash, false);
}
if ((isMovementPressed && isRunPressed) && !isRunning)
{
animator.SetBool(isRunningHash, true);
}
else if ((!isMovementPressed && !isRunPressed) && isRunning)
{
animator.SetBool(isRunningHash, false);
}
else if ((isMovementPressed && !isRunPressed) && isRunning)
{
animator.SetBool(isRunningHash, false);
}
}
void handleGravity()
{
if (characterController.isGrounded) {
float groundedGravity = -0.05f;
currentMovement.y = groundedGravity;
currentRunMovement.y = groundedGravity;
} else {
float gravity = -9.8f;
currentMovement.y = gravity;
currentRunMovement.y = gravity;
}
}
// Update is called once per frame
void Update()
{
handleRotation();
handleAnimation();
handleGravity();
if (isRunPressed) {
characterController.Move(currentRunMovement * Time.deltaTime);
}
else {
characterController.Move(currentMovement * Time.deltaTime);
}
}
void OnEnable()
{
playerInput.CharacterControls.Enable();
}
void OnDisable()
{
playerInput.CharacterControls.Disable();
}
}
EDIT
Updated full code and handleRotation() is where Rotation is being handled:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class AnimationAndMovementController : MonoBehaviour
{
PlayerInput playerInput;
CharacterController characterController;
Animator animator;
int isWalkingHash;
int isRunningHash;
Camera _camera;
Vector2 currentMovementInput;
Vector3 currentMovement;
Vector3 currentRunMovement;
bool isMovementPressed;
bool isRunPressed;
//float rotationFactorPerFrame = 15.0f;
float runMultiplier = 3.0f;
// Start is called before the first frame update
void Start()
{
_camera = Camera.main;
}
void Awake()
{
playerInput = new PlayerInput();
characterController = GetComponent<CharacterController>();
animator = GetComponent<Animator>();
isWalkingHash = Animator.StringToHash("isWalking");
isRunningHash = Animator.StringToHash("isRunning");
playerInput.CharacterControls.Move.started += onMovementInput;
playerInput.CharacterControls.Move.canceled += onMovementInput;
playerInput.CharacterControls.Move.performed += onMovementInput;
playerInput.CharacterControls.Run.started += onRun;
playerInput.CharacterControls.Run.canceled += onRun;
}
void onRun(InputAction.CallbackContext context)
{
isRunPressed = context.ReadValueAsButton();
}
void onMovementInput(InputAction.CallbackContext context)
{
currentMovementInput = context.ReadValue<Vector2>();
currentMovement.x = currentMovementInput.x;
currentMovement.z = currentMovementInput.y;
currentRunMovement.x = currentMovementInput.x * runMultiplier;
currentRunMovement.z = currentMovementInput.y * runMultiplier;
isMovementPressed = currentMovementInput.x != 0 || currentMovementInput.y != 0;
}
void handleAnimation()
{
bool isWalking = animator.GetBool(isWalkingHash);
bool isRunning = animator.GetBool(isRunningHash);
if (isMovementPressed && !isWalking)
{
animator.SetBool(isWalkingHash, true);
}
else if (!isMovementPressed && isWalking)
{
animator.SetBool(isWalkingHash, false);
}
if ((isMovementPressed && isRunPressed) && !isRunning)
{
animator.SetBool(isRunningHash, true);
}
else if ((!isMovementPressed && !isRunPressed) && isRunning)
{
animator.SetBool(isRunningHash, false);
}
else if ((isMovementPressed && !isRunPressed) && isRunning)
{
animator.SetBool(isRunningHash, false);
}
}
void handleGravity()
{
if (characterController.isGrounded)
{
float groundedGravity = -0.05f;
currentMovement.y = groundedGravity;
currentRunMovement.y = groundedGravity;
}
else
{
float gravity = -9.8f;
currentMovement.y = gravity;
currentRunMovement.y = gravity;
}
}
void handleRotation()
{
// We're getting a Vector2, whereas we will need a Vector3
// Get a z value based on camera, and include it in a Vector3
var mousePosition = playerInput.CharacterControls.MousePosition.ReadValue<Vector2>();
var mousePositionZ = _camera.farClipPlane * .5f;
var mouseViewportPosition = _camera.ScreenToWorldPoint(new Vector3(mousePosition.x, mousePosition.y, mousePositionZ));
// Do the same with the object's position
var positionOnViewport = Camera.main.WorldToViewportPoint(transform.position);
// Get the angle between the points
var angle = AngleBetweenTwoPoints(positionOnViewport, mouseViewportPosition);
// Apply the angle as the rotation of the object
transform.rotation = Quaternion.Euler(new Vector3(0f, -angle, 0f));
}
float AngleBetweenTwoPoints(Vector3 a, Vector3 b)
{
return Mathf.Atan2(b.y - a.y, b.x - a.x) * Mathf.Rad2Deg;
}
// Update is called once per frame
void Update()
{
handleRotation();
handleAnimation();
handleGravity();
if (isRunPressed)
{
characterController.Move(currentRunMovement * Time.deltaTime);
}
else
{
characterController.Move(currentMovement * Time.deltaTime);
}
}
void OnEnable()
{
playerInput.CharacterControls.Enable();
}
void OnDisable()
{
playerInput.CharacterControls.Disable();
}
}
EDIT 2: with current iteration
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class AnimationAndMovementController : MonoBehaviour
{
PlayerInput playerInput;
CharacterController characterController;
Animator animator;
int isWalkingHash;
int isRunningHash;
Camera _camera;
Vector2 currentMovementInput;
Vector3 currentMovement;
Vector3 currentRunMovement;
bool isMovementPressed;
bool isRunPressed;
float rotationFactorPerFrame = 15.0f;
float runMultiplier = 3.0f;
// Start is called before the first frame update
void Start()
{
_camera = Camera.main;
}
void Awake()
{
playerInput = new PlayerInput();
characterController = GetComponent<CharacterController>();
animator = GetComponent<Animator>();
isWalkingHash = Animator.StringToHash("isWalking");
isRunningHash = Animator.StringToHash("isRunning");
playerInput.CharacterControls.Move.started += onMovementInput;
playerInput.CharacterControls.Move.canceled += onMovementInput;
playerInput.CharacterControls.Move.performed += onMovementInput;
playerInput.CharacterControls.Run.started += onRun;
playerInput.CharacterControls.Run.canceled += onRun;
}
void onRun(InputAction.CallbackContext context)
{
isRunPressed = context.ReadValueAsButton();
}
void onMovementInput(InputAction.CallbackContext context)
{
currentMovementInput = context.ReadValue<Vector2>();
currentMovement.x = currentMovementInput.x;
currentMovement.z = currentMovementInput.y;
currentRunMovement.x = currentMovementInput.x * runMultiplier;
currentRunMovement.z = currentMovementInput.y * runMultiplier;
isMovementPressed = currentMovementInput.x != 0 || currentMovementInput.y != 0;
}
void handleAnimation()
{
bool isWalking = animator.GetBool(isWalkingHash);
bool isRunning = animator.GetBool(isRunningHash);
if (isMovementPressed && !isWalking)
{
animator.SetBool(isWalkingHash, true);
}
else if (!isMovementPressed && isWalking)
{
animator.SetBool(isWalkingHash, false);
}
if ((isMovementPressed && isRunPressed) && !isRunning)
{
animator.SetBool(isRunningHash, true);
}
else if ((!isMovementPressed && !isRunPressed) && isRunning)
{
animator.SetBool(isRunningHash, false);
}
else if ((isMovementPressed && !isRunPressed) && isRunning)
{
animator.SetBool(isRunningHash, false);
}
}
void handleGravity()
{
if (characterController.isGrounded)
{
float groundedGravity = -0.05f;
currentMovement.y = groundedGravity;
currentRunMovement.y = groundedGravity;
}
else
{
float gravity = -9.8f;
currentMovement.y = gravity;
currentRunMovement.y = gravity;
}
}
void handle_isRunPressed()
{
if (isRunPressed)
{
characterController.Move(currentRunMovement * Time.deltaTime);
}
else
{
characterController.Move(currentMovement * Time.deltaTime);
}
}
void handleRotation()
{
var mousePosition = playerInput.CharacterControls.MousePosition.ReadValue<Vector2>();
var mousePositionZ = _camera.farClipPlane * .5f;
var mouseWorldPosition = _camera.ScreenToWorldPoint(new Vector3(mousePosition.x, mousePosition.y, mousePositionZ)); // _camera.ScreenToViewportPoint(mousePosition);
// Get the angle between the points
// Use the x and z from the object/mouse, since we're looking along the y axis
var angle = AngleBetweenTwoPoints(new Vector2(transform.position.x, transform.position.z), new Vector2(mouseWorldPosition.x, mouseWorldPosition.z));
transform.rotation = Quaternion.Euler(new Vector3(0f, -angle, 0f));
}
float AngleBetweenTwoPoints(Vector3 a, Vector3 b)
{
return Mathf.Atan2(b.y - a.y, b.x - a.x) * Mathf.Rad2Deg;
}
// Update is called once per frame
void Update()
{
handleRotation();
handleAnimation();
handleGravity();
handle_isRunPressed();
}
void OnEnable()
{
playerInput.CharacterControls.Enable();
}
void OnDisable()
{
playerInput.CharacterControls.Disable();
}
}
Rotation still not working as intended with my code unforunately within handleRotation()
EDIT 3:
void handleRotation()
{
// We're getting a Vector2, whereas we will need a Vector3
// Get a z value based on camera, and include it in a Vector3
Vector2 mousePosition = playerInput.CharacterControls.MousePosition.ReadValue<Vector2>();
var mousePositionZ = _camera.farClipPlane * .5f;
Vector3 mouseViewportPosition = _camera.ViewportToWorldPoint(new Vector3(mousePosition.x, mousePosition.y, _camera.transform.position.y));
Debug.Log("MousePos: " + mouseViewportPosition);
Vector3 positionToLookAt;
positionToLookAt.x = mouseViewportPosition.x;
positionToLookAt.y = 0.0f;
//positionToLookAt.z = currentMovement.z;
positionToLookAt.z = mouseViewportPosition.z;
Quaternion currentRotation = transform.rotation;
Quaternion targetRotation = Quaternion.LookRotation(positionToLookAt - transform.position);
transform.rotation = Quaternion.Slerp(currentRotation, targetRotation, rotationFactorPerFrame * Time.deltaTime);
}
IT'S ROTATING. But not correctly.
Solution 1:[1]
UPDATE Take 2
Here is the full code I have in my Update()
:
var mousePosition = _playerInput.Player.MousePosition.ReadValue<Vector2>();
var mousePositionZ = _camera.farClipPlane * .5f;
var mouseWorldPosition = _camera.ScreenToWorldPoint(new Vector3(mousePosition.x, mousePosition.y, mousePositionZ)); // _camera.ScreenToViewportPoint(mousePosition);
// Get the angle between the points
// Use the x and z from the object/mouse, since we're looking along the y axis
var angle = AngleBetweenTwoPoints(new Vector2(transform.position.x, transform.position.z), new Vector2(mouseWorldPosition.x, mouseWorldPosition.z));
transform.rotation = Quaternion.Euler(new Vector3(0f, -angle, 0f));
and the helper function is the same:
float AngleBetweenTwoPoints(Vector2 a, Vector2 b)
{
return Mathf.Atan2(b.y - a.y, b.x - a.x) * Mathf.Rad2Deg;
}
UPDATE
I figured out how to do this in the way that Joseph was originally trying.
ScreenToWorldPoint
does indeed work correctly with the new input system. The issue is the way we're trying to use it. It needs a Vector3
!
You CAN'T do this:
// Don't do this, it wont work!
var mousePosition = _playerInput.Player.MousePosition.ReadValue<Vector2>();
var mouseViewportPosition = _camera.ScreenToWorldPoint(mousePosition);
You need to do this:
// We're getting a Vector2, whereas we will need a Vector3
// Get a z value based on camera, and include it in a Vector3
var mousePosition = _playerInput.Player.MousePosition.ReadValue<Vector2>();
var mousePositionZ = _camera.farClipPlane * .5f;
var mouseViewportPosition = _camera.ScreenToWorldPoint(new Vector3(mousePosition.x, mousePosition.y, mousePositionZ));
Original answer:
Your example had some issues... I think you were in the middle of editing it to make the rotation based on the movement direction? There may be an issue with the mouse position stuff right now in the engine... I noticed some other threads complaining that the input values weren't the same, but I'm not sure to what extent. Maybe the conversion to world coordinates isn't working?
For some reason, this does not work for me, it always spits out (0.0, 10.0, 0.0)
:
var mouseWorldPosition = Camera.main.ScreenToWorldPoint(mousePosition);
This is what I was able to get working:
// Get the mouse position from the NEW input system
var mousePosition = _playerInput.Player.MousePosition.ReadValue<Vector2>();
// Convert the mousePosition to the VIEWPORT
var mouseViewportPosition = Camera.main.ScreenToViewportPoint(mousePosition);
// Do the same with the object's position
var positionOnViewport = Camera.main.WorldToViewportPoint(transform.position);
// Get the angle between the points
var angle = AngleBetweenTwoPoints(positionOnViewport, mouseViewportPosition);
// Apply the angle as the rotation of the object
transform.rotation = Quaternion.Euler(new Vector3(0f, -angle, 0f));
The general idea was from here, including this function (https://answers.unity.com/questions/855976/make-a-player-model-rotate-towards-mouse-location.html):
float AngleBetweenTwoPoints(Vector3 a, Vector3 b)
{
return Mathf.Atan2(b.y - a.y, b.x - a.x) * Mathf.Rad2Deg;
}
Note that this isn't perfect... the angle is slightly off... not sure why that is. You can see it exaggerated a little here:
I think the moral of the story here is to check the input at each stage to see what it looks like and how it gets/got transformed. Now that we know some of the new input values may be off, you can either use the old system's mouse position, or combine my example with some of the other methods you mentioned.
Solution 2:[2]
Adding a new answer because this will just use Quaternions like you're trying to do.
The first step is to convert the mouse from screen coordinates, to world coordinates:
// Read the mouse position from the new input system
var mousePosition = _playerInput.Player.MousePosition.ReadValue<Vector2>();
// Ensure that there is a "valid" z value so that the conversion works properly
var mousePositionZ = _camera.farClipPlane * .5f;
// Convert!
var mouseWorldPosition = _camera.ScreenToWorldPoint(new Vector3(mousePosition.x, mousePosition.y, mousePositionZ));
Now that we have the mouse position in world coordinates, we can compare it to the object's (player's) position, and rotate based on that difference:
// Calculate the difference between the positions
var positionVector = mouseWorldPosition - transform.position;
// Match the new y value to the object's Y value.
// This ensures that the rotation is calculated only with the X and Z
// I would love to know why this is happening... but I didn't find anything in my initial research
positionVector.y = transform.position.y;
// Now we calculate the rotation
var targetRotation = Quaternion.LookRotation(positionVector);
// FYI, if your object's final rotation is off by 90 degrees, you can do the following
// I think it has to do with what the system thinks "forward" is, and which way your model is facing by default.
// So you can either fix it in your model, or add/subtract 90 degrees
// Note that **multiplying** Quaternions together effectively **combines** them
// var targetRotation = Quaternion.LookRotation(positionVector) * Quaternion.Euler(0, -90, 0);
// And smoothly transition to the new angle using Slerp
transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, rotationFactorPerFrame * Time.deltaTime);
Solution 3:[3]
I was having the same issue and finally i come up with smoother look at mouse solution for 3D Top-Down games. This is what my whole code looks like. I hope it helps.
[SerializeField] float moveSpeed = 5f;
[SerializeField] float turnSpeed = 2f;
Vector2 _moveInput;
Vector2 _mousePos;
void Update()
{
HandleMovement();
HandleRotation();
}
void OnMove(InputValue moveInput)
{
_moveInput = moveInput.Get<Vector2>();
}
void HandleMovement()
{
Vector3 deltaPos = new Vector3(_moveInput.x, 0f, _moveInput.y) * moveSpeed * Time.deltaTime;
transform.position += deltaPos;
}
void OnAim(InputValue currentMousePos)
{
_mousePos = currentMousePos.Get<Vector2>();
}
void HandleRotation()
{
Ray ray = Camera.main.ScreenPointToRay(_mousePos);
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
float rayDistance;
if (groundPlane.Raycast(ray, out rayDistance))
{
Vector3 point = ray.GetPoint(rayDistance);
LookAt(point);
}
}
void LookAt(Vector3 lookPoint)
{
Vector3 direction = (lookPoint - transform.position).normalized;
Quaternion lookRotation = Quaternion.LookRotation(new Vector3(direction.x, 0f, direction.z));
transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * turnSpeed);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | John B |
Solution 3 |