- Home /
How to Constrain Quaternion Rotations Over Time?
Right now in our first-person game we have an aiming system similar to (but not exactly like) Red Orchestra (http://www.youtube.com/watch?v=oiP82nAV9xM if you're not familiar): when the player moves the mouse, the screen will reorient perfectly, but the gun's aim will interpolate to follow the player's head with a little lag (to simulate imperfect aim when firing from the hip).
However, though right now the implementation we have works, it doesn't constrain the gun's movement when the player moves their aim too quickly for the gun to catch up. The result is that if the player moves the aim too quickly the gun will fall so far behind that it ends up just rotating around and around if the player moves their aim quickly enough. Anyway, here's the relevant code we have so far, along with some failed solutions I've already tried. Anyone have any ideas on how to solve this problem? Obviously I don't understand Quaternions well enough to solve this myself.
public float maxAngle; //maximum angle of difference between gun and aim point
public float aimDistance; //the distance at which to aim along the player's actual line of vision.
public float aimSpeed; //the speed at which the gun point catches up to the view point
private Vector3 aimPoint; //the line at which the gun is aiming - i.e., the line from the gun point to the point directly in front of from the player's vision. Currently sets the gun's rotation to follow to that point
private void aimLook() //called once per frame from Update()
{
//calculate the aimPoint as the current look direction outwards aimDistance distance
aimPoint = transform.forward * aimDistance;
//calculate new rotation to have the gun look at the aimPoint
Quaternion aimAtAimPoint = Quaternion.LookRotation(aimPoint);
//interpolate between the current aim and the desired aim point
Quaternion modAimAtAimPoint = Quaternion.Lerp(unmodifiedAimRotation, aimAtAimPoint, Time.deltaTime * aimSpeed);
//BEGIN SOLUTION ATTEMPT 1
//if the interpolated rotation angles are too far away from the center, constrain them
//Vector3 rotation = modAimAtAimPoint.eulerAngles;
//if(modAimAtAimPoint.eulerAngles.z > maxAngle || modAimAtAimPoint.eulerAngles.z < -maxAngle)
// rotation.z = maxAngle;
//if(modAimAtAimPoint.eulerAngles.y > maxAngle || modAimAtAimPoint.eulerAngles.y < -maxAngle)
// rotation.y = maxAngle;
//if(aimAtAimPoint.eulerAngles.z > maxAngle || aimAtAimPoint.eulerAngles.z < -maxAngle)
// rotation.z = maxAngle;
//modAimAtAimPoint.eulerAngles = rotation;
//END SOLUTION ATTEMPT 1
//BEGIN SOLUTION ATTEMPT 2
// Quaternion modAimAtAimPoint;
// float difference;
//
// //if the interpolated aim is too far away from the aimAtAimPoint, keep interpolating until it isn't
// do
// {
// //interpolate between the current aim and the desired aim point
// modAimAtAimPoint = Quaternion.Lerp(unmodifiedAimRotation, aimAtAimPoint, Time.deltaTime * aimSpeed + .1f);
//
// difference = Quaternion.Angle(modAimAtAimPoint, aimAtAimPoint);
// Debug.Log(difference);
// }while(difference > maxAngle || difference < -maxAngle);
// END SOLUTION ATTEMPT 2
//set the gun's actual aim to the interpolated aim
gunPoint.transform.rotation = modAimAtAimPoint;
}
Answer by aldonaletto · Apr 07, 2012 at 04:14 AM
I did something similar once just Lerp'ing the weapon to follow its parent. That's my hierarchy:
Player Camera Weapon Holder <- script attached to this empty object Machine Gun Rocket Launcher Laser Gun etc.That's the script:
var speed: float = 15; private var localRot: Quaternion; private var curRot: Quaternion;
function Start () { localRot = transform.localRotation; // save starting local rotation curRot = transform.rotation; }
function Update () { // targetRot is the ideal rotation: var targetRot = transform.parent.rotation localRot; // curRot follows targetRot with some delay: curRot = Quaternion.Lerp(curRot, targetRot, speed Time.deltaTime); transform.rotation = curRot; }
EDITED: I can't understand your script because some parts are missing, but I suppose it's similar to mine: you get the desired rotation and Lerp to it using Time.deltaTime (we both should use Slerp instead!).
Supposing aimAtAimPoint is the desired rotation and curRotation the current rotation, you can limit the angle between them with the following code (C#, this time!):
public float maxAngle = 10; private Quaternion curRotation; // the current weapon rotation ... // Slerp to it over time: curRotation = Quaternion.Slerp(curRotation, aimAtAimPoint, Time.deltaTime * aimSpeed); float angle = Quaternion.Angle(curRotation, targetRot); if (angle > maxAngle){ // if maxAngle exceeded... // find max rotation using interpolation: curRotation = Quaternion.Slerp(aimAtAimPoint, curRotation, maxAngle/angle); } ...How it works: if angle > maxAngle, the value maxAngle/angle is exactly what you need to find the max acceptable rotation by interpolating between aimAtAimPoint and curRotation. The interpolation must be linear in degrees, what means Slerp, the spherical interpolation.
Unfortunately that doesn't seem to work. It actually just makes the weapon keep the same rotation over time (it just Lerps the gun back to wherever it was pointing to begin with, no matter where the player is looking). I made sure to disable our previous rotation code so that didn't affect it, so I'm not sure why it's doing that.
Forgive me if I'm wrong, but it seems like your script would have the same problems in the end anyway (the gun not rotating fast enough if the player moves the camera too quickly). Our goal is to have the gun's rotation constrained so that it never points anywhere off screen from a first-person perspective.
I don't know why my script didn't work in your case, but maybe it's due to differences in the hierarchy. Anyway, you can limit the max angle between the ideal and the delayed rotations using a smart trick with interpolation. Take a look at my answer: I edited it to include this limit (and translated it to C#, too...)