2
\$\begingroup\$

I'm making a 3D space shooter in Unity (think Star Fox). The ship, when I move it around the map, should rotate slightly (smoothly) in the direction I'm controlling it.

I have smoothness mostly working. When I press an arrow key, the ship banks in that direction, and when I release it, it smoothly returns to the default rotation.

However, if I quickly change the direction it's rotating before it has the chance to get back to the default rotation, instead of rotating from "banking left" to "banking right", for example, it instantly snaps to the default rotation before beginning the smooth rotation.

Here's what it looks like:

Ship changing rotation abruptly when it changes direction mid-flight

In addition, when I connect a joystick (instead of using arrow keys) to control the ship, I get a different problem: The ship has literally no smoothing at all, rotating one-to-one with the direction the joystick is rotated in. Here's what that looks like:

Ship snapping around with rotation with no smoothing at all

Here's the code that controls ship movement:

public bool inverted = true;
public float speed = 20; //speed modifier of horizontal/vertical movement
public float rotSpeed = 120; //speed modifer of rotation (ship tilting)
public float ZRotMax = 30f;
public float YRotMax = 50f;
public float XRotMax = 30f;
public float width = 30;
public float height = 15;
public float centerDriftStrength = 5; //lower is stronger, higher is weaker.

// Update is called once per frame
void Update () {
    //Get input
    float controlHorizontal = Input.GetAxis("Horizontal");
    float controlVertical = (inverted ? -Input.GetAxis("Vertical") :
        Input.GetAxis("Vertical"));

    //Add mild center drift
    float moveHorizontal = controlHorizontal - ((transform.position.x)
        / (width * centerDriftStrength));
    float moveVertical = controlVertical - ((transform.position.y)
        / (height * centerDriftStrength));

    //Modify position
    transform.position += (new Vector3(moveHorizontal, moveVertical, 0)
        * speed * Time.deltaTime);

    //Cosmetic rotation based on controller input
    Quaternion oldRot = Quaternion.identity;
    Quaternion newRot = Quaternion.Euler(XRotMax * -controlVertical,
        YRotMax * controlHorizontal, ZRotMax * -controlHorizontal);
    transform.rotation = Quaternion.Slerp(oldRot, newRot, 
        Time.deltaTime * rotSpeed);
}

In particular:

transform.rotation = Quaternion.Slerp(oldRot, newRot, Time.deltaTime * rotSpeed);

Seems to be what I need to look at.

rotSpeed should cap the speed of rotation on the ship, so that it can't instantly rotate in a different direction. Right now, when rotSpeed is less than 120 or so, the ship twitches while rotating, and if it's particularly low, like 10, the ship barely rotates at all.

(The rotSpeed variable doesn't seem to be working anyways -- setting it to 30000 or something doesn't make the rotation any faster.)

Basically, I want the ship to always rotate smoothly, never jumping instantly from one rotation to the next. I'm not sure if I need to use Slerp for this, or if there's some better smoothing method available to Unity.

Thanks, and if anybody knows of other ways to smooth rotation between constantly-changing Vector3s/Quaternions, please tell me!

\$\endgroup\$
1
\$\begingroup\$

You never told Unity you wanted the plane to rotate from its current rotation. By setting oldRot to the identity quaternion, you've said "only ever start from the default orientation, no matter how the plane was oriented last frame"

Secondly, you haven't described a rate of rotation over time. You don't store any kind of rotation progress that can accumulate frame to frame — you're choosing your bank angle using just the time since the last frame (Time.deltaTime) and snapping to it immediately — which means at high framerates your plane will bank less, and if the framerate drops it will bank more sharply.

Overall, it looks like what you really want is something like:

transform.rotation = 
Quaternion.RotateTowards(
       transform.rotation, // start from our current orientation
       newRot, // blend toward the target rotation
       Time.deltaTime * rotationSpeed // turn at most rotationSpeed degrees per second
       );

Here, we're using transform.rotation to accumulate change over multiple frames, always starting from where we are now.

This will turn at a constant rate until it reaches the target orientation, which can feel abrupt / mechanical. For a smoother feeling, you can scale rotationSpeed down as you change directions or close in on the desired angle.

\$\endgroup\$
  • \$\begingroup\$ It's a good answer +1, though I think OP (if we forget about Quaternion.identity - it's obviously bad for this situation) just set rotationSpeed to a very high value that even after multiplication by Time.deltaTime it was 1, so it snapped right away. I don't know what it has to do with a joystick, but with the arrow keys it worked for him, which is kind of odd. He might have got lucky identity in some way, but it doesn't make sense for me. Maybe he get's snapped to middle because the value is too small - and it's aligning with axis by Quaternion.identity? Interesting. \$\endgroup\$ – Candid Moon _Max_ Mar 1 '17 at 0:39
  • 1
    \$\begingroup\$ It's not surprising that it gave an appearance of smoothness over time with the keyboard. When emulating an analog stick with keyboard keys, Unity smoothly blends the input over time (to a configurable degree) so there's a delay between full-left and full-right, often longer than it takes to snap an analog stick throug its full range. What OP sees with keyboard input is this blending effect on the input side, not anything accomplished by the code in the question, since this code does not blend over time. \$\endgroup\$ – DMGregory Mar 1 '17 at 0:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.