I have a knob with RigidBody. It is constrained against movement in X, Y, or Z, and also constrained against rotation in Y or Z. It is allowed to rotate around X.
When the player presses the button down, code sets the RigidBody to isKinematic == true, and then rotates the knob around X in response to mouse movement. The effect is to let the player spin the knob via click-and-drag of the mouse.
When the player releases the mouse button, code sets the RigidBody to isKinematic == false. Also, the code keeps the amount of rotation applied in the previous FixedUpdate, converts this to an equivalent angular velocity, and sets the RigidBody’s angularVelocity to this amount. The effect is for the knob to continue spinning for a while due to its angular momentum after the player releases the mouse button if the mouse was in motion when the player released it.
This all works spectacularly well. Now I want to add a sound effect that will cause the knob to emit a “clink” every time it rotates by a certain number of degrees. I tried this trick:
if (Quaternion.Angle(latestClink, transform.localRotation) > clinkAngle)
{
audioSource.PlayOneShot(clink, 1);
latestClink = transform.localRotation;
}
This works, after a fashion. Every time the knob’s localRotation exceeds clinkAngle when measured against the localRotation as of the previous clink, it sounds a clink and stores the localRotation into lastestClink. The next time the localRotation creates an angle greater than clinkAngle when measured against what the localRotation was at the last clink (which is stored in latestClink), it does all this again.
There are (at least) two problems with this: First, the angle of the knob for each clink isn’t precise. The clinks happen after the knob has rotated more than clinkAngle away from the last clink. That’s going to be some number near clinkAngle, but always greater and never necessarily by the same amount (depending on how fast the knob is spinning). Second, when the knob makes the clink sound, if the player reverses direction immediately and spins the knob back the other way, no clink will be heard until the knob spins more than clinkAngle again. The player is probably going to expect there to be a clink sound whenever the knob passes through a certain angle. That is, if the player is turning the knob clockwise and hears a clink. the player will expect that immediately turning the knob counterclockwise will generate the same clink again, kind of like the gizmos that make the clicking sounds on the Wheel of Fortune.
My trick does a nice job of simulating a sort of hidden mechanism, perhaps the tumblers in a safe. I can use that when it is appropriate, but it’s not like the gizmos on the Wheel of Fortune. If I knew the exact angle of rotation, I could just track that and sound a clink every time it crossed a multiple of clinkAngle. But the wacky way Quaternions get decomposed into Euler angles makes that unreliable. I could add up the angular changes of the knob, by summing the rotations when it is in isKinematic == true, and the angular velocities at each FixedUpdate when isKinematic == false, but that’s going to accumulate round-off error pretty fast.
How do I tell when a RigidBody has rotated past a multiple of a fixed angle?