- Home /
Rotating local Z-axis (roll) on a MouseLook (or similarly) controlled camera?
Desired Result: Similar to a space craft, capable of maneuvering in any direction within a 3d environment: keyboard movement (X/Y/Z translation), mouse Yaw/Pitch (X/Y rotation), and keyboard Roll (Z rotation).
Problem: Can't manipulate the Z-axis rotation for roll.
Basic example in a (v2.6) new scene-
- Select the default Main Camera
- Component -> Camera-Control -> MouseLook
- Component -> Physics -> Rigid Body
- In the Inspector, turn off 'Use Gravity'
- Create a new JavaScript asset and apply it to the Main Camera
JavaScript-
var speed : int = 1;
function FixedUpdate() {
if (Input.GetKey("w")) {
rigidbody.AddRelativeForce(Vector3.forward * speed);
}
}
This behaves exactly the way I want, except for roll. It works great for forward momentum with the mouse controlling your direction of travel (yaw/pitch), and I can easily add additional controls using other Vector3's (x/y/z translation). However, I just can't seem to find any way to rotate the Z-axis for that desired roll effect.
I've tried using transform.Rotate()
and rigidBody.AddRelativeTorque()
, but both only work if the MouseLook node is detached/disabled.
I've also tried mimicking MouseLook behavior directly in the above script by applying Input.GetAxis("Mouse X/Y")
to transform.Rotate()
, which does seem to work for Pitch/Yaw, and roll does rotate the Z axis - but it would only rotate within 0-8 degrees, and moving the mouse would cause it to "jitter" within 0-15 degrees.
I found that attempting to use rigidBody.AddRelativeTorque()
with Input.GetAxis("Mouse X/Y")
, causes it to spin uncontrollably. Which I think does make sense, since it's constantly applying torque based on the mouse's position - not the desirable effect.
From what I can tell so far, setting the X/Y rotation effects the Z axis as well, which is where my problem is. Perhaps this is part of Quaternion nature to avoid gimbal lock? That's just a curious guess, as I don't understand the Quaternion concept well enough to grasp why this is happening.
For reference, http://answers.unity3d.com/questions/2544/first-person-flight-controls helped me get this far - but unless I'm missing something, it doesn't quite fulfill this scenario.
So, the basic question is: How do I let the user 'roll' the camera on the Z axis with a keystroke, while still controlling the yaw/pitch (X/Y) axis with the mouse?
Thank you for reading, and even more thanks for any answers or suggestions!
~Badger
Answer by BadgerMeles · Jun 01, 2010 at 11:55 AM
The following is the full control script that I wrote after finding the solution. It's a strong basis for the 3d-space controller I was looking for - and fun to fly around with! For best testing results, I suggest setting your rigid body Mass
, Drag
and Angular Drag
all to 1
; also, make sure Gravity
, Is Kinematic
and Freeze Rotation
are all disabled.
var speed : int = 10; // Relative force applied for standard ws,ad,zx,qe movement. var mouseSensativity : int = 15; // Speed of mouse movements for X/Y rotation, looking around. var invertPitch : boolean = false; // Invert the X rotation axis. False: MouseDown=LookDown, True:MouseDown=LookUp // Whether to use transform.Rotate() or rigidbody.AddRelativeTorque(); enum rotationMethods { Torque = 0, Rotate = 1 }; var rotationMethod = rotationMethods.Rotate;
function FixedUpdate() { // This probably could be turned into a constant, instead of checking every update, but doing so lets the user change the setting on the fly without repercussions. var invertPitchInt; if (invertPitch) invertPitchInt = -1; else invertPitchInt = 1;
// Standard translate/position controls. if (Input.GetKey("w")) { rigidbody.AddRelativeForce (Vector3.forward speed); } if (Input.GetKey("s")) { rigidbody.AddRelativeForce (Vector3.forward -1 speed); } if (Input.GetKey("a")) { rigidbody.AddRelativeForce (Vector3.left speed); } if (Input.GetKey("d")) { rigidbody.AddRelativeForce (Vector3.right speed); } if (Input.GetKey("z")) { rigidbody.AddRelativeForce (Vector3.down speed); } if (Input.GetKey("x")) { rigidbody.AddRelativeForce (Vector3.up * speed); }
// Keyboard controls to mimic mouse movements.
if (Input.GetKey("i")) { // Simulate increasing the X axis, mouse movement up.
if (rotationMethod == rotationMethods.Torque) { rigidbody.AddRelativeTorque (invertPitchInt * -mouseSensativity * Time.deltaTime, 0, 0); }
else { transform.Rotate(invertPitchInt * -mouseSensativity * Time.deltaTime, 0, 0); }
}
if (Input.GetKey("k")) { // Simulate decreasing the X axis, mouse movement down.
if (rotationMethod == rotationMethods.Torque) { rigidbody.AddRelativeTorque (invertPitchInt * mouseSensativity * Time.deltaTime, 0, 0); }
else { transform.Rotate(invertPitchInt * mouseSensativity * Time.deltaTime,0,0); }
}
if (Input.GetKey("j")) { // Simulate increasing the Y axis, mouse movement left.
if (rotationMethod == rotationMethods.Torque) { rigidbody.AddRelativeTorque (0, -mouseSensativity * Time.deltaTime, 0); }
else { transform.Rotate(0, -mouseSensativity * Time.deltaTime, 0); }
}
if (Input.GetKey("l")) { // Simulate decreasing the Y axis, mouse movement right.
if (rotationMethod == rotationMethods.Torque) { rigidbody.AddRelativeTorque (0, mouseSensativity * Time.deltaTime, 0); }
else { transform.Rotate(0, mouseSensativity * Time.deltaTime, 0); }
}
// Actual mouse movement controls
if (Input.GetAxis("Mouse X")) {
if (rotationMethod == rotationMethods.Torque) { rigidbody.AddRelativeTorque( 0, Input.GetAxis("Mouse X") * mouseSensativity, 0); }
else { transform.Rotate(0, Input.GetAxis("Mouse X") * mouseSensativity, 0); }
}
if (Input.GetAxis("Mouse Y")) {
if (rotationMethod == rotationMethods.Torque) { rigidbody.AddRelativeTorque( invertPitchInt * Input.GetAxis("Mouse Y") * -mouseSensativity, 0, 0); }
else { transform.Rotate(invertPitchInt * Input.GetAxis("Mouse Y") * -mouseSensativity, 0, 0); }
}
// Roll controls
if (Input.GetKey("q")) {
if (rotationMethod == rotationMethods.Torque) { rigidbody.AddRelativeTorque(0, 0, speed * Time.deltaTime); }
else { transform.Rotate( 0, 0, -speed * Time.deltaTime); }
}
if (Input.GetKey("e")) {
if (rotationMethod == rotationMethods.Torque) { rigidbody.AddRelativeTorque(0, 0, -speed * Time.deltaTime); }
else { transform.Rotate( 0, 0, speed * Time.deltaTime); }
}
}
I don't know if it's "proper" to post this, too, but I think it can be removed if overkill. This is the test script I was working with, so others that might have similar issues can see what I tested, and why it didn't work. It's not very pretty, but hopefully the comments explain my thought process. I'm still getting used to this, though, so my apologies if it's not very clear.
var speed : int = 10; // Relative force applied for standard ws,ad,zx movement. var mouseSensativity : int = 15; // Rotation method used for rolling. {q/e} enum rollMethods { Torque = 0, Rotate = 1 }; var rollMethod = rollMethods.Rotate; // Rotation method used for mouse, or mouse-simulated key strokes {ik,jl} enum rotationTypes { Torque = 0, Rotate = 1 }; var rotationType = rotationTypes.Rotate; // Option to roll before or after the X/Y mouse rotations. enum whenToRollOptions {BeforeMouseLook = 0, DuringMouseLook = 1, AfterMouseLook = 2}; var whenToRoll = whenToRollOptions.DuringMouseLook; // Switch between the mouse-simulated keyboard, or actual mouse movement. var useKeyboardMouseSimulator : boolean = false; // Select which test result to use. enum mouseLookTests { TestA = 0, TestB = 1, TestC = 3, TestD = 4, TestE = 5, TestF = 6, TestG = 7, TestH = 8 } var mouseLookTest = mouseLookTests.TestA;
var clampedRotationX : float = 0.0; var clampedRotationY : float = 0.0; var clampedRotationZ : float = 0.0;
function FixedUpdate() { // Standard translate/position controls. if (Input.GetKey("w")) { rigidbody.AddRelativeForce (Vector3.forward speed); } /else/ if (Input.GetKey("s")) { rigidbody.AddRelativeForce (Vector3.forward -1 speed); } if (Input.GetKey("a")) { rigidbody.AddRelativeForce (Vector3.left speed); } /else/ if (Input.GetKey("d")) { rigidbody.AddRelativeForce (Vector3.right speed); } if (Input.GetKey("z")) { rigidbody.AddRelativeForce (Vector3.down speed); } /else/ if (Input.GetKey("x")) { rigidbody.AddRelativeForce (Vector3.up * speed); }
if (useKeyboardMouseSimulator) { // Manual key controls simulating mouse movement.
if (Input.GetKey("q")) {
if (rollMethod == rollMethods.Torque) { rigidbody.AddRelativeTorque(0,0,speed * Time.deltaTime); }
else { transform.Rotate(0,0,speed * Time.deltaTime); }
} else if (Input.GetKey("e")) {
if (rollMethod == rollMethods.Torque) { rigidbody.AddRelativeTorque(0,0,-speed * Time.deltaTime); }
else { transform.Rotate(0,0,-speed * Time.deltaTime); }
}
// ** -- Converting this block to accept Mouse input should be the solution I'm looking for -- **
// The result these keys produce is the exact same result the mouse movement should produce: i=MouseDown, k=MouseUp, j=MouseLeft, k=MouseRight
// Using Rotate is much more responsive, and likely the best result for user controlled mouse-like movement, and probobly is the result I'm most likely to desire to use.
// However, I'd also like to have the option to use the Torque method, if it would be possible to impliment - just to see how it functions.
if (Input.GetKey("i")) { // Simulate increasing the X axis, mouse movement up.
if (rotationType == rotationTypes.Torque) { rigidbody.AddRelativeTorque (speed * Time.deltaTime, 0, 0); }
else { transform.Rotate(speed * Time.deltaTime, 0, 0); }
} else if (Input.GetKey("k")) { // Simulate decreasing the X axis, mouse movement down.
if (rotationType == rotationTypes.Torque) { rigidbody.AddRelativeTorque (-speed * Time.deltaTime, 0, 0); }
else { transform.Rotate(-speed * Time.deltaTime,0,0); }
}
if (Input.GetKey("j")) { // Simulate increasing the Y axis, mouse movement left.
if (rotationType == rotationTypes.Torque) { rigidbody.AddRelativeTorque (0, -speed * Time.deltaTime, 0); }
else { transform.Rotate(0, -speed * Time.deltaTime, 0); }
} else if (Input.GetKey("l")) { // Simulate decreasing the Y axis, mouse movement right.
if (rotationType == rotationTypes.Torque) { rigidbody.AddRelativeTorque (0, speed * Time.deltaTime, 0); }
else { transform.Rotate(0, speed * Time.deltaTime, 0); }
}
} else { // This block contains my various attempts to achieve the goal I'm after.
clampedRotationX = ClampAngle(clampedRotationX + (Input.GetAxis("Mouse X") * mouseSensativity), -360, 360);
clampedRotationY = ClampAngle(clampedRotationY + (Input.GetAxis("Mouse Y") * mouseSensativity), -360, 360);
if (Input.GetKey("q")) { clampedRotationZ = ClampAngle(clampedRotationZ + (speed * Time.deltaTime), -360, 360); }
else if (Input.GetKey("e")) { clampedRotationZ = ClampAngle(clampedRotationZ + (-speed * Time.deltaTime), -360, 360); }
if (mouseLookTest == mouseLookTests.TestA) {
// This test works almost perfectly, except that the mouse movements do not follow the Z rotation propperly.
// Moving the mouse up/down looks up/down as expected when Z=0, but as Z changes the up/down movement does not follow.
// Basically if Z=90, moving the mouse up/down looks left/right. This is not the desired effect. Moving the mouse up/down, should always look up/down.
transform.localRotation = Quaternion.Euler(-clampedRotationY, clampedRotationX, clampedRotationZ);
} else if (mouseLookTest == mouseLookTests.TestB) {
// This seems to have the same result as TestA.
transform.localRotation = Quaternion.AngleAxis(clampedRotationX, Vector3.up) * Quaternion.AngleAxis(clampedRotationY, Vector3.left) * Quaternion.AngleAxis(clampedRotationZ, Vector3.forward);
} else if (mouseLookTest == mouseLookTests.TestC) {
// This freaks out beyond my understanding.
transform.localRotation.x = clampedRotationX; // clampedRotationY;
transform.localRotation.y = clampedRotationY; // clampedRotationX;
transform.localRotation.z = clampedRotationZ;
} else if (mouseLookTest == mouseLookTests.TestD) {
// This applies a constant rotation to the object, meaning it will always be rotating as long as clampedRotationX/Y/Z have non-zero values.
// Effectively this makes it impossible to control, since the camera will just start spinning.
transform.Rotate(clampedRotationY, clampedRotationX, clampedRotationZ);
} else if (mouseLookTest == mouseLookTests.TestE) {
// Same as TestD, just with Torque/force instead of direct rotation.
rigidbody.AddRelativeTorque(clampedRotationY, clampedRotationX, clampedRotationZ);
} else if (mouseLookTest == mouseLookTest.TestF) {
// This works perfectly! Used without torque, best used with 'Freeze Rotation' enabled to avoid Torque forces interfearing with your rotational axis.
if (Input.GetKey("q")) { transform.Rotate(Input.GetAxis("Mouse Y") * -mouseSensativity, Input.GetAxis("Mouse X") * mouseSensativity, speed * Time.deltaTime); }
else if (Input.GetKey("e")) { transform.Rotate(Input.GetAxis("Mouse Y") * -mouseSensativity, Input.GetAxis("Mouse X") * mouseSensativity, -speed * Time.deltaTime); }
else { transform.Rotate(Input.GetAxis("Mouse Y") * -mouseSensativity, Input.GetAxis("Mouse X") * mouseSensativity, 0); }
} else if (mouseLookTest == mouseLookTest.TestG) {
// This works perfectly! This is truely what I was after, with 'Freeze Rotation' disabled, Torque forces apply beautifully.
if (Input.GetKey("q")) { rigidbody.AddRelativeTorque(Input.GetAxis("Mouse Y") * -mouseSensativity, Input.GetAxis("Mouse X") * mouseSensativity, speed * Time.deltaTime); }
else if (Input.GetKey("e")) { rigidbody.AddRelativeTorque(Input.GetAxis("Mouse Y") * -mouseSensativity, Input.GetAxis("Mouse X") * mouseSensativity, -speed * Time.deltaTime); }
else { rigidbody.AddRelativeTorque(Input.GetAxis("Mouse Y") * -mouseSensativity, Input.GetAxis("Mouse X") * mouseSensativity, 0); }
} else if (mouseLookTest == mouseLookTests.TestH) {
// This also works as expected, and accepts the roll/rotate options accurately.
// This was used to test/prove that X/Y rotation for looking can be 'quick' using transform.Rotate(), while leaving Z roll effected by physics/torque/drag, producing an interesting control result.
// Set rollMethod=Torque and rotationMethod=Rotate to test. -- Though I've read that using Rotate() on a Torque controlled objct, can have unpredictable results.
if (rotationType == rotationTypes.Torque) { rigidbody.AddRelativeTorque(Input.GetAxis("Mouse Y") * -mouseSensativity, Input.GetAxis("Mouse X") * mouseSensativity, 0); }
else { transform.Rotate(Input.GetAxis("Mouse Y") * -mouseSensativity, Input.GetAxis("Mouse X") * mouseSensativity, 0); }
if (Input.GetKey("q")) {
if (rollMethod == rollMethods.Torque) { rigidbody.AddRelativeTorque(0, 0, -speed * Time.deltaTime); }
else { transform.Rotate(0, 0, -speed * Time.deltaTime); }
}
if (Input.GetKey("e")) {
if (rollMethod == rollMethods.Torque) { rigidbody.AddRelativeTorque(0, 0, speed * Time.deltaTime); }
else { transform.Rotate(0, 0, speed * Time.deltaTime); }
}
}
}
}
function ClampAngle (angle : float, min : float, max : float) { if (angle < -360) angle += 360; if (angle > 360) angle -= 360; return Mathf.Clamp (angle, min, max); }
Hopefully this helps someone else sometime!
~Badger
Answer by Tetrad · May 31, 2010 at 10:19 PM
I don't have an exact answer for you, but the reason your MouseLook script breaks anything you try to do with roll is because the MouseLook script sets the transform.rotation of your object. So, depending on the order of operations of update, the MouseLook will hammer the rotation, back to be facing upwards, probably causing the "jittering" effect you're talking about.
If you open the default MouseLook script it does something like this:
// Read the mouse input axis rotationX += Input.GetAxis("Mouse X") * sensitivityX; rotationY += Input.GetAxis("Mouse Y") * sensitivityY;
rotationX = ClampAngle (rotationX, minimumX, maximumX);
rotationY = ClampAngle (rotationY, minimumY, maximumY);
Quaternion xQuaternion = Quaternion.AngleAxis (rotationX, Vector3.up);
Quaternion yQuaternion = Quaternion.AngleAxis (rotationY, Vector3.left);
transform.localRotation = originalRotation * xQuaternion * yQuaternion;
You might be able to add a Z rotation in there and just multiply them all together. I'd suggest trying that.
That's what I mentioned didn't work when mimicking $$anonymous$$ouseLook. Using transform.Rotate(rotationX,rotationY,0)
spins the camera uncontrollably, as does rigidBody.AddRelativeTorque()
and transform.localRotate *= xQuaternion * yQuaternion
. Using transform.localRotation = Quaternion.Euler(rotationY * -1, rotationX,0);
or transform.localRotation = Quaternion.AngleAxis(...)
(as $$anonymous$$ouseLook does) works for X/Y, but it sets Z. Quaternion.AngleAxis(transform.localRotaion.z*10, Vector3.forward)
just sets Z, snapping back when the key is released. I don't know how to otherwise add a Z-rotation.
Why not juts mimic what rotationX and rotationY does for your Z component? So at the end of the day you have something like
transform.localRotation = originalRotation * xQuaternion * yQuaternion * zQuaternion.
You could get the zQuaternion by keeping a rotationZ variable that you add to or subtract from depending on whatever your input variables are, and then do the AngleAxis thing appropriately (using the forward vector).
Your answer

Follow this Question
Related Questions
Make the player go into the direction I am looking at 0 Answers
Camera following Pivot not working 1 Answer
camera rotation interferes with controller rotation 0 Answers
Lock an axis from rotating 1 Answer
second "skybox" camera and fps problem 0 Answers