- Home /
Turret Slerp rotation and clamping
Firstly, following on from my turret rotation problem, which is now working wonderfully, I now need to smoothly rotate the turret between targets.
I tried doing it using:
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targetVector,hardpointPosition.transform.up), Time.deltaTime * turnspeed);
gun.transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(target.position-transform.position,hardpointPosition.transform.up), Time.deltaTime * turnspeed);
While this did make the turret rotates as expected, the gun can now no longer rotate up or down, which is useless to me. How can I go about fixing this?
Secondly, I also need to prevent the gun from rotating too far downwards, to stop it from trying to aim through the bottom of the turret base. I only need to worry about the maximum downwards angle, as when the gun points directly up, the turret base rotates around to face the target, meaning the gun cannot rotate more than 90 degrees up. What's the best way to go about sorting this out?
Thanks in advance.
Is it possible to construct your turret so that the turret base (rotator) and gun (elevator) are separate GameObjects (the gun being a child of the turret base)?
That's how I have it set up at the moment: Ship > Turret > Gun. The gun variable in the code is just a public Transform that I've assigned the gun model to.
Answer by Brian@Artific · Oct 24, 2013 at 06:44 PM
Here's an excerpt from the Gun script I created for a tank game we worked on last year. In brief: it checks whether the aim point is left or right of a plane orthogonal to the local right of the rotating part of the turret (the turret base) and then checks whether the aim point is above or below a plane orthogonal to the local up of the elevating part of the turret (the gun). It could probably use some optimization, but you're welcome to it...
/// <summary>
/// The transform to rotate around the X axis (elevate) when aiming.
/// </summary>
public Transform elevator;
/// <summary>
/// The maximum elevation, in degress, below the zero point.
/// </summary>
public float minElevation;
/// <summary>
/// The maximum elevation, in degress, above the zero point.
/// </summary>
public float maxElevation;
/// <summary>
/// The transform to rotate around the Y axis (rotate) when aiming.
/// </summary>
public Transform rotator;
/// <summary>
/// The maximum rotation, in degress, to the left of the zero point.
/// </summary>
public float minRotation;
/// <summary>
/// The maximum rotation, in degress, to the right of the zero point.
/// </summary>
public float maxRotation;
/// <summary>
/// The accuracy of the weapon, in meters.
/// </summary>
public float accuracy;
/// <summary>
/// The speed at which the weapon may be turned.
/// </summary>
public float turnSpeed;
/// <summary>
/// Rotates and elevates the weapon torwards the target.
/// </summary>
/// <param name="target">The target to aim at.</param>
public virtual void Aim(Vector3 target)
{
// Get a plane through the rotation of the weapon
Plane rot = new Plane(rotator.right, rotator.position);
if (Mathf.Abs(rot.GetDistanceToPoint(target)) < accuracy)
return;
// And rotate towards target
if (rot.GetSide(target))
Rotate(1.0f); //right
else
Rotate(-1.0f); //left
// Get a plane through the elevation of the weapon
Plane elev = new Plane(elevator.up, elevator.position);
if (Mathf.Abs(elev.GetDistanceToPoint(target)) < accuracy)
return;
// And elevate towards target
if (elev.GetSide(target))
Elevate(1.0f); //up
else
Elevate(-1.0f); //down
}
/// <summary>
/// Pivots the weapon up(+) and down(-).
/// </summary>
/// <param name="direction">The direction to pivot; up is positive, down is negative.</param>
public virtual void Elevate(float direction)
{
// Clamp the direction input between -1 and 1...
direction = Mathf.Clamp(-direction, -1.0f, 1.0f);
// Calculate the new angle...
float angle = elevator.localEulerAngles.x + direction * turnSpeed * Time.deltaTime;
if (angle > 180)
angle -= 360;
// Clamp the new angle between the given minimum and maximum...
angle = Mathf.Clamp(angle, -maxElevation, -minElevation);
// Update the transform...
elevator.localEulerAngles = new Vector3(angle, elevator.localEulerAngles.y, elevator.localEulerAngles.z);
}
/// <summary>
/// Pivots the weapon right(+) and left(-).
/// </summary>
/// <param name="direction">The direction to pivot; right is positive, left is negative.</param>
public virtual void Rotate(float direction)
{
// Clamp the direction input between -1 and 1...
direction = Mathf.Clamp(direction, -1.0f, 1.0f);
// Calculate the new angle...
float angle = rotator.localEulerAngles.y + direction * turnSpeed * Time.deltaTime;
if (angle > 180)
angle -= 360;
// Clamp the new angle between the given minimum and maximum...
if (Mathf.Abs(minRotation) + Mathf.Abs(maxRotation) > 0)
angle = Mathf.Clamp(angle, minRotation, maxRotation);
// Update the transform...
rotator.localEulerAngles = new Vector3(rotator.localEulerAngles.x, angle, rotator.localEulerAngles.z);
}
How would I go about adding a target generated from another script into that?
Just add a public variable for the target and pass it to the Aim method in Update, like so:
public Transform myTarget;
void Update()
{
Aim(myTarget.position);
}
Thanks! This script was a massive help in figuring out a solution for my problem.
Answer by robertbu · Oct 24, 2013 at 05:11 PM
I have a couple of untested solutions for you. Usually for something like this, I test the solution, but I can't right now. First take a look at my gun script in this answer:
http://answers.unity3d.com/questions/388185/make-the-turret-automatically-rotate-to-look-at-wh.html
It works by managing the gun only on the local rotation, and gun rotates only on its local 'x' axis. The code does this by "transporting" the point so it is effectively (in local coordinates) right in front of the gun so the gun is "forced" to only rotate on the 'x' axis.
For your use, given the turret can have any arbitrary rotation, things are more complex. For calculating the 'Z' distance, you will need to calculate the distance from the pivot point to the point you projected on the plane you used in my last answer. For the 'y' height it will be the signed distance above/below the plane the turret sits on. These two values can be to construct the local rotation of the gun using Quaternion.LookRotation().
As for limiting the rotation, my suggestion is to start with the gun (in the editor) at the midpoint of its travel range. So if the gun could travel up to 90 and down to -20, you would start the gun with an 'x' axis rotation of 35.0. Save the local rotation of the gun in a variable in Start(). Each time you calculate a new rotation, test it with Quaternion.Angle() against the local rotation you saved in Start(). If the angle is above the maximum travel angle (one side), then just don't assign it to 'transform.localRotation'.
Well I had a go at your suggestion, and I'm obviously doing something wrong.
zVector = targetVector - gun.transform.position;
zVectorDistance = zVector.magnitude;
yHeight = ($$anonymous$$ath3d.SignedDistancePlanePoint(gun.transform.up,gun.transform.position,target.transform.position));
newTarget = new Vector3(target.position.x,yHeight,zVectorDistance);
Now this is completely wrong, as the turret ends up ai$$anonymous$$g completely off target.
What am I doing wrong?
First, I'm assu$$anonymous$$g that 'targetVector' is the point on plane you calculated for the rotation of the turret. And to make this work without undue complication, the pivot of the gun and the turret should be in the same place. If so, you are mostly right. The final line needs to be:
newTarget = new Vector3(0.0f,yHeight,zVectorDistance);
That is, you are putting the point directly in front of the local forward. And the calculated rotation is assigned to 'transform.localRotation' of the gun.
tempRotation = Quaternion.Slerp(gun.transform.localRotation, Quaternion.LookRotation(newTarget,hardpointPosition.transform.up), Time.deltaTime * turretTurnSpeed);
angle = Quaternion.Angle(tempRotation,localRotation);
if(angle <93)
{
gun.transform.localRotation = tempRotation;
}
That's what I'm using right now. The turret is Slerping properly between targets, but now the gun is stuck at a 45 degree angle, and won't aim up or down.
Is it just the angle that is the problem? That is, without the '`if(angle <93)`', does the gun now aim correctly?
I took a bit of time to code up an example. I wanted to make sure what I was telling you would work. Here is a package of my test project:
www.laughingloops.com/Turret.unitypackage
You can use the arrow keys to rotate the base to test other angles.
And here is the turret script. It took surprising few lines of code. It works just as I've indicated above. Note to avoid duplicate calculation, I've combined the turret and gun scripts.
#pragma strict
var target : Transform;
var gun : Transform;
var turretDegreesPerSecond : float = 45.0;
var gunDegreesPerSecond : float = 45.0;
var maxGunAngle = 45.0;
private var qTurret : Quaternion;
private var qGun : Quaternion;
private var qGunStart : Quaternion;
private var trans : Transform;
function Start() {
trans = transform;
qGunStart = gun.transform.localRotation;
}
function Update () {
var distanceToPlane = Vector3.Dot(trans.up, target.position - trans.position);
var planePoint = target.position - trans.up * distanceToPlane;
qTurret = Quaternion.LookRotation(planePoint - trans.position, transform.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, qTurret, turretDegreesPerSecond * Time.deltaTime);
var v3 = Vector3(0.0, distanceToPlane, (planePoint - transform.position).magnitude);
qGun = Quaternion.LookRotation(v3);
if (Quaternion.Angle(qGunStart, qGun) <= maxGunAngle)
gun.localRotation = Quaternion.RotateTowards(gun.localRotation, qGun, gunDegreesPerSecond * Time.deltaTime);
else
Debug.Log("Target beyond gun range");
}
Your answer
![](https://koobas.hobune.stream/wayback/20220613120546im_/https://answers.unity.com/themes/thub/images/avi.jpg)