Unity collisions often have exaggerated physics, sending the player flying off the scene

I'm building a car driving game in Unity 2017.3.1f1.

The car movement works fine over a flat surface and even over gently rolling terrain. It's a city environment, and there are lots of small lips and angles where the street meets the sidewalk, where terrain ends, and around ramps and other features.

When the car hits one of these small seams in the ground, 60% of the time it flies up in the air over the buildings, rotating like crazy. It often flies off the game world. When going off a ramp, sometimes the car will continue driving forward and rotating up on the x-axis in mid-air, as if it's going through an invisible loop-the-loop.

I'm not sure what's causing the extreme physics reactions.

Here's the car object:

And here's the car's movement code:

  void Update ()
{
if (Input.GetKeyDown (KeyCode.UpArrow) && !key_up) key_up = true;
if (Input.GetKeyUp (KeyCode.UpArrow) && key_up) key_up = false;

if (Input.GetKeyDown (KeyCode.DownArrow) && !key_down) key_down = true;
if (Input.GetKeyUp (KeyCode.DownArrow) && key_down) key_down = false;

if (Input.GetKeyDown (KeyCode.LeftArrow) && !key_left) key_left = true;
if (Input.GetKeyUp (KeyCode.LeftArrow) && key_left) key_left = false;

if (Input.GetKeyDown (KeyCode.RightArrow) && !key_right) key_right = true;
if (Input.GetKeyUp (KeyCode.RightArrow) && key_right) key_right = false;
}

//////////////////////////////////////////////////

void FixedUpdate ()
{
if (on_ground)
{
// driving
if (key_up)
{
if (current_speed < top_speed) current_speed += acceleration;
}
else if (key_down)
{
if (current_speed > -top_speed) current_speed -= acceleration;
}
else
{
if (current_speed > -.1f && current_speed < .1f) current_speed = 0f;
if (current_speed != 0f) current_speed = current_speed * 0.9f;
}

// turning
if (key_left) turning_rotation = current_speed * 2.5f * -1f;
if (key_right) turning_rotation = current_speed * 2.5f;
}

// rotation
transform.Rotate (0f, turning_rotation, 0f);

forward_movement = transform.forward * current_speed;
body.MovePosition (body.position + forward_movement);

// reset values
turning_rotation = 0f;
}

//////////////////////////////////////////////////

void OnCollisionStay ()
{
update_on_ground ();
}

//////////////////////////////////////////////////

void OnCollisionExit ()
{
update_on_ground ();
}

//////////////////////////////////////////////////

void update_on_ground ()
{
// check if car is grounded
Vector3 ray_start = transform.position + new Vector3 (0f, 1f, 0f);
if (Physics.Raycast (ray_start, Vector3.down, 1.1f))
{
if (!on_ground) on_ground = true;
}
else if (on_ground) on_ground = false;
}
}


Any ideas are appreciated.

NOTES:

There was a ton of extra code in here attempting various fixes, including trying to manually restrict rotations on the z and x axes, to reduce velocity while airborne, to use several alternative types of movement code, and to increase the car's mass to a ridiculous amount. None of them ever worked, so I've removed them here.

By request, other solutions I've tried:

• Changing the car's mass seems to have no effect on anything.

• Trying to clamp the car's rotations led down an enormous rabbit hole that would require a separate question to resolve, but the short version is that Euler angles don't seem to work correctly and I don't understand quaternions (yet).

• Gradually reducing the car's velocity in mid-air helped slightly in keeping it airborne shorter, but not enough to stop it from launching upward like a rocket in the first place or bouncing like crazy when it hits the ground.

• I've also messed around with using transform.translate() rather than RigidBody.MovePosition(), but this doesn't seem to affect the bounciness, gravity, or rotations of the car. In fact, it sometimes makes the collisions glitchier.

• The last thing I tried was using the car's direction and velocity to figure out its global, separate x,y,z velocities and move it globally based on only the x and z. My thought process was that if the car is going off a ramp, then the nose is pointed up, which means it's technically still moving in that direction relative to itself (transform.forward) when it leaves the ramp, even though gravity is pushing it down on the global y axis. I got this working correctly, but it didn't have a noticeable impact on the physics.

• Freezing the X and Z rotations in the car's Unity inspector actually does work and addresses all the physics problems. However, it looks pretty damn silly when the car goes up a ramp or over terrain and stays perfectly level no matter what.

All the world geometry is currently just built from Unity primitives.

• I'm not sure why you have the key_down, key_left, etc. variables, Input.GetKeyDown does the exact same thing. Feb 23 '18 at 2:55
• @Bálint That's how I've always done it in other game engines. I'm still figuring out Unity. Feb 23 '18 at 2:59
• For troubleshooting physics issues, I recommend checking out Bennett Foddy's GDC 2015 talk on understanding & mitigating common simulation errors. Can you tell us a bit more about the "several alternative" solutions you've tried? Without more details, it's hard to know whether an answer we'd propose is one you've already tried and rejected. Feb 23 '18 at 3:00
• Just curious, did you try to clamp velocity and angluarVelocity? Jul 13 '18 at 15:56
• @CandidMoon: The velocity never exceeded the limits set by top_speed. Doing a straight clamp on a value never seems to work for me in Unity, although my current code (now months later) does manually limit both angularVelocity and rotations. Really, it was the gravity and/or scale of my models that caused the problem. Jul 16 '18 at 21:48

• The correct scale for realistic gravity at the default settings is 1 unit = 1 meter. Then the default gravity represents exactly the 9.81 m/s2 average acceleration due to gravity on the Earth's surface. Feb 23 '18 at 11:47