Unity Rotation Gotchas And How To Avoid Them
Understanding Quaternion Rotations
Quaternions are a mathematical concept that can represent rotations in 3D space using 4 components – x, y, z, and w. In Unity, quaternions are commonly used to store and manipulate the rotations of GameObjects.
Conceptually, a quaternion rotation represents an axis in 3D space to rotate around, and an angle to rotate by. The x, y, and z components define the axis, while the w component defines the angle (in quaternion math, w is often called the “real” component).
For example, a quaternion with (x=1, y=0, z=0, w=0.5) would represent a rotation of 0.5 radians (about 30 degrees) around the x-axis. Visually, this would rotate an object to be “tilted” up or down.
Quaternions have several advantages over other rotation representations like Euler angles:
- They avoid gimbal lock issues
- Rotations and interpolation are smoother
- Concatenating quaternion rotations is fast
The main downside is that quaternions are less intuitive to visualize than raw angle values. However, we can visualize and debug quaternions in Unity with some specialized tools.
Visualizing Quaternion Rotations
To understand what a quaternion is doing to the rotation of your GameObject, you can visualize quaternions in the Scene view by enabling rotation gizmos.
With a GameObject selected, in the Scene view toolbar:
- Tick the Rotation button to enable rotation gizmos
- Set the Rotation Handles dropdown to “Quaternion”
This will display the current quaternion rotation using an axis and arc visual. You can grab each gizmo axis to manipulate the rotation for testing.
Another option is to visualize quaternion curves in animation clips. Having the curve view open with quaternion rotation curves visible provides an informative visual of changes over time.
Common Mistakes When Using Quaternions
Some common mistakes when working with quaternion rotations include:
- Assuming quaternion rotation order doesn’t matter – order impacts final transform
- Mixing up coordinate system handedness – consistent right-handed coords are key
- Resetting rotations incorrectly – use Quaternion.identity, not new Quaternion()
In general, always keep coordinate systems and quaternion manipulation orders consistent throughout your codebase to avoid subtle rotation errors accumulating.
Rotating GameObjects in Unity
There are two primary ways to rotate GameObjects at runtime in Unity – by manipulating the Transform directly, or by using physics forces like Rigidbody.MoveRotation.
Using Transform.Rotate vs Rigidbody.MoveRotation
Transform.Rotate should be used for ordinary non-physics GameObjects to apply a direct rotation.
Examples include:
- Implementing player controller turning
- Rotating aircraft or space ships
- Animating transitions between angles
Rigidbody.MoveRotation applies a physics force to achieve a target rotation over time based on attributes like mass. This is best for physics objects.
Examples include:
- Rotating a powerup bonus after its picked up
- Applying explosion impacts or collision reactions
- Simulating torque forces
In some cases you may need to sync Transform and Rigidbody rotations manually with functions like Rigidbody.MovePosition to overcome physics quirks.
Euler Angles vs Quaternion Rotation
Both Transform and Rigidbody rotations can take Euler angles or quaternions. Euler angles represent rotation states using degrees, which is more intuitive.
However, direct euler usage can cause rotation issues over time due to mathematical singularities and floating point error accumulation.
The best practice is to convert angles to/from quaternions at the interface boundaries of components:
- Take raw input for euler angles for an intuitive authoring workflow
- Immediately convert to quaternions for internal processing
- Convert back to euler only when displaying raw data to users
This avoids the pitfalls of euler math while keeping the workflow simplicity, giving you the best of both worlds.
When to Use Which Rotation Method
In summary, use Transform.Rotate for direct animated rotations, Rigidbody.MoveRotation for physics torque rotations, while using quaternions internally within components to ensure robust calculations.
Fixing Erratic Rotations
Strange or erratic rotation can happen when rotating nested object hierarchies or accumulating values incorrectly over time due to floating point error.
Issues With Rotating Nested GameObjects
Child GameObjects inherit rotations from their parent. Rotating child objects or parents while nested will cause both current rotation and end result to behave unexpectedly.
For example, consider a spaceship with a turret child object. Rotating only the turret will work fine in local space. But spaceship parent rotations alter the turret space over time leading to compounding issues.
There area few ways to overcome this:
- Maintain independent rotation components for each GameObject’s local rotation needs
- Counter-rotate child objects whenever parents are rotated
- Detach child objects before rotating parents
Applying these techniques appropriately for your hierarchy avoids entanglement and unintended rotations.
Resetting Rotations to Avoid Accumulation
Due to how floats store data internally, iterative math over long periods of time can accumulate enough small fractional errors to visibly alter GameObject rotations.
For example, endlessly incrementing a rotation over thousands of frames may end up rotating an object fully around when it should only rotate partially.
To avoid this, periodically reset accumulated rotations back to your expected base value like 0 or 360 degree increments:
void Update() { // Rotate 0.1 degrees per frame transform.Rotate(Vector3.up, 0.1f); // Reset after 100 frames if(frameCount % 100 == 0) { transform.rotation = Quaternion.identity; } }
This resets any internal float discrepancies to ensure your values don’t undergo “rotation drift” unexpectedly.
Examples of Resetting and Nested Rotations Done Right
Here is another example for nesting a turret under a spaceship GameObject, using the techniques described above:
public class Spaceship : MonoBehaviour { public Transform turret; void RotateSpaceship(Vector3 eulerAngles) { // Store any existing parent rotation Quaternion curParentRot = transform.rotation; // Zero out spaceship rotation transform.rotation = Quaternion.identity; // Perform spaceship rotation transform.Rotate(eulerAngles); // Counter-rotate child to undo parent change turret.rotation = curParentRot * Quaternion.Inverse(turret.rotation); } void RotateTurret(Vector3 eulerAngles) { // Reset turret rotation turret.rotation = Quaternion.identity; // Rotate turret turret.Rotate(eulerAngles); } }
This example decouples the rotations, avoiding entanglement issues. It also resets rotations before application to avoid floating point problems.
Applying these two simple techniques makes all rotation behavior reliable and decoupled across nested GameObjects.
Smoothing Out Rotation Jitter
Sudden or erratic jumps during rotation animation sequences generates unpleasant visual noise distracting from gameplay.
Fortunately, smoothing algorithms can be added to ease rotations toward target values gradually.
Using Interpolation for Smooth Rotations Over Time
Interpolation refers to gradual blending between two values over time according to some progression curve. Applying interpolated blending to quaternion rotations yields smooth results.
For example, Quaternion.Slerp smoothly rotates from one quaternion toward another based on a 0-1 lerp progression factor each frame. Higher layer components like Animation, Timeline, and Cinemachine use interpolated blending under the hood too.
Example snippet to smoothly rotate toward a target over 2 seconds:
Quaternion target = Quaternion.Euler(30, 0, 0); float duration = 2f; void Update() { float lerp = Time.deltaTime / duration; transform.rotation = Quaternion.Slerp(transform.rotation, target, lerp); }
The key thing is that interpolated blending prevents hard snapping between angles, eliminating jitter.
Configuring Interpolation Options
Different curve patterns beyond linear can be used, with easing in/out generating more natural motion.
AnimationCurve within the animation system allows crafting custom curve presets to reuse. Or math formulas can directly shape progressions like with SmoothStep.
Duration also affects interpolation perceptibility. Short times should use linear while longer rotations can apply acceleration curves for natural starts and ends.
Experiment with different curve types, durations and lerp rates until rotations look smooth without distracting wavering artifacts.
Sample Code for Smoothed Rotations
Here is one example for a reusable smoothing system to eliminate jitter:
public class SmoothRotate : MonoBehaviour { public float duration = 1f; public AnimationCurve curve = AnimationCurve.EaseInOut(0, 0, 1, 1); Quaternion target; float startTime; public void BeginRotation(Quaternion rot) { target = rot; startTime = Time.time; } void Update() { float progress = (Time.time - startTime) / duration; progress = curve.Evaluate(progress); transform.rotation = Quaternion.Slerp( transform.rotation, target, progress ); if(progress >= 1f) { transform.rotation = target; } } }
This general purpose approach works for any GameObject, providing smoothed rotations with duration and curve flexibility simply by calling BeginRotation()
.
Debugging Strange Rotations
Despite best practices, weird rotation issues still happen. When they do, pinpointing causes requires digging into current configuration.
Tools for Visualizing Current GameObject Rotations
Enable Scene view quaternion rotation gizmo visualizations described earlier while running suspect logic.
Also output current rotation states periodically via print(transform.rotation)
to check values match expectations.
Graphing values under Monitor components provides informative time-based visualization too. Watch for spikes or unexpected jumps indicating processing flaws.
Identifying Odd Rotation Values
Scan graphs and console logs with issues for patterns like:
- Large occasional rotations causing pops between frames
- Gradual drift over time indicative of accumulation error
- Sudden snaps between very different angles
Compare supposedly synced GameObjects for mismatches revealing linkage issues. If using interpolation, ensure duration and curve values are appropriate.
Techniques for Debugging Rotation Issues
Beyond visualizations and logging, debug tactics include:
- Temporarily removing scripts piecemeal until issue disappears, to identify problem component
- Splitting too-complex scripts into smaller single responsibility scripts
- Manually forcing rotations via Inspector during playmode to check logic branches
- Stepping through code line-by-line when problems occur
Take time to meticulously investigate each part of your rotation update pipeline – calculation, application, interpolation. Test assumptions!
As usual, decompose to simplify, print key data, visualize results, and slowly build back up while carefully confirming expected behavior at each stage.
Key Takeaways and Best Practices
Rotating objects in Unity takes some care to ensure expected behavior over long durations and nested hierarchies.
Summary of Key Learnings
Key lessons include:
- Use quaternions for robust internal handling
- Reset rotations to address floating point limitations
- Handle child object rotations separately from parent spaces
- Smoothing via lerp/slerp helps avoid jitter
Understanding engine math idiosyncrasies guides optimal rotation structure for runtime stability.
Best Practices for Unity Rotations
In summary, adhere to the following core best practices:
- Favor Quaternion over Euler angles
- Interpolate rotations for smoothness
- Reset accumulated values often
- Decouple nested GameObject rotations
- Debug issues early and methodically
Following these rules of thumb will help tame rotation chaos!
Further Resources for Managing Rotations
For more help managing rotations robustly, check these out:
- Official Unity Quaternion documentation
- Unity Asset – Advanced Rotator
- Blog – Rotation gotchas in Unity
- YouTube tutorials on Quaternion interpolation
With quaternions demystified and awareness of key issues, you can implement stable predictable GameObject rotations handling any application needs.