Flight control without torque/force

The goal:

No physics flying object rotating around it’s local z and x axis based on joystick input. So if I pull back, it goes back by a fixed angle. If I push right, it rolls right by fixed angle increments. I don’t want to use forces since I want the controls to feel very “tight” without having to counteract all the forces to straighten out the object.


The problem:

I can get it to work along their independent axis. If I push only right at the start, it rotates correctly. If I push only back, it does it correctly. When I start combining the two (doing a bank turn for example) it all goes wack.

I’ve been hammering away for a while trying to get this to work right, but it just doesn’t seem to want to work as expected. For some reason when the transform’s “forward” is no longer along the z plane, pulling back has very different behavior than just pulling back. Gifs for reference (you’ll note in the second gif, I get off the x and z plane, and when pulling straight back or forward, left or right (values at bottom left of recording) the square moves completely off axis.):

Regular single-axis movements

Irregular movements when two axis are combined


The code:

  public Rigidbody body;
  public int SPEED = 20;
  public float tiltAngle = .5f;
  public Text debugText;

  private float previousX;
  private float previousY;
  private float previousZ;

....

void FixedUpdate() {
    Vector3 forward = body.transform.forward;
    Vector3 right = body.transform.right;
    // static forward movement (working fine)
    body.velocity = new Vector3(SPEED * forward.x, SPEED * forward.y, SPEED * forward.z);
    // Inverse tilt angle so it matches with roll left - left on thumbstick
    float tiltAroundZ = Input.GetAxis("Horizontal") * -tiltAngle;
    float tiltAroundX = Input.GetAxis("Vertical") * tiltAngle;
    // Modifying tilt around axis to be relative to transforms local axis by
    // using it's identity vectors. Likely where the issue is.
    previousX += (tiltAroundX * right.x) + (tiltAroundZ * forward.x);
    previousY += (tiltAroundX * right.y) + (tiltAroundZ * forward.y);
    previousZ += (tiltAroundX * right.z) + (tiltAroundZ * forward.z);
    Quaternion target = Quaternion.Euler(previousX, previousY, previousZ);
    // Removed slerp since I felt the imprecision might build up from it's actual position
    // and the previous x/y/z. Am I wrong in this assumption?
    transform.rotation = target;
  }

Okay, so for full rotation around all three axes, you’re going to need a more elegant solution than euler angles.

What I would recommend is to use the euler angles to make a quaternion for DELTA rotation, or change in rotation. Then simply multiply that on top of your existing rotation quaternion.

So, basically, you want to keep your rotation stored as a quaternion, rather than component euler floats.

Alright, for posterity here’s what I eventually settled on. Quaternions weren’t working, and I think it’s because I kept hitting the gimbal lock problem since my math is bad.

I turned to torque like I had originally ignored, but to get the behavior I want, I reset angular velocity back to 0 every frame. This gives me the sharp start and stop rotation around the appropriate axis without the constant rolling of using forces.

Here’s the working code:

void FixedUpdate() {
    Vector3 forward = body.transform.forward;
    Vector3 right = body.transform.right;

    body.velocity = new Vector3(SPEED * forward.x, SPEED * forward.y, SPEED * forward.z);
    float tiltAroundZ = Input.GetAxis("Roll") * -tiltAngle;
    float tiltAroundY = Input.GetAxis("Horizontal") * tiltAngle;
    float tiltAroundX = Input.GetAxis("Vertical") * tiltAngle;

    body.angularVelocity = Vector3.zero;

    body.AddRelativeTorque(tiltAroundX, tiltAroundY, tiltAroundZ);
  }

No more needing to save values from previous frames. Thanks for the help.