Mouse Look Accuracy Problem

Hello everyone. I have a problem with the mouse accuracy in Unity and I’m trying to figure out why. When using the standard Mouse Look script, I noticed that it is impossible to maintain a good accuracy and control it smoothly. It’s like it “jumps” over pixels, making it not very fun to aim and more difficult to aim at tiny spots than it should be.

I’m trying to do a FPS and get the basics right one step at a time, so accuracy is important. At first I thought it was a issue with my custom setup, models etc. but I reproduced the issue in a seperate Project with nothing but standard assets.

Here is a image GIF showing what I mean in this simplified seperate Project (moved 1px):

alt text

Here is also a Webplayer Demo of the Setup you can try it at. Try to aim at the bottles in small increments, like 1px at a time. The problem gets worse the more distance there is between you and the bottles: Play Webplayer Demo

Here is the Unity Asset Package in Case you want to see the Project. Except for the simple Shooting and Crosshair it uses only Standard Asssets (FPS Input and MouseLook): Download Project Files here

The issue has been confirmed on 5 PCs by 3 different people and has upset each one of them…

Things I noticed in testing:

  • When reducing Sensitivity of the Mouse Look Script to zero, it becomes 100% accurate, but the mouse is very slow to move around
  • Tests with DeltaTime can have a similiar result but, due to the nature of deltaTime, lead to undesirable effects on frame drops where the camera suddenly appears somewhere else
  • There are no alternative scripts. I found one “Smooth Mouse Look”, but it did not fix the problem.
  • I can’t think of a good way to hide the issue either (put it under the carpet and pretend it’s not there). Even a bullet stray along the center wouldn’t help “hiding” it because players expect if they move the mouse slowly they gain more accuracy, which is not given with this issue especially when you have a center dot showing you where you aim at. Same for a bigger Crosshair dot, the player would still want to accurately aim there and have trouble doing so.

I hope someone has a solution for this.

Thank you for your time and help

Ray

The best way to get better precision is to square the values returned by Mouse X and Mouse Y - they range from -1 to +1, so these values squared will have better precision around 0. But just squaring the values would loose the original polarity, so we must multiply the axis value by its absolute value. The squared axes behave like they had variable sensitivity - low sensitivity for small movements, and high for large excursions.

That’s a modified version of MouseLook where the axes are read first, multiplied by their absolute values and used instead of the original values. You can adjust the sensitivity to further improve the precision.

using UnityEngine;
using System.Collections;

[AddComponentMenu("Camera-Control/Mouse Look")]
public class MouseLook : MonoBehaviour {

	public enum RotationAxes { MouseXAndY = 0, MouseX = 1, MouseY = 2 }
	public RotationAxes axes = RotationAxes.MouseXAndY;
	public float sensitivityX = 15F;
	public float sensitivityY = 15F;
	public float minimumX = -360F;
	public float maximumX = 360F;
	public float minimumY = -60F;
	public float maximumY = 60F;
	float rotationY = 0F;

	void Update ()
	{
		float dx = Input.GetAxis("Mouse X");
		dx = dx * Mathf.Abs(dx); // squares mouse x while keeping the polarity
		float dy = Input.GetAxis("Mouse Y");
		dy = dy * Mathf.Abs(dy); // squares mouse y while keeping the polarity
		if (axes == RotationAxes.MouseXAndY)
		{
			float rotationX = transform.localEulerAngles.y + dx * sensitivityX;
			rotationY += dy * sensitivityY;
			rotationY = Mathf.Clamp (rotationY, minimumY, maximumY);
			transform.localEulerAngles = new Vector3(-rotationY, rotationX, 0);
		}
		else if (axes == RotationAxes.MouseX)
		{
			transform.Rotate(0, dx * sensitivityX, 0);
		}
		else
		{
			rotationY += dy * sensitivityY;
			rotationY = Mathf.Clamp (rotationY, minimumY, maximumY);
			transform.localEulerAngles = new Vector3(-rotationY, transform.localEulerAngles.y, 0);
		}
	}
	
	void Start ()
	{
		if (rigidbody) rigidbody.freezeRotation = true;
	}
}

This works pretty well but I have a question. Why use the transform.rotate for the X rotation but not for the Y rotation?