Mouse Orbit modification: Clamping X angle trouble

Hey guys,

As the title says, I am trying to modify the MouseOrbit script supplied with Unity so that it also clamps the X axis. The desired result is that the camera will not rotate post a min and max variable (for my example i use 40 and -40 degrees) of the object it is watching.

Below is an image illustrating what the desired result is. This is designed for a tank script. The turret looks at a ray cast via the camera and uses Quaternion.Lerp to look at the ray hit point. For this specific tank I wish for the turret to only be able to shoot in front of the vehicle, that is why I have decided to try edit the MouseOrbit script to achieve this.

The turret shouldn’t be able to look past the min and max angles illustrated above.

Now I have managed to achieve this mostly through the addition of these lines to the original MouseOrbit.js script

var targetY = targetRotation.localEulerAngles.y;		
var newXMinLimit = targetY + xMinLimit;
var newXMaxLimit = targetY + xMaxLimit;
x = ClampAngle(x, newXMinLimit, newXMaxLimit);

The whole script looks as follows for those who are interested

var target : Transform;
var targetRotation : Transform;
var distance = 10.0;

var xSpeed = 250.0;
var ySpeed = 120.0;

var yMinLimit = -20;
var yMaxLimit = 80;

var xMinLimit = -40;
var xMaxLimit = 40;

private var x = 0.0;
private var y = 0.0;

function Start () {
    var angles = target.transform.eulerAngles;
    x = angles.y;
    y = angles.x;

	// Make the rigid body not change rotation
   	if (rigidbody) {
		rigidbody.freezeRotation = true;
	}
}

function LateUpdate () {
    if (target) {
        x += Input.GetAxis("Mouse X") * xSpeed * 0.02;
        y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02;
		var targetY = targetRotation.localEulerAngles.y;
		
		var newXMinLimit = targetY + xMinLimit;
		var newXMaxLimit = targetY + xMaxLimit;
				
 		y = ClampAngle(y, yMinLimit, yMaxLimit);
 		x = ClampAngle(x, newXMinLimit, newXMaxLimit);
		
 		       
        var rotation = Quaternion.Euler(y, x, 0);
        var position = rotation * Vector3(0.0, 0.0, -distance) + target.position;
        
        transform.rotation = rotation;
        transform.position = position;
    }
}

static function ClampAngle (angle : float, min : float, max : float) {
	if (angle < -360)
		angle += 360;
	if (angle > 360)
		angle -= 360;
	return Mathf.Clamp (angle, min, max);
}

Now the problem I have run into is when the forward direction of the base reaches between 320 to 0 to 40 degrees. The camera jumps 40 degrees to the right or left when the base enters these angles. I checked into how Mathf.Clamp works and basically it checks whether the camera angle is <= the min or >= the max and sets it to it if it is true.
Basically the following:

if (x >= max) {
    x = max
} else if (x <= min) {
    x = min
}

Now when the base enters the angles between 320 and 40, these are what those min and max angles are:
Base direction = 39 : max = 79 : min = 359;
Base direction = 20 : max = 60 : min = 340;
Base direction = 0 : max = 40 : min = 320;
Base direction = 340 : max = 20 : min = 300;
Base direction = 321 : max = 1 : min = 281;

Now if you place those values into the above if statement you can see why it jumps. Where, in angle terms, 39 degrees should be greater than 359 for the example I used, but the if statement obviously records the latter. same with 321 degrees should be less than 1 for the if statement to work.

I hope I am making sense. I have tried to work around this but I am completely stumped as to how I would fix this.

I don’t believe in posting questions that could have been solved through proper searching, but this time I really haven’t been able to solve this on my own.

Can anyone show me how I would get around this? It would be much appreciated.

You could treat x as the local turret rotation alone, then always clamp it between -40 and 40. Then add targetRotation.localEulerAngles.y for the final rotation.
This will result in a fixed relative turret angle, unless you move the mouse. If you want the turret to automatically compensate for any base rotation, subtract the base rotation delta (current - previous rotation) from the mouse influence.

Thanks Jasper Flick for your response :slight_smile:

This did fix the jumping problem I described above :smile:, but brought about new ones :(. The 1st one is I used Quaternion.Lerp to rotate my turret at a speed of 6, so it lags a bit behind the mouse when you look to generate a “turrety” sort of feel. What happens then is the camera goes past the 40 degree mark before the turret, then when the turret reaches the 40 degree mark it shoots the camera back to that position.

I fixed this by changing it from Quaternion.Lerp to Quaternion.LookRotation and it stopped the problem above but doesn’t look nice : / and is quite irritating to the eye. Preferably I would like to try keep the turret rotating with Quaternion.Lerp (or one that limits its rotation to a certain speed, as Lerp slows down towards the end which isn’t desired for Third Person Shooter Aiming). Another problem that has arisen is the camera sometimes sticks to the max and min values if I moved the mouse to fast into them, and can be quite anoying to unstick.

Heres what my code looks like at the moment:

var target : Transform;
var targetRotation : Transform;
var targetBaseRotation : Transform;
var distance = 10.0;

var xSpeed = 250.0;
var ySpeed = 120.0;

var yMinLimit = -20;
var yMaxLimit = 80;

var xMinLimit = -40;
var xMaxLimit = 40;

private var x = 0.0;
private var y = 0.0;

function Start () {
    var angles = target.transform.eulerAngles;
    x = angles.y;
    y = angles.x;

	// Make the rigid body not change rotation
   	if (rigidbody) {
		rigidbody.freezeRotation = true;
	}
}

function LateUpdate () {
    if (target) {
        x += Input.GetAxis("Mouse X") * xSpeed * 0.02;
        y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02;
		
		var targetY = targetRotation.localEulerAngles.y;
		var targetBaseY = targetBaseRotation.localEulerAngles.y;
		if (targetY < 180) {
			if (targetY > xMaxLimit) {
			x = (targetBaseY + xMaxLimit);
			}
		} else {
			if (targetY < (xMinLimit + 360) ) {
			x = (targetBaseY + xMinLimit);
			}
		}
		
		var newXMinLimit = targetY + xMinLimit;
		var newXMaxLimit = targetY + xMaxLimit;
				
 		y = ClampAngle(y, yMinLimit, yMaxLimit);
 		       
        var rotation = Quaternion.Euler(y, x, 0);
        var position = rotation * Vector3(0.0, 0.0, -distance) + target.position;
        
        transform.rotation = rotation;
        transform.position = position;
    }
}

static function ClampAngle (angle : float, min : float, max : float) {
	if (angle < -360)
		angle += 360;
	if (angle > 360)
		angle -= 360;
	return Mathf.Clamp (angle, min, max);
}

The lines I changed above are the following:

		var targetY = targetRotation.localEulerAngles.y;
		var targetBaseY = targetBaseRotation.localEulerAngles.y;
		if (targetY < 180) {
			if (targetY > xMaxLimit) {
			x = (targetBaseY + xMaxLimit);
			}
		} else {
			if (targetY < (xMinLimit + 360) ) {
			x = (targetBaseY + xMinLimit);
			}
		}

I have probably messed up somewhere in the coding :S. I tried solving the sticking problem by adding 0.01 and subtracting 0,01 to where I reset the X values to make sure they don’t trigger the if statement continually, but when turning at high speeds the camera vibrates just a little, but enough to irritate the eye and possibly bring about headaches, and even with this setting it still manages to stick now and then :(.

Thanks in advance for the replies, I really hope to get this thing working :slight_smile:

So you want the turret to align with the mouse, but respond sluggish? It might be worth checking whether you can get this effect by simply adjusting the input settings for Mouse X. Or filter the mouse delta yourself before using it for further calculations. Then use this data to keep the turret and camera exactly aligned.

Oh and factor in Time.deltaTime somewhere if you want the speed to be time-dependent instead of framerate-dependent.

Perhaps you could make the camera a child of the turret. Then you only need to worry about its x rotation and nothing else. Really depends on the detail of your game, which I don’t know.

Once again thanks Jasper Flick, your responses have been most helpful.

Yeah I will probably look into that when I am a bit more comfortable with my knowledge of Unity :stuck_out_tongue:

I thought it was weird that a built-in Unity script didn’t factor that in but decided they probably had reason not to. It could be because the following:

x += Input.GetAxis("Mouse X") * xSpeed * 0.02;
y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02;

takes the direct value of the mouse coordinates, so Time.deltaTime might be redundant. If it poses a problem, when I start testing on slower machines, I’ll look into it again but thanks for the tip :slight_smile:

I decided to go with this, but due to the code it resists any rotation that is not brought on via moving the mouse. So I added the following lines of code and I am happy to say that it is working 100% :smile:

function LateUpdate () {
    if (target) {
        x += Input.GetAxis("Mouse X") * xSpeed * 0.02;
        y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02;
				
 		y = ClampAngle(y, yMinLimit, yMaxLimit);
		x = ClampAngle(x, xMinLimit, xMaxLimit);
		var baseRotation = targetBaseRotation.rotation;
		baseRotation.z = 0;
 	
        // The line that made it all work :smile:	       
        var rotation = baseRotation * Quaternion.Euler(y, x, 0);
        var position = rotation * Vector3(0.0, 0.0, -distance) + target.position;
        
        transform.rotation = rotation;
        transform.position = position;
    }
}

Once again thank you :smile:, been stuck on this for quite a while and has set my project a tad behind schedule :stuck_out_tongue: (I know I should have probably moved on, but I just couldn’t :S). So glad I have something that works and is appeasing to the eye.