Simple Flight Mechanics (Independent Yaw, Pitch and Roll) - Please help!

Hi all, I’ve spent the last few days mulling over this. I’m trying to achieve the same aircraft movement as in the mobile game Metalstorm, if you’ve played it before. For reference, I’m using Unity 6.0 LTS and coding in C#.

I’ve set up a simple prefab of an empty gameobject, with a 3D cube childed to it that has a rigidbody (Main Fuselage). Also childed to the parent empty gameobject, I have three empty gameobjects, which I call the transforms:

image

My player controller script is simple. It just adds a virtual joystick (on-screen for mobile). This sends the x and y player joystick input to a ship controller script, as a Vector2.

The ship controller script is as follows:

using UnityEngine;
using UnityEngine.EventSystems;

public class ShipMovement : MonoBehaviour
{
    // Ship's Rigidbody component variable and pitch, yaw and roll variables
    public Rigidbody shipRigidbody;
    public Transform transformPitch;
    public Transform transformYaw;
    public Transform transformRoll;

    // Ship's pitch, yaw and roll variables
    private Quaternion shipPitchRotation;
    private Quaternion shipYawRotation;
    private Quaternion shipRollRotation;
    private float shipRollAngle;
    private float shipPitchSpeed = 15.0f;   // TEMPORARY VALUE
    private float shipYawSpeed = 15.0f;   // TEMPORARY VALUE
    private float shipRollSpeed = 0.5f;   // TEMPORARY VALUE

    private void FixedUpdate()
    {
        // Call ship movement methods
        MoveShip();
    }

    public void PlayerShipMoveControl(Vector2 moveDirection)
    {
        // If there is a yaw input
        if (moveDirection.x != 0.0f)
        {
            // Rotate the yaw transform in yaw
            shipYawRotation = Quaternion.AngleAxis((moveDirection.x * shipYawSpeed * Time.fixedDeltaTime), Vector3.up);
            transformYaw.localRotation *= shipYawRotation;
        }

        // If there is a pitch input
        if (moveDirection.y != 0.0f)
        {
            // Rotate the pitch transform in pitch
            shipPitchRotation = Quaternion.AngleAxis((-moveDirection.y * shipPitchSpeed * Time.fixedDeltaTime), Vector3.right);
            transformPitch.localRotation *= shipPitchRotation;
        }

        // Calculate the ship roll angle based on the pitch and yaw inputs and only subtract 90° if there is input (to correct orientation)
        if (moveDirection.y != 0.0f || moveDirection.x != 0.0f)
        {
            shipRollAngle = (Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg) - 90.0f;
        }
        else
        {
            shipRollAngle = 0.0f;
        }

        // If there is no input and the pitch transform is pointing downwards (the ship is up-side-down when the player lets go of the input)
        if (shipRollAngle == 0.0f && (Vector3.Dot(transformPitch.up, Vector3.up) < 0))
        {
            // THE FOLLOWING CODE WORKS EXCEPT THE SHIP JUST FLIPS INSTANTLY:
            shipPitchRotation = Quaternion.AngleAxis(180.0f, Vector3.forward);
            transformPitch.localRotation *= shipPitchRotation;
        }

        // Rotate the roll transform with the calculated roll angle using a smooth linear interpolation
        shipRollRotation = Quaternion.AngleAxis(shipRollAngle, Vector3.forward);
        transformRoll.localRotation = Quaternion.Slerp(transformRoll.localRotation, shipRollRotation, shipRollSpeed * Time.fixedDeltaTime);
    }

    private void MoveShip()
    {
        // Rotate the ship's rigidbody to match the yaw, pitch and roll transforms (in this order, as required by Unity when working with Quaternion algebra)
        shipRigidbody.MoveRotation(transformYaw.localRotation * transformPitch.localRotation * transformRoll.localRotation);

        // Position the pitch, yaw and roll transforms on the ship's position
        transformPitch.position = shipRigidbody.position;
        transformYaw.position = shipRigidbody.position;
        transformRoll.position = shipRigidbody.position;
    }
}

The above works wonderfully well in terms of keeping the aircraft movement independent in yaw, pitch and roll. As the player moves the joystick, the aircraft rolls to the same angle and it yaws and pitches independently as well in the direction of the joystick. There is also no gimbal locking.

All seems to be solved until I get to the fact that when the aircraft is up-side-down (see lines 55 to 61 above), it doesn’t re-orient to go up-right. I want the game to automatically roll the ship 180° so that the pitch transform faces upwards as soon as the player releases the joystick. The above code appears to work but simply doesn’t as the ship immediately rotates 180° to face upwards. Literally from one frame to the next, it’s oriented upwards (it doesn’t smoothly roll to that position).

All attempts to Quaternion.Slerp it to roll into said position have failed as the movement goes haywire and the aircraft ends up pointing a random direction. If have even tried to use a single empty gameobject transform in my prefab to handle everything but then yaw, pitch and roll stop being independent of each other, which I don’t want.

Any pointers would be greatly appreciated. This is driving me mad. :sweat_smile: Thank you very much!

Here’s a complete airplane package… pretty spiffy. These are my forks, look upstream for the originals. I used it for my Pilot Kurt game and it works flawlessly.

Discussion video:

And here’s a young fellow who haxed it up a bit and
added flaps and brakes and power control… SO COOL!

His tutorial video:

Thank you. Fair enough about using the Update function.

I tried your code and modified it a little to have extra speed and such but the code doesn’t keep Yaw and Pitch independent of each other. The ship also doesn’t seem to roll at all.

Treating Yaw, Pitch, and Roll as independent Transforms is a bit unusual.
Unity’s Quaternion.Euler applies rotations in the ZXY order, so if you consider Z as forward and Y as upward, you can directly treat these as Yaw, Pitch, and Roll.

Vector3 pitchYawRoll = shipRigidbody.rotation.eulerAngles;

pitchYawRoll.y += moveDirection.x * shipYawSpeed * Time.fixedDeltaTime;
pitchYawRoll.x += moveDirection.y * shipPitchSpeed * Time.fixedDeltaTime;
pitchYawRoll.z += rollDirection * shipRollSpeed * Time.fixedDeltaTime;

shipRigidbody.MoveRotation(Quaternion.Euler(pitchYawRoll));