This post is going to be a bit more technical than previous posts as we discuss the process behind creating the driving behaviour of the enemy vehicles and some quirks about the Unity3D engine. Minor knowledge about vector math is required to fully understand everything but it should be mostly understandable without it. I am going to refer to vehicles in general while meaning vehicles moving on land which move roughly in the same way as cars.
But before moving on to how the behaviour works, what is its purpose and what requirements are placed on it? Firstly it has to move the vehicle to be in the proximity of the player. Secondly it has to drive the vehicle in a somewhat believable way, meaning that the vehicle should not move sideways or in other obvious non-vehicle directions. Finally, I wanted to avoid using Unity’s wheel colliders as they can cause a lot of problems.
From this we see that we need some kind of path finding to get to the player as there will be obstacles in the level. The first thing that crossed my mind was to use Unity’s built in NavMesh and NavMeshAgents to control the movement. However, it quickly dawned on me that the navmesh system wouldn’t be as perfect as first believed.
As the navmesh agent is built for free-moving entities rather than vehicles with a turning radius the movement was quite far from what I wanted. So I scrapped that solution and sat down to implement my own vehicle driving behaviour.
I started with just trying to get the vehicle to move in a somewhat believable way. This meant that the vehicle should move mostly along the forward vector with a small offset for turning. After a little while I had an early implementation that looked quite good.
Every frame the vehicle calculated the direction to the player and used that as the target direction. Then it rotated the forward vector towards the target vector clamped to a maximum angle and this is the real direction the vehicle tries to move in. After that it smooths the velocity towards that direction using standard lerp. This velocity is then used to actually move the vehicle.
Although it looks quite good this solution is not problem free. As the vehicle always tries to move at its maximum speed the player could, by careful placement, cause the vehicle to literally drive around in circles.
To solve this problem I needed to lower the target speed when the player is located to the side of the vehicle. I did not want this to be an abrupt change so I multiplied the max speed with the dot product and used the absolute value of that. This made the vehicles zero in on the player really quickly and then move almost straight ahead at full speed.
This worked as a first draft as we continued work on other parts of the game but we soon realized the behaviour was too problematic. The single-handedly biggest issue was that the vehicles did not plan for obstacles and therefore frequently got stuck trying to drive through buildings. In order to make the vehicles actually plan their route I went back to the first idea with the navmesh. This time however, I used it for direction instead of movement. What I mean by this is that the path calculated on the navmesh is used as target direction instead of using the direct vector from the vehicle to the player. This made the vehicles try to steer around the buildings more even though they still get stuck sometimes.
By now the vehicles mostly fulfil the requirements stated at the top but after seeing them in the game we realised we wanted one more thing for them. As the player goes around destroying buildings the level is riddled with debris. We noticed that the vehicles would often drive towards the player, have a building near them be blown to pieces and then be stuck in a pile of debris. To counter this we wanted the vehicles to push away all debris blocking their path. This was implemented by checking all the collisions that the vehicle received.
When a collision occurs with a piece of debris the direction is calculated by subtracting the vehicles position from the point of collision. If the vector dot product between the debris direction and the vehicles forward direction is a positive value that means the debris is more to the front than to the back. Then we apply a force impulse to all debris that is front of the vehicle. The size and direction of the force is based on the direction vector to the debris. Each vehicle also has a variable to control how good it is at driving through piles of debris that is used to scale the force vector. Altogether this gives something that is good enough to focus on the other parts of the game for now, but we will probably revisit the vehicles in some way later in development.
The vehicle behaviour is far from perfect. One of the biggest problem is that the navmesh is baked for the smaller enemies and therefore hugs the corners tighter and considers many gaps between buildings passable where the vehicles are wider and get stuck. We will see how we tackle the problem when we get there, but that might be a subject for its own blog post.