1
\$\begingroup\$

I'm working on a driving game, and I've been using RigidBody.MovePosition (non-kinematic) to move the car. I've read that this is not the best method for physics reasons, but I've had massive problems moving objects around with the other techniques. So far, it's been working out great, at least with horizontal movement.

However, now I'm trying to turn the car into a hover-car, and it seems MovePosition is terrible with vertical movement. Rather than trying to fight what is likely a losing battle, I'd like to get either AddForce or velocity working. I'm only concerned with the horizontal (for now).

Here are the three versions, along with the results I'm getting.

RigidBody.MovePosition:

Works great, at least for forward movement. Attempting the same technique with Vector3.up results in all kinds of broken and unpredictable behavior with collisions and physics, though.

  void Start ()
    {
    body = GetComponent<Rigidbody> ();

    forward_speed = 0f;
    forward_max_speed = .9f;
    forward_acceleration = .02f;
    forward_movement = Vector3.zero;
    rotation_multiplier = 2.5f;
    }

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

  void FixedUpdate ()
    {
    if (key_up)
      {
      if (forward_speed < forward_max_speed) forward_speed += forward_acceleration;
      }
    else if (key_down)
      {
      if (forward_speed > -forward_max_speed) forward_speed -= forward_acceleration;
      }
    else
      {
      if (forward_speed > -.1f && forward_speed < .1f) forward_speed = 0f;
      if (forward_speed != 0f) forward_speed = forward_speed * 0.9f;
      }

    if (key_left) turning_rotation = forward_speed * -rotation_multiplier;
    if (key_right) turning_rotation = forward_speed * rotation_multiplier;

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

    // forward
    forward_movement = transform.forward * forward_speed;
    body.MovePosition (body.position + forward_movement);
    }

RigidBody.velocity:

The car performs great on flat surfaces, but if it goes airborne for any reason, it falls incredibly slowly, basically letting the player fly around (but not the way I intended). Freezing X and Z rotations partially mitigates the flying, but doesn't fix the slow falling, and also causes the car to have strangely intense trouble getting up inclines. I can't fathom why assigning the velocity would mess with gravity. Unity, you've outdone yourself.

  void Start ()
    {
    body = GetComponent<Rigidbody> ();

    forward_speed = 0f;
    forward_max_speed = 50f;
    forward_acceleration = 1f;
    forward_movement = Vector3.zero;
    rotation_multiplier = .067f;
    }

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

  void FixedUpdate ()
    {
    if (key_up)
      {
      if (forward_speed < forward_max_speed) forward_speed += forward_acceleration;
      }
    else if (key_down)
      {
      if (forward_speed > -forward_max_speed) forward_speed -= forward_acceleration;
      }
    else
      {
      if (forward_speed > -.1f && forward_speed < .1f) forward_speed = 0f;
      if (forward_speed != 0f) forward_speed = forward_speed * 0.9f;
      }

    if (key_left) turning_rotation = forward_speed * -rotation_multiplier;
    if (key_right) turning_rotation = forward_speed * rotation_multiplier;

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

    // forward
    body.velocity = transform.forward * forward_speed;
    }

RigidBody.AddForce:

This one almost works, but the car slides around like it's Disney on ice. Fun, but not right. After changing direction, it seems to take a half second for the car's movement or the force direction to catch up with the rotation, so it slides to the side pretty badly. The same thing happens with gas and brakes. There's a noticeable and frustrating delay. I also had the same problem when I tried vertical movement with this - it just refuses to stop at the elevation I tell it to. I've tried all four of the different ForceMode settings, and aside from having to massively adjust the forward_max_speed and forward_acceleration values, they feel mostly the same. AddForce is the same method used in the CharacterFirstPerson scene in the Standard Assets Project, but I noticed the exact same control delay in that project as well. The guy doesn't start and stop on a dime.

  void Start ()
    {
    body = GetComponent<Rigidbody> ();

    forward_speed = 0f;
    forward_max_speed = 70f;
    forward_acceleration = 5f;
    forward_movement = Vector3.zero;
    rotation_multiplier = .02f;
    }

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

  void FixedUpdate ()
    {
    if (key_up)
      {
      if (forward_speed < forward_max_speed) forward_speed += forward_acceleration;
      }
    else if (key_down)
      {
      if (forward_speed > -forward_max_speed) forward_speed -= forward_acceleration;
      }
    else
      {
      if (forward_speed > -.1f && forward_speed < .1f) forward_speed = 0f;
      if (forward_speed != 0f) forward_speed = forward_speed * 0.9f;
      }

    if (key_left) turning_rotation = forward_speed * -rotation_multiplier;
    if (key_right) turning_rotation = forward_speed * rotation_multiplier;

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

    // forward
    forward_movement = transform.forward * forward_speed;
    body.AddForce (forward_movement, ForceMode.Acceleration);
    }

What I'd like to do is get either the RigidBody.AddForce or RigidBody.velocity method working properly so everything is on the up and up with the physics engine. Unfortunately, I've run out of ideas and I'm too frustrated with Unity right now to see the problem clearly.

\$\endgroup\$
2
\$\begingroup\$

Rigidbody.MovePosition does the same as setting transform.position, but calculates collision along the way.

Rigidbody.velocity is the distance and direction the Rigidbody travels each second, including all external forces. So setting this directly will indeed disable gravity.

Rigidbody.AddForce is what you need, but vehicle physics aren't as simple as you think. You will want to check if the wheels are grounded, you can do this by adding Raycasts where the wheels are. Then you use RigidBody.AddRelativeForce to move the car forward, and AddRelativeTorque for steering. You can stabilize the car using this:

rigidbody.AddRelativeForce(Vector3.left * localVelocity.x, ForceMode.VelocityChange);

This will cancel out all horizontal movement. If you want the car to drift a little, you could change it like this:

float drift = 1.2f;
rigidbody.AddRelativeForce(Vector3.left * localVelocity.x / drift, ForceMode.VelocityChange);

But what I can recommend most is watching this video on raycast vehicles. It has a very good explanation on developing simple vehicles that feel good to drive.

\$\endgroup\$
  • \$\begingroup\$ I wasn't sure at first where you were going with that code snippet, but the video cleared it up about halfway through. Thanks! \$\endgroup\$ – Nightmare Games Mar 21 '18 at 23:16
0
\$\begingroup\$

For a car simulation, generally AddForce is the recommended method.

To solve the sliding issue, take a look at the physics materials. There, you will find an option to adjust friction.

Documentation for physics materials: https://docs.unity3d.com/Manual/class-PhysicMaterial.html

\$\endgroup\$
0
\$\begingroup\$

Based partly on Robin van Ee's answer, I was able to fix movement on both AddForce and velocity, and get them working "properly" (according to me, at least).

It seems that when Unity applies force or velocity, that force doesn't stay local to the direction the object is travelling, and instead gets converted to a global force. When the object changes direction, the force (or velocity) doesn't change direction with it, and the physics engine take a few seconds to interpolate between the old force and the new force. Meanwhile, your plane/boat/dog/unicycle has left the map sideways.

Here's what I did.

AddRelativeForce Method:

I'm not sure if it matters that I switched from AddForce to AddRelativeForce, but it does save me having to transform the local vector to the world vector. The big change was stopping the undesired x-axis movement. I'm constantly checking for force on the x-axis and applying an equal and opposite force to cancel it out. I also had to do a similar compensation for forward / back movement, since Unity's arbitrary interpolation was messing up my desired acceleration and deceleration. This seems a bit hacky, and I still think there ought to be a more straight-forward (pun intended) way of applying force that stays local and changes instantly, but this works for now.

  Mathf.Clamp (forward_speed, -forward_max_speed, forward_max_speed);
  float forward_speed_adjustment = forward_speed - transform.InverseTransformDirection (body.velocity).z;
  float side_speed_adjustment = -transform.InverseTransformDirection (body.velocity).x;
  body.AddRelativeForce (side_speed_adjustment, 0f, forward_speed + forward_speed_adjustment, ForceMode.VelocityChange);

Velocity Method:

The mistake I was making here was trying to set velocity directly, which was overwriting all movement, including the vertical movement as caused by gravity. I also had to stop the x-axis movement, which was much easier here.

  Mathf.Clamp (forward_speed, -forward_max_speed, forward_max_speed);
  Vector3 current_velocity_vector = transform.InverseTransformDirection (body.velocity);
  current_velocity_vector.z = forward_speed;
  current_velocity_vector.x = 0f;
  body.velocity = transform.TransformDirection (current_velocity_vector);

With both methods, the car now feels exactly like it did under MovePosition, I've got complete control over the car's motion, and the movement code is (theoretically) completely physics-friendly.

\$\endgroup\$

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.