cut from the mouse look script. sorry - it’s c#. don’t have time right now to js it. Mathf.Clamp is what you want. angle is your current rotation. min and max are vars you set in the inspector.
function ClampAngle (aAngle : float, aMinAngle : float, aMaxAngle : float) {
// Limit the angle to the range -360 to 360
if (aAngle < -360.0) {
aAngle += 360.0;
} else if (aAngle > 360.0) {
aAngle -= 360.0;
}
// Clamp the value between your specified min/max values
return Mathf.Clamp(aAngle, aMinAngle, aMaxAngle);
}
Then, in your code when you want to limit an angle between -20 and 20, you would call it like this:
var tClampedAngle = ClampAngle(tSomeAngle, -20.0, 20.0);
Note: the above is untested forum code but it should be mostly correct. Also note that the code to limit the angle to -360 to 360 won’t work right if the angle value is something like 732 (really the same as saying 12 degrees), so if you’re going to encounter values greater than 720 degrees you’ll need to customize the above function a bit more. Let us know if that’s the case and we can advise from there.
The ClampAngle sounds like it would do it, but it still isn’t working correctly. I want to limit the x rotation of a cannon. Here’s a unity project so you can see exactly what I want.
daniel, i don’t have time to mess with this now but i did throw it on a test scene. what i posted works ALMOST.
i made a cube the target and the ball the gun. mostly works fine. for some reason if the cube’s position is -9,-9,-9 the gun’s x rotation will flip. it seems to be related to x and z both being negative numbers but it works properly at -9,-9,-4. if anyone sees what would cause this let daniel know.
one thing i didn’t look at is your gun’s pivot point and how the range is calc’d. if the range center wasn’t at 0,0,0 in my scene, the cube could be outside the range at -9,-9,-9 which could cause that behavior. that’s a whole other prob tho. the gun should go to idle or something if the target leaves the range but i don’t think the script accounts for it (iirc). anyway, no time now. good luck with it!
Ok guys, I’ve got a handle on what’s going on here. The truth is that setting euler angle rotation values directly, especially when modifying only one component, is risky at best. The truth is that setting individual component values may have unexpected effects* on the other components and therefore cause unexpected behavior*. Similarly, watching a single component’s value is risky as it too will be changing and could perhaps take on unexpected values*.
Why so much unexpected behavior? The three components of rotation values are tied together in a chain due to how rotations are computed. Unless you know the inner-workings of how the rotation values are determined then many unexpected things can happen if you start manually tinkering with those components. Such is the situation in this discussion so far as well as in a few PM’s Daniel and I have been exchanging.
So what are we to do?
The solution is to avoid setting the values directly and instead rely on the exposed methods to achieve our goal. With some slight modifications I’ve made this work in Daniel’s sample project posted above. In that project you’ll find a script attached to the turret (CannonAI.js) and it has an AttackTarget() function defined. Here is my modified version of that function that now works as expected:
function AttackTarget ()
{
var targetDir = target.position - gun.position;
var forward = gun.forward;
if (Vector3.Angle (forward, targetDir) < shootingAngle)
BroadcastMessage ("Fire", SendMessageOptions.DontRequireReceiver);
// This is where the gun rotates towards the target
var targetRotation = Quaternion.LookRotation (targetDir);
gun.rotation = Quaternion.Slerp (gun.rotation, targetRotation, Time.deltaTime * trackingSpeed);
// Now that the turret is pointing a new direction (Lerp'ed toward the target) we need
// to see if it's more than 20 degrees above or below horizontal. If it is then we
// should counter-rotate the turrent to clamp its rotation at +/- 20 degrees.
// Get the gun's forward component in the horiztonal plane
var tHorizComp = gun.forward - (Vector3.Dot(gun.forward, Vector3(0,1,0)) * Vector3(0,1,0));
// Get the angle between the forward vector and its horizontal component
var tAngle = Vector3.Angle(tHorizComp, gun.forward);
// See if that angle is too large
if (tAngle > xMaxRotation) {
// Get the correctional rotation axis
var tRotateAxis = Vector3.Cross(tHorizComp, gun.forward);
// Rotate to around that axis to clamp the rotation at +/- 20 degrees above horizontal
gun.RotateAround(gun.position, tRotateAxis, -(tAngle - xMaxRotation));
}
}
Note that on my end I updated the camera controls a bit too. I left the original controls in-place but commented out so it’s easy to switch back and forth between them. But now with this solution all seems to behave as expected, and as I said above I believe that’s due to relying on the exposed methods and not on modifying the euler angle components directly.
And here is a copy of my project folder for y’all to review.
// Get the gun's forward component in the horiztonal plane
var tHorizComp = gun.forward - (Vector3.Dot(gun.forward, Vector3(0,1,0)) * Vector3(0,1,0));
And what Vector3.Dot() does this is gets the portion of gun.forward along the world’s y-axis*. But given that gun.forward returns a world-relative vector anyway I suppose that Dot-operation is redundant. I replaced the code above with this instead, it does the exact same thing with less hassle:
// Get the gun's forward component in the horiztonal plane
var tHorizComp = Vector3(gun.forward.x, 0, gun.forward.z);
Sorry for the extra code confusion…
*Dot products are very common and something well worth knowing. If you have two vectors, A and B, and you compute the dot product of them you get:
Vector3.Dot(A, B) = A.magnitude * B.magnitude * cosine(angle between A and B)
And when one of those vectors (B) is a world unit vector (like Vector3(0,1,0), the y-axis) then the result of the dot operation is the length of A along B, or in my case the length of A (gun.forward) along B (the world’s y-axis). Imagine this example graph:
As I mentioned above, the dot product of y-axis (y) and gun.forward (fwd) is:
And other is the component of gun.forward along the y-axis. A rather long way around things but hey, it gave me a chance to share a bit of info about Dot products. After all this, you don’t need to use it in your script anyway.
If you want to know more then do a Google search on dot product and you’ll get a ton of results…
The general technique is the same except that instead of getting the angle between the gun’s forward vector and that forward vector’s horizontal component, you compare the angle between the gun’s forward-horizontal component and the turret’s forward vector. Here’s my working AttackTarget() function that limits both x and y rotations simultaneously:
function AttackTarget ()
{
var targetDir = target.position - gun.position;
var forward = gun.forward;
if (Vector3.Angle (forward, targetDir) < shootingAngle)
BroadcastMessage ("Fire", SendMessageOptions.DontRequireReceiver);
// This is where the gun rotates towards the target
var targetRotation = Quaternion.LookRotation (targetDir);
gun.rotation = Quaternion.Slerp (gun.rotation, targetRotation, Time.deltaTime * trackingSpeed);
// Now that the turret is pointing a new direction (Lerp'ed toward the target) we need
// to ensure that its rotations are within the specified limits...
// Get the gun's forward component in the horiztonal plane
var tHorizComp = gun.forward - (Vector3.Dot(gun.forward, Vector3(0,1,0)) * Vector3(0,1,0));
// Get the angle between the forward vector and its horizontal component
var tAngle = Vector3.Angle(tHorizComp, gun.forward);
// See if that angle is too large
if (tAngle > xMaxRotation) {
// Get the correctional rotation axis
var tXRotateAxis = Vector3.Cross(tHorizComp, gun.forward);
// Rotate to around that axis to clamp the rotation at +/- 20 degrees above horizontal
gun.RotateAround(gun.position, tXRotateAxis, -(tAngle - xMaxRotation));
}
// Once again get the gun's forward component in the horizontal plane
tHorizComp = gun.forward - (Vector3.Dot(gun.forward, Vector3(0,1,0)) * Vector3(0,1,0));
// Now get the angle between the turret base's forward vector and the gun forward's horizontal component
tAngle = Vector3.Angle(tHorizComp, base.forward);
// See if that angle is too large
if (tAngle > yMaxRotation) {
// Get the correctional rotation axis
var tYRotateAxis = Vector3.Cross(tHorizComp, base.forward);
// Rotate around that axis to clamp the rotation
gun.RotateAround(gun.position, tYRotateAxis, (tAngle - yMaxRotation));
}
}
I’ve attached the second revision of my solution that limits both x and y rotations of the turret (x is limited to +/- 10 degrees whereas y is limited to +/- 45 degrees). Notice that it required some new public properties as well as the above modifications to AttackTarget().