Correcting a Plane's Pitch Down when it Banks and Yaws

Hi all, I’m currently scripting a player controller script for a plane. I have chosen simplistic controls in that if you press a or d, the plane banks up to 40° to either side (the angle is scaled by the plane’s speed up to the 40° maximum) and yaws to either side at the same time. Pitch is controlled separately by w and s at present.

I’m controlling everything via the plane’s rigidbody.MoveRotation using Quaternion.Euler rotations. The issue is the obvious Physics situation - if the plane banks to a side and then yaws, it’s going to pitch down slightly and cause it to move downwards. I want to basically cancel this out so that it (unrealistically) just yaws left and right without pitch difference.

The following is the current method for controlling the plane via the WASD keys:

// Detect if "w" key is pressed down
if (Input.GetKey("w") == true)
{
    // Pitch the ship's rigidbody down and save its new pitch (X) angle
    shipRigidbody.MoveRotation(shipRigidbody.rotation * Quaternion.Euler(shipPitchSpeed * Time.fixedDeltaTime, 0.0f, 0.0f));
    shipPitchAngle = shipRigidbody.rotation.eulerAngles.x;

    // Detect if ship has pitched down too far and if so, clip the pitch to the maximum pitch down angle
    if ((shipRigidbody.rotation.eulerAngles.x > shipPitchDownMax && shipRigidbody.rotation.eulerAngles.x < (shipPitchDownMax + 5.0f)))
    {
        shipRigidbody.rotation = Quaternion.Euler(shipPitchDownMax, shipYawAngle, shipRigidbody.rotation.eulerAngles.z);
    }
}

// Detect if "s" key is pressed down
if (Input.GetKey("s") == true)
{
    // Pitch the ship's rigidbody up and save its new pitch (X) angle
    shipRigidbody.MoveRotation(shipRigidbody.rotation * Quaternion.Euler(-(shipPitchSpeed * Time.fixedDeltaTime), 0.0f, 0.0f));
    shipPitchAngle = shipRigidbody.rotation.eulerAngles.x;

    // Detect if ship has pitched up too far and if so, clip the pitch to the maximum pitch up angle
    if (shipRigidbody.rotation.eulerAngles.x > (shipPitchUpMax - 5.0f) && shipRigidbody.rotation.eulerAngles.x < shipPitchUpMax)
    {
        shipRigidbody.rotation = Quaternion.Euler(shipPitchUpMax, shipYawAngle, shipRigidbody.rotation.eulerAngles.z);
    }
}

// Detect if "a" key is pressed down and if so, yaw and roll the ship's rigidbody left and set boolean such that the ship has to return from roll
if (Input.GetKey("a") == true)
{
    // Yaw the ship left and save its new yaw angle
    shipRigidbody.MoveRotation(shipRigidbody.rotation * Quaternion.Euler(0.0f, -(shipYawSpeed * Time.fixedDeltaTime) * shipYawAngleMultiplier, 0.0f));
    shipYawAngle = shipRigidbody.rotation.eulerAngles.y;

    // Smoothly roll the ship left, maintaining its yaw angle
    shipRigidbody.rotation = Quaternion.Slerp(shipRigidbody.rotation, Quaternion.Euler(shipPitchAngle, shipYawAngle, shipRollAngle * shipRollAngleMultiplier), shipRollSpeed * Time.fixedDeltaTime);
    shipReturnFromRoll = false;
}
// Detect if "a" key is released and if so, set boolean so ship returns from roll
else if (Input.GetKeyUp("a") == true)
{
    shipReturnFromRoll = true;
}

// Detect if "d" key is pressed down and if so, yaw and roll the ship's rigidbody right and set boolean such that the ship has to return from roll
if (Input.GetKey("d") == true)
{
    // Yaw the ship right and save its new yaw angle
    shipRigidbody.MoveRotation(shipRigidbody.rotation * Quaternion.Euler(0.0f, (shipYawSpeed * Time.fixedDeltaTime) * shipYawAngleMultiplier, 0.0f));
    shipYawAngle = shipRigidbody.rotation.eulerAngles.y;

    // Smoothly roll the ship right, maintaining its yaw angle
    shipRigidbody.rotation = Quaternion.Slerp(shipRigidbody.rotation, Quaternion.Euler(shipPitchAngle, shipYawAngle, -shipRollAngle * shipRollAngleMultiplier), shipRollSpeed * Time.fixedDeltaTime);
    shipReturnFromRoll = false;
}
// Detect if "d" key is released and if so, set boolean so ship returns from roll
else if (Input.GetKeyUp("d") == true)
{
    shipReturnFromRoll = true;
}

// If the boolean states that the ship has to return from roll, carry out the return from roll but to the same pitch and yaw angles
if (shipReturnFromRoll == true)
{
    shipRigidbody.rotation = Quaternion.Slerp(shipRigidbody.rotation, Quaternion.Euler(shipPitchAngle, shipYawAngle, 0.0f), shipRollSpeed * Time.fixedDeltaTime);
}

// If the ship is close to having returned to its pre-roll orientation, set it to its pre-roll orientation and set the boolean that the ship has returned from roll
if (shipReturnFromRoll == true && (Quaternion.Angle(shipRigidbody.rotation, Quaternion.Euler(shipPitchAngle, shipYawAngle, 0.0f)) < 0.5f))
{
    shipRigidbody.rotation = Quaternion.Euler(shipPitchAngle, shipYawAngle, 0.0f);
    shipReturnFromRoll = false;
}

The variable shipRollAngleMultiplier is calculated as follows:

// Calculate the ship's current speed to maximum speed ratio (forced absolute magnitude)
shipCurrentSpeedToMaxRatio = Mathf.Abs(Vector3.Magnitude(shipRigidbody.velocity) / shipMaxSpeed);

The trouble I’m currently having is that if I simply try to cancel out the pitch angle, it cancels out the entire pitch angle (including the user input), not just the downward pitch due to banking. Thank you. I appreciate any help in advance.

To do a simplified flight model like this, it might be useful to independently track the aircraft heading and nose up/down as floating point values.

Then you could adjust those variables over time under your flight controls. Finally you would feed those variables into rotations to make your aircraft point a particular way, perhaps through a set of parented GameObjects, such as the root one controlling your heading, the next one down controlling your pitch, or some such construct.

In this way you could drive the apparent yaw (heading change) directly based on bank angle, as it seems you are looking for coordinate turning at all times, ie., the “simplified” part. And thus banking could be explicitly not applied to your pitch.

The downside to this approach is you won’t be able to loop-the-loop or go beyond what might be considered “reasonable” bank angles, without getting a lot of motion distortion.

ALSO, traditionally aircraft actually do require back-elevator pressure when turning in order to avoid loss of altitude, and that is a specific training requirement for airmen to learn. I understand if you don’t want it in your model, but I kinda like it in my simplistic flight model in Pilot Kurt. You can get Pilot Kurt off the appstores for Google or Apple, and it’s a one-finger flying game that does use a fairly simplistic flight model, but you DO need to pull back when you turn.

Hi Kurt, thanks for the reply. Today I had a chance to try this and it worked just fine. I changed the code so that the player input only makes changes to two empty game object transforms. The w and s keys alter the pitch of one transform (x rotation) and the a and d keys alter the yaw and roll of another transform (y and z rotations). After the yaw and roll is applied to the yaw and roll transform, the inherited pitch is killed off by setting it to 0.0f. Another method simply aligns the plane object to these two transforms and that’s it. Thank you for the suggestion!

You are very welcome! Glad it worked out. I love making flying-simulating code! It saves a lot of money not having to rent airplanes… :slight_smile: