-
Notifications
You must be signed in to change notification settings - Fork 2
Technical: Driver AI
AI should be implemented as a component that can be added to the karts (in this Repo, inheriting from KartInput). This "separation of concerns" means features only used by the AI like collision sensors, navigation agents and taxing vector math can be instanced, enabled and disabled separately.
As a developer, your priorities for this system should be, in order:
- The AI can successfully drive the kart around the track;
- The AI can drive the kart around while obstacles are thrown at them;
- The AI can compete and race its way to 1st place, if given reasonable advantages.
The AI should be able to know where it is, where it should go, and what inputs it needs to get there. In a racing game, it should always be looking ahead and targeting the next track section. If that section is to the left it should turn left, and if it's to the right it should turn right. If the AI sees it's driving in reverse it should turn 180 degrees and continue.
As most vehicles in racing games have a "turn radius", or how sharp of a corner they can make at a certain speed, the AI also needs to know how sharp any incoming turn is and apply brakes as necessary.
It's also desirable for the driver to additionally avoid unfavorable positions like driving too close to a wall, or driving over rough terrain. Separate sensors can be used for each requirement.
Obstacles here have a particular meaning: anything that's dynamic, not static. This may include other racers, or items, or moving sections of the track like drawbridges, gates and moving platforms. Of course, everything that's static can be easily accounted for by fine-tuning the AI or racing paths during development.
This doesn't require obstacle avoidance, per se; as a player, you don't want CPU controlled opponents that can perfectly dodge all your tools. The goal here is to make the AI able to recover itself after such events and get back to racing. It can't get stuck after running over an e.g. oil spill, or not be able to drive on a moving platform as it's not in the navigation mesh.
This will usually require some sort of state machine to determine if the AI is on track or stranded off-track, if it's driving on reverse or not. Worst case scenario, if the kart gets stuck for too long, you can warp it back to the track.
Avoidance is still desirable, however. AI drivers should avoid direct contact with other karts as that can disrupt steering for both. Steering away from traps and track gimmicks also makes the AI seem more responsive, helping with the game's immersion and making it more fun.
The AI should also know that it is racing with the goal of getting to 1st place, and try to act as such. It should not use the brakes as its main form of collision avoidance as that can slow it down massively, and mostly rely on steering. If the AI driver is going faster than the driver in front of it, it should try to overtake from the inside.
If the AI has an offensive item, it should use it when there's a reasonable chance of hitting another driver. If it has a defensive item, it might choose to wait to use it only when in danger. The AI should also consider different paths like shortcuts if it's too far behind or fighting for a higher ranking, especialy if it has a "boost" item in its possession.
Of course, in an accessible party game like a kart racer the AI shouldn't always make the most optimal choices, and such "expert" behavior should be less frequent in easier difficulties. Making those kinds of decisions, though, shows the player they're not just pushovers and should be paid attention to.
There is also room for adding "rubberbanding", which might need a section on its own, but essentially, make the lead drivers a bit slower and the bottom rank drivers a bit faster as to not spread the track too thin and force more opportunities for overtake and item usage between the player and the AI.
The simplest approach to racing AI is to define a "racing line" as a Path3D, and have AI drivers follow that path. Worst case scenario, drivers can be programmed to follow the path exactly, as if "on-rails", which could be acceptable for a 90's shovelware game but that's it. We can do better and use these paths for guiding steering, which still lets AI react to being thrown off its path.
In a serious racing game, this path should follow the actual racing line, but in a kart game where drivers generally go slower and aren't as punished for bad racing lines, having the AI path start from the middle of the track is a safe option. There are still concerns to address while making that path, like aligning it to the terrain, shifting it away from corners with no railings, and starting sharp corners a bit earlier from the outside.
If you find the drivers will naturally understeer in a sharp corner, make the suggested path closer to the inside
One interesting evolution of this path concept is to make separate "center", "inside" and "outside" paths through the horizontal width of the track, and allow the AI drivers to switch between them. Different paths through the same roads make the drivers feel more natural and less on-rails, and also helps with programming racing situations like blocking and overtaking.
As for actually following the track, first calculate the path's closest point to the vehicle, then calculate a few units ahead of that as your target. Now that the car has a forward reference, vector math can help the car steer left or right if not looking towards the point. I'd also suggest applying the brakes if the target point is too far to one side, to make sharper turns.
Some games even use two target points (I've seen the technique called "rabbit and hound"), with one target closer and one far ahead, and the difference in heading direction between the two (dot product of directions) indicates how tight the corners are.
What happens when an AI-controlled vehicle gets sent off the track by either player action or random chance? Some games use standard Navigation Agents to let cars find their way back to the track, avoiding off-track obstacles and track limits. The current Godot prototype uses Godot's navigation for the whole driving experience for now.
Godot's Navigation Agents can only use A-star pathfinding. It's a lightweight algorithm, but a major limitation is that it doesn't take in consideration how a vehicle steers and assumes the agent can rotate in place instantly. There are patches that can be added onto such pathfinding, like calculating a turning radius and steering in reverse if needed.
However, if the team has enough resources and targets high performance platforms, Hybrid star pathfinding [5] can be used. I wouldn't suggest simulating hybrid star for very long distances though.
Racing path implementations might struggle with this too, but pathfinding algorithms especially suffer from this problem a lot: If the track intersects itself, or gets close to doing it (like two-lane straights, or overpasses/underpasses, loops), pathfinding will either think the target position is on the other side of the intersection, or keep switching between the two. As such, you need some way to separate those sections of the track so that the AI can't pathfind from one to the other.
One solution in Godot (that we're yet to investigate) is to split the track in a few Navigation Layers (e.g. one layer per different elevation) and use trigger zones (in Godot, Area3D nodes) to change which layer the navigation agent should pathfind to. This could also be an apt solution for split pathways.
The video in [6] showcases an implementation of waypoint paths with branches where AI agents can break off into different paths.
If the obstacles are big enough, like gates, doors and track harzards, pathfinding can be used to avoid them, with an optional "driving cautiously" state where the AI driver applies the brakes a bit for higher responsiveness.
If the obstacles are small, like other racers or "landmine" items, simple steering behaviors can be applied.
Directly following hardcoded racing paths or using navigation agents may not be ideal 100% of the time, as each method has its weaknesses. We may also want special behaviors depending on the game state, like driving more or less aggressively or reacting to certain items. For that, our AI implementation can use State Machines [3].
The most trivial example for driving would be a simple 2-state system including Follow Path and Find Path states. If the driver is on rough terrain, in reverse, or very far from the closest path point, Find Path will use navigation to find its way back on the road. If the driver is close enough to the racing line, Follow Path will make it tightly follow the racing line.
If we have a kart racer like Mario Kart, we can also use State Machines to induce smarter item use. If the AI is in the lead, it should be in a Defensive state and instead of spamming items it should wait for the right time to use them (in Mario Kart it may keep holding the item button to defend with shells, in Blur it may actively try to get Shield or Repair powerups). If the AI is behind another kart, it could enter an Overtake state and start attempting overtakes by steering a bit to the left or right of that kart, and saving boost items for the moment the overtake happens if available.
write about implementation of driver and obstacle avoidance. write about rubberbanding. write about KartInput and SimpleAIKartInput as on the current build
[1] https://youtu.be/SKXqWcaoTGE?si=8yV8Q9nSEjdV45vU&t=1209
[2] https://www.youtube.com/watch?v=-juhGgA076E
[4] https://youtu.be/4bOsJSRk0i8?si=Ph3lsLsoJzV80JMv
[5] https://blog.habrador.com/2015/11/explaining-hybrid-star-pathfinding.html