- Home /
Turret Rotation on a moving object with rotation limits
I've been working at a problem with some time, and a week+ of googling and experimenting has got me nowhere.
--THE PROBLEM--
I have a turret on a moving object (let's say, for the sake of simplicity, it's on a tank).
Now the turret faces the mouse position with limitations:
It cannot rotate left or right so that the angle is in excess of variable maxLRArc/2.
It is limited in L/R rotation speed by rotationSpeedLR
It cannot rotate up or down so that the angle is in excess of variable maxUDArc/2.
It is limited in U/D rotation speed by rotationSpeedUD
Here's a picture for clarity:
EDIT: A picture of the scene in Unity:
The YELLOW line is the raytrace from camera to mouse position
The CYAN lines form the limits of the Up/Down rotation
The RED (less visible) lines form the limits of the Left/Right rotation
The BLUE line is the mouse position projected on the L/R plane
The MAGENTA line is the mouse position projected on the U/D plane
The GREEN line shows the parent's forward vector from the position of the turret
If it's still unclear you can think about it as having someone's head track a moving object... The angle limitations are apparent in that scenario... but the person can only turn his head left or right and pitch up or down, but not tilt his head to one side or another.
--THE APPROACH--
I've been attempting to approach the problem from a step-by-step method, which is to say:
Turret first rotates left or right towards the mouse.
Turret then rotates up or down towards the mouse.
The way I'm doing is is first projecting the mouse position vector onto a plane in-line with the turret. Once for each step of the rotation, so that the mouse position is in-plane with the step's angle limits.
The checks and balances all work beautifully, the problem is executing the rotations, WITH a constant speed.
When I attempted to rotate for each axis separately, the rotations interfered with one another. When I attempted to rotate them together, the turret ended up twisting along the local Z axis (separating from it's logical point-of-contact to the tank) particularly noticeable when the tank goes up or down an incline.
--THE PLEA--
I've tried a lot of solutions that I've found but none give the desired results. Most (if not all) deal with a few of the restrictions I've listed individually, but not together like this.
I figure I'm at the limits of my capability and need to yield to more experienced individuals. Any help is greatly appreciated!
EDIT: The code I have is so messy and fragmented that it would be more harm than good, but if necessary, i can provide it.
Answer by Epitaph778 · Oct 07, 2014 at 01:39 AM
After attempting the solutions in the answers provided, I eventually came upon a working solution.
Since the code was functional for turn movement and pitch movement individually, I calculated the proper quaternions for the movement within the boundaries, then converted portions of these quaternions to vector3s of euler angles and used quaternion.euler to rotate:
Vector3 rot = new Vector3(pitchQuat.eulerAngles.x,
turnQuat.rotation.eulerAngles.y,
pitchQuat.rotation.eulerAngles.z);
transform.rotation = Quaternion.Euler(rot);
Thanks for all the responses everyone!
Answer by robertbu · Sep 24, 2014 at 02:31 AM
Here is a script that gets you most of the way there. There some unknowns in your question, so I expect you'll have a few changes, but I believe all the heavy lifting is done.
#pragma strict
var target : Transform;
var gun : Transform;
var turretDegreesPerSecond : float = 45.0;
var gunDegreesPerSecond : float = 45.0;
var maxGunAngle = 45.0;
var maxTurretAngle = 45.0;
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;
var qTurret : Quaternion = Quaternion.LookRotation(planePoint - trans.position, transform.up);
var qForward : Quaternion = Quaternion.LookRotation(transform.parent.forward);
if (Quaternion.Angle(qTurret, qForward) < maxTurretAngle) {
transform.rotation = Quaternion.RotateTowards(transform.rotation, qTurret, turretDegreesPerSecond * Time.deltaTime);
}
else {
Debug.Log("Target beyond turret range");
}
var v3 = Vector3(0.0, distanceToPlane, (planePoint - transform.position).magnitude);
var qGun : Quaternion = 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");
}
And here is a unitypackage with this script in use:
www.laughingloops.com/Turret2.unitypackage
Note that maxGunAngle() will define the maximum delta from the start position of the gun. For maxTurretAngle, the maximum angle is between the forward of the parent object and the gun.
Hey, thanks for your response! I've tried out the script and it works, though not in the manner I'm looking for.
I'm trying to figure out a means of rotation for the gun IS the turret. there are no separate object. You can think of it as a rocket turret or as my initial analogy (provided above) stated: someone's head tracking an object without twist.
I also forgot to mention, that the gun should always track the target, but stop at a maximum range. In a two-step rotation process should perform correctly. Again, similar to head tracking in games. If you are running around a character, and you run past their head range behind that character's head, the head will track you to an extent, and when you pass directly behind the character, the head will turn the other way and stop at the other extent until you come into range again.
I think this will get you what you want, or at least a step closer. There is just one angle limit, so the limit will be a circle...or you can think about it as a cone-shaped possible area for targets. So it will perform similar to a head rotation.
#pragma strict
public var target : Transform;
public var maxAngle : float = 35.0;
public var speed : float = 55.0;
function Update () {
var v3Target = (target.position - transform.position);
var v3Forward = transform.parent.forward;
if (Vector3.Angle(v3Target, v3Forward) > maxAngle) {
var v3Axis = Vector3.Cross(v3Forward, v3Target);
v3Target = Quaternion.AngleAxis(maxAngle, v3Axis) * v3Forward;
Debug.Log("Rotation limited");
}
var qTarget = Quaternion.LookRotation(v3Target);
transform.rotation = Quaternion.Slerp(transform.rotation, qTarget, Time.deltaTime * speed);
}
The maxAngle is from the forward of the parent, so the angle of view will be twice this angle.
Answer by sevensixtytwo · Sep 24, 2014 at 04:45 PM
You can use just one segment of code from robertbu's answer. Here:
var toTarget : Quaternion = Quaternion.LookRotation(target.position - turret.position, turret.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, toTarget, turretSpeed * Time.deltaTime);
//freeze Z-axis to stop turret from rolling
transform.localRotation.z = Quaternion.identity.z;
With these alone, the turret will rotate towards the target on all axes, except Z.
Use the ClampAngle function specified in aldonaletto's answer here:
It automatically converts negative values into usable eulers. Of course, you'll need minimum and maximum values for your X and Y axes. Use it like this:
var angleX = ClampAngle(turret.localEulerAngles.y,minX,maxX);
var angleY = ClampAngle(turret.localEulerAngles.x,minY,maxY);
turret.localEulerAngles = new Vector3(angleY,angleX,turret.localEulerAngles.z);