3
\$\begingroup\$

I am currently trying out Unity 3D and I've run into a problem. The problem is I have a car that moves along a road and then when it reaches a corner I would like it to turn the corner and have the model rotate based on its current velocity.

Here is the code I have for moving the object forward:

_velocity += (_velocity * Time.deltaTime) + (_acceleration / 2f) * Time.deltaTime * 2f;
_velocity = Mathf.Clamp(_velocity, 0f, MaxVelocity);
Vector3 position = this.transform.position;
position += -this.transform.forward * _velocity * Time.deltaTime;
this.transform.position = position;

The variables are declared as class members:

private const float MaxVelocity = 10;
private float _velocity;
private float _acceleration;

This appears to work OK for moving forward. If I applied a rotation it may look OK for a certain velocity, but if the velocity changes, then the rotation seems to quick or to slow.

What I would like (but completely failing at accomplish) is when it comes time to turn, then I would look at the velocity and calculate a smooth rotation so when an object performs a turn it will always end up at the same point regardless of how quick it approaches the corner.

I have tried a number of options, over countless hours, but I'm not having any luck!

Any ideas?

UPDATE #1:

Below is a half-way solution I got, but for now I will add it here and leave the question open for a bit in case someone can provide a better approach.

From what I worked out a LERP rotation from the current direction to the new direction did the trick (kind of!). So, I created a behaviour called VehicleController and in the Awake method I added this:

_target = this.transform.rotation * Quaternion.Euler(0f, 90f, 0f);

This gave me the target rotation relative to my current rotation, i.e. my rotation would have to equal _target for me to be facing in the desired direction. I then added the following to the FixedUpdate method:

//  Rotate
if(_doTurn)
{
    this.transform.rotation = Quaternion.Lerp(this.transform.rotation, _target, Time.fixedDeltaTime * _velocity);
}

This performed the actual rotation. The key part was setting the third parameter Time.fixedDeltaTime * _velocity. This meant that my rotation speed matched my velocity.

My final FixedUpdate method looked like this:

//  Forward
_velocity += (_velocity * Time.deltaTime) + (_acceleration / 2f) * Time.fixedDeltaTime * 2f;
_velocity = Mathf.Clamp(_velocity, 0f, MaxVelocity);
Vector3 position = this.transform.position;
position += -this.transform.forward * _velocity * Time.fixedDeltaTime;
this.transform.position = position;

//  Rotate
if(_doTurn)
{
    this.transform.rotation = Quaternion.Lerp(this.transform.rotation, _target, Time.fixedDeltaTime * _velocity);
}

In this sample I had a BoxCollider that triggered the turn, so in the OnTriggerEnter method I simply set _doTurn to true.

Now there are a few things with this:

  1. After the turn I had to fix the positioning and rotation a little. The values were off by about .2, but I couldn't really notice any oddness to care.
  2. This approach gives a quick snap change in direction which wasn't what I was really after. Ideally I wanted a situation where the turn would be gradual at a certain angle and end in the same point. But, to be honest, it looks OK and that gets me moving on for now! :)

With regards to #1 I think I had to fix some of the position/rotation values because I was using a BoxCollider to trigger the turn so there may be better approaches.

\$\endgroup\$
1
\$\begingroup\$

The easiest way to rotate an object to face the direction of its velocity is to represent the rotation as a vector. Unity's physics does this internally, but unfortunately won't let you access any of these variables. Instead, you can simply apply Torque which will do everything for you (it's not good to use a physics engine and apply physics yourself).

First, get the the rotation you want.

Vector3 DesiredRotation = Quaternion.LookRotation(rigidbody.Velocity).EulerAngles;

Then find the rotation you need to rotate to get there:

Vector3 RotationSteering = DesiredRotation - rigidbody.rotation;

Normalize RotationSteering so you can get more control over the degree of the rotation:

RotationSteering.Normalize();

Multiply Rotation steering by whatever amount you wish to rotate at.

RotationSteering *= 30;

Apply the force then $$$:

rigidbody.AddTorque (RotationSteering);

I'm not sure how Unity handles Torque but that's the physics way. If you want complete control, you could set the rotation manually, but this leads to a lot of awkward things. You'll also have to change the rotational velocity for realistic collisions. Just stick to le standard (albeit flawed) wheel and don't reinvent it. You'll have many less headaches.

\$\endgroup\$
  • \$\begingroup\$ This doesn't always give the shortest rotation. \$\endgroup\$ – Louis Hong Jul 2 '18 at 9:07

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.