Overview
This game was developed for Brackey's 2025 game, with the theme of 'Risk it for the Biscuit'. We wanted to build a world of pure chaos for the player to make delivering their biscuits as difficult as possible while still maintaining fun. My role for this game was creating the behaviour for the AI cars, which would change depending on the environment around them.
The Goal
Coming into this, I had the goal of creating customisable car types that moved fluidly through the world. I didn't want them moving in straight lines as that would be too predictable (we want chaos!) and wouldn't feel great for gameplay. In the final game, we had three different car types:
Safe driver - Drives at a moderate speed, will slow down before turning corners and will break when it sees the player
Reckless driver - Drives slightly faster than a safe driver and doesn't break at all, not even for the player
Murderous driver - Drives the faster, no breaking and will actively seek the player when in view
Implementing the Steering Behaviours
To achieve the goal of fluid motion, I decided that cars would use physics-based movement, where forces are applied to move them along. If I took the approach of transforming the position of the cars, their movement would look and feel very rigid. Each car would use a mixture a different steering behaviours to determine how they move and act within the world. These behavuours are:
Seek - moving towards a target location
Wander - cars moving around the world
Arrival - slowing when reaching the target location
Obstacle avoidance - adjusting movement to avoid hitting things
Seek acts as a way to steer the cars towards their target location. It works by adjusting the cars velocity by applying a steering force to it, calculated based on its current location and target location. To find the desired velocity of the car, I minus the cars current position from its targets position, and then minus the cars velocity from the desired velocity. But through testing I noticed that this wasn't sufficient enough, as I wanted the cars speed to be taken into consideration too. To achieve each car type having their own speed value, I adjusted the second step of the equation by normalised the vector of the desired velocity and multiplyied it by its speed value, and then minus the cars velocity from this. Finally, to ensure the cars are maintaining a maximum speed, the steering is clamped to a maximum force value before apply the force to the car.
Seek demonstration
To move around the world, the cars use a node map to determine where they can and can't travel. The world is built using a modular tile set, where each tile contains a node in the center of it. Using each of these nodes, I am able to generate the map, where each node contains a list of their connected neighbours. A node is only considered a neighbour if it has a clear path/sight to the other node.
Our wander behaviour is very similar to our seek behaviour. Each car has their own target node it is trying to reach, which is determined by randomly selecting one of its neighouring nodes once it has arrived at its current target. They all have their own 'acceptance radius', so when they are within that certain radius of their target, they will then switch to a new target.
Node map
Arrival works like similar to how seek does, but the car will begin to break and slow its movement when it's within a certain distance of its target. To do so, the cars desired velocity is calculated by taking the cars current velocity (normalised) and multiplying it by the magnitude of its current velocity divided by the arrival radius. Steering for the car is then calculated by taking the desired velocity from the cars current velocity. After limiting the maximum steering force we can apply and taking the cars mass into consideration, I apply this steering to the car to slow its movement. But leaving it here wouldn't work, as the car will eventually stop moving. If the cars velocity magnitude ever falls below the arrival speed of the car, I just bump it back up again to its arrival speed.
Car slowing in radius
After implementing the first three steering behaviours, the cars were moving around the world from each node quite well, except for one problem: They kept running into things and getting stuck! We don't really have a game if all the cars are stuck on objects. So I needed to include some form of obstacle avoidance to stop this from happening.
I needed a way for each car to detect what was around them so they could steer away from them accordingly. I chose the use a series of ray casts that are shot out in a circle from the center of the car to a maximum 'avoidance range'. Each ray will do its own force calculation that is accumulated in a direction variable to later be applied to the cars steering. I need two elements to complete this force calculation:
offset of the hit
intensity of the hit
The offset will be the amount of overlap there is into the ray from the object it hit, and the intensity is the intensity multiplied by a 'max avoidance force' variable. I chose this approach to have forces applied dynamically based on how close or far an object is to the car. This works well with my goal of movement fluidity, as the cars organically adjust their position based on whats around them and how close they are.
Yellow lines indicate the rays
Yellow spheres indicate the point of impact
Car avoiding obstacles
Final Thoughts
Physics based movement was an interesting challenge to tackle, but overall the final results gave us the ability to generate the chaotic AI movement that we wanted for the game. It was a great learning experience and I was able to implement steering behaviour into a game and see it in action.