In this lecture, we will create the foundation of a simple racing car driven by A.I. So far, we've developed
an NPC that can follow a waypoint circuit progresser around at a constant
rate,
but in reality, that's not how cars move.
Instead, we need to consider the cars velocity, acceleration and deceleration and program the NPC to
know when to speed up and when to slow down.
Velocity is the vector representing the NPC speed, as well as the direction it is traveling.
The speed is used to drive the NPC forward. Acceleration and deceleration are considered forces that can
increase or reduce the velocity over time.
They are also vectors. Usually acceleration is a forward force and deceleration is a backward force, though
they could be applied at
any angle. For example, in the case of a collision from the side.
So far we've developed an NPC that will follow a point that is tracking around a waypoint circuit.
This point we will use as the NPCs eyes for how the track is turning.
When the track is running straight, the NPC and the tracker will have the same forward vector.
However, when the circuit is bending, the angle between the tracker and the NPCs forward vectors will get larger.
To follow along, as we put together this simple car A.I., download the WaypointsCarsStarter resources
with this.
When you open it, you'll get exactly what I'm looking at here, though your Scene and Game views might
be slightly different, but in it you've got a racing track with a waypoint circuit already set
up on it, using the same code that we used in the last lecture
and I've added in an orange car. That orange car has a cube attached to it.
Now this orange car has the WaypointProgressTracker set up on it.
The cube is set as the Target for that WaypointProgressTracker
and there's a C# script called SimpleAICar attached to that car. That script, if we open it up, you'll
find is very similar to the last lot of code that we've got for pushing objects along in a straight
line, whilst they're continually looking at a goal.
So we're going to take this script and modify it to add a little bit of car dynamics to it.
You may also have seen, back in this scene in the game that there's a speedometer that's been added.
This is driven by code called AnalogSpeedConverter and we're not going to go into that
but the speed from the car is being fed into the speedometer.
So it gives you a visual representation of what the car is doing with respect to its speed.
Now with this car, if we just press Play then you'll see it go around the track at a constant speed.
So it's going to go up to 200 (as you can see in the dial) and just constantly follow around the cube
whether you're in the corner or not.
This speed isn't very indicative of an actual speed, so it says it's going at 200
but is this 200 'meters per second' or 200 'kilometers an hour'.
It doesn't look like it's traveling at any of those speeds
and that's because of the way that we're calculating it.
So we'll change that as well and update it, so the dial makes sense.
Back in our code, the first thing we'll do is to modify the way that the speed is set up.
So I want you to get rid of these two lines (or move them about) and we're going to put in this.
So we start with an acceleration of 5, then a deceleration which I've got as 5 as well (these two
values you can change, based on how much power and braking power the car is going to have), a minimum
speed and a maximum speed
(because without this, you can get your car going backwards when you don't want it to because it's braked
too much), and a max speed to stop it from going way too fast to infinity.
Speed itself comes down here, now I've taken the public off of this because speed is not an exposed variable
any more.
We're going to calculate it.
We're not going to set it.
The other things I've got here are acceleration, deceleration, min and max speed NOT as exposed variables,
though you can expose them later.
So they are to be set by the user.
The reason I do this is because I always forget when I'm editing that if I try and change the values
here and they're exposed, it doesn't change in the Inspector
and then I spend a long time trying to figure out why my code's not working.
So I find it better not to have them exposed until I've got everything working the way I want.
Next we'll add our calculation for the speed.
Come down to just before the speed is used, in this Translate function here and above that, we're going to
add in THIS: 'Speed ='. We're using a Mathf.Clamp and that's going to make sure that whatever calculation
for speed we put in here, will get clamped between the minSpeed and the maxSpeed.
So this bit first in the clamp is doing a calculation for us, which it will then put into 'speed'.
If however it falls outside of that range, it will clamp it either to 0 or whatever the maxSpeed
is.
This calculation is increasing the speed with...acceleration * Time.deltaTime
and because we've multiplied by Time.deltaTime up there, we can come down and remove it out of
the Translate
so they were only doing a simple speed.
Now the other thing with this speed converter (which is showing you the dial on the screen) is it takes
a 'min' and 'max' value to work out how much it should rotate the needle.
Now, in this case, this will be minSpeed and the 100 will be the maxSpeed.
So it's going to go, in this case, between 0 and 100.
So let's save that, go back to Unity and press Play
and now instead of the dial zipping around and hitting the top speed, it will slowly accelerate.
So you can see the dial increasing and the car following the cube. You can also see that we need to fix
the formatting of our speed
and then we get to a point that you can see here. See how the car's jumping round and round doing circles
around that tracker? It's the same problem that we've had in the past with our NPCs when they're not quite reaching
the point that they need to be at
and this car essentially can't follow the tracker because it's stuck going around it. Now to
fix that, if you go into your car and go down to your WaypointProgressTracker attached circuit, the
'Look Ahead For Target' will be set to 1
and also the 'Look Ahead For Speed', which are just too big.
Turn them down to 0.1 instead,
so that we're not looking so far ahead.
We're staying much closer to that waypoint tracker.
Then if we run again, you'll see that's staying nice and close
but what it's not going to do is get too far away and it's not going to cut corners either.
So as you increase that value for the 'Look Ahead Target', you'll start to get your car further and further
away from the waypoint tracker. If I just pause this for a second...because it's calculating in a straight line
to that point, when the waypoint tracker is around the corner up here
and if the cars over here, it could track straight across the track here and you will find that as you build
more systems that you will get the car lagging behind the cube and it will cut corners and not stay
on the track perfectly and that's why you need to adjust these 'Look Ahead Target' and speed values to
your certain circumstance and your certain tracks. So, if we go back to play...
we're now going at 100, which is extremely fast as you can see on this track, but the car is following
the circuit tracker and following the track, so that's good.
Now what's happening though is, it's getting up to 100
and that's its constant speed. Of course, real cars don't do that.
You don't go round the corner at 100.
So we now want to change it so that we'll brake when we're coming up to a corner. If you can run and pause
in a corner, you'll get a situation like this.
Notice how the cars forward-facing vector is not aligned with the forward facing vector of the tracker.
Whenever this happens, it means that the car is traveling in a different direction to the tracker
and therefore we know that the tracker has gone around a corner and the car hasn't.
When this happens we want to apply the brakes (or bring in deceleration into our speed calculation) and
we can determine when to do this in our code by calculating the angle between each forward vector. Back
in the code,
we'll add in another variable at the top: 'float brakeAngle' and this will
be the angle at which we will apply the brakes, so because it's a variable, you can change it for different
car types. Some can be very cautious and brake when there's a small angle.
Others can be a bit more daredevil and you can have a large angle, but let's set 20 to start with as
our angle.
So that's out brake angle that will test the angle between our goal forward vector and our car forward
vector.
So we'll do that
down here in the Update. Currently, we're calculating the speed all by itself just here
and it's only got the acceleration. Now deceleration is going to be the same deal, but it will be a minus.
Let's replace this line here
with this. So this calculates with a Vector3.Angle. You can give it two vectors so the forward vector
of the goal and the forward vector of the car
and then test if that angle is greater than the brake angle.
If it is, then we're going to decelerate (so we're going to apply the brakes essentially),
otherwise we will keep accelerating. While we're in here,
we'll fix that read out text on the speedometer, by casting speed to an 'int', just to cut off all the
decimal places.
Right,
save that.
Now let's go and play.
So we begin by accelerating and when we get to the first corner you'll see the speed have a little hiccup
and then as it goes around other corners, you'll see the needle of the speed dip just down a little bit,
by a little bit.
But the thing is, it's still accelerating and if we leave it to run long enough, it will get up to maximum
speed.
Now the reason that that is occurring is because the rotation between the cars forward vector and the cubes
forward vector isn't
different enough for us to worry about applying the brakes.
The reason that is, if we stop playing, go into the car and down to your WaypointProgressTracker.
If you have a look, you've got this 'Look Ahead For Speed' value which is currently set at 0.1.
Now that's the cube rotating itself, for however far ahead it can look on the track and how much it can
rotate. So, this is like the cubes rotational speed.
Now if we change that to something like 1, it will allow the cube to update a lot quicker for further
ahead. Now if you watch the cube as we press Play here, you'll see it turning on its own axis
and what that will do is then allow the car to apply the brakes into the corners. So as it comes to the
corner, see that cube rotates and flips around quite quickly (and up here again it will do it as well)
and the brakes are now being applied a lot because there is that big difference between the car and
the cube and you can see that it's really putting a dampener on the speed here.
So again it comes down to "What are you after, in this?" and "How do you want it to look and work?".
It will depend on the size of your world and also the size of the track as well because the world...remember
that a cube is 1 meter x 1 meter x 1 meter
and therefore this track (you can see that white cube was made as a unit cube),
it's not a very big track.
You can't expect to get up to a great speed. If you want real-world speeds, then you need to make sure your
track is in the real-world size coordinates as well.
So let's leave this here and in the next lecture we'll start to look at customizing the properties to
make unique NPC behavior.