Rotations don't work the same as translations. They're order-dependent and their axes are not globally orthogonal - a small rotation on one axis begins to affect the results of subsequent rotations, even when you're compounding rotations in world space (since the orientation of the object relative to the rotation axis changes). See this answer for some animated examples.
To fix this, it often works better to separate your destination from your current rotation. Then you can change your destination in discrete steps, while your current rotation plays catch-up at a controllable speed. As a bonus, this also makes it easier to introduce easing curves for a smoother/juicier feel. :)
// Measured in turns per second for ease of use.
public float rotationSpeed = 1f;
Quaternion _targetRotation = Quaternion.identity;
void Update() {
if(Input.GetButtonDown("PitchPlus"))
_targetRotation = Quaternion.Euler(90, 0, 0) * _targetRotation;
if(Input.GetButtonDown("PitchMinus"))
_targetRotation = Quaternion.Euler(-90, 0, 0) * _targetRotation;
if(Input.GetButtonDown("YawPlus"))
_targetRotation = Quaternion.Euler(0, 90, 0) * _targetRotation;
if(Input.GetButtonDown("YawMinus"))
_targetRotation = Quaternion.Euler(0, -90, 0) * _targetRotation;
if(Input.GetButtonDown("RollPlus"))
_targetRotation = Quaternion.Euler(0, 0, 90) * _targetRotation;
if(Input.GetButtonDown("RollMinus"))
_targetRotation = Quaternion.Euler(0, 0, -90) * _targetRotation;
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
_targetRotation,
rotationSpeed * 360f * Time.deltaTime);
}
This could hypothetically still accumulate small errors over many rotations due to floating point rounding (though with pure 90-degree rotations it should be OK as long as Unity's internal methods are numerically stable). If that turns out to be a problem, you can replace the _targetRotation
with something like an orientation struct that models just the 24 orientations you care about.