Unreal Engine 4 has a character AI implemented out of the box. And I have already posted about simple behavior trees. But what about non-characters? Well, this is unknown territory. But good thing Epics left some hooks which you can use to turn things alive. This is a tutorial on how to implement a simple vehicle AI in UE4.
Setting up a project
I’ll try to make things as simple as possible, so let’s use the basic assets. We’ll use Advanced Vehicle C++ template as a starting point.
1. Setting up a player pawn
First, we’ll set up a player pawn. This is not mandatory. But I like having an RTS-like first player to see things from above. So, let’s create such a player. Add a new blueprint based on Pawn and define following event handlers:
Add a spring arm and a camera. Set them up to your liking.
Add FloatingPawnMovement component, otherwise the player won’t move.
Then delete all PlayerStart actors from the default level and disable “AutoPossessPlayer” property on the vehicle.
Add the newly created actor to your scene and make it auto-possessed by the player. Now when you start a game you can navigate on a map in an RTS-like way (use WASD to control the view).
2. Setting up AI
Let’s move on to AI now.
First, we need an AIController:
Then, we need a blackboard with only one variable of type Vector: “ClickLocation”. And a behavior tree, which is really simple:
The only task is move to a ClickLocation set earlier in the AIController blueprint.
And one more thing: add a NavMeshBoundsVolume to the level, otherwise, PathFindingComponent won’t work.
As you noticed so far, I haven’t explained how things work. I think everything above is a bit trivial, so I don’t want to waste your time. But if some things are not clear, let me know in the comments.
If it was a character-based pawn, then it would already work. But it is a vehicle so things are a little bit more complicated.
Let’s see how MoveTo chain works in UE4.
When a behavior tree runs MoveTo task it, in fact, runs AIController::MoveTo method. Then there is a lot of stuff going on in the guts of AIModule. But eventually, UNavMovementComponent::RequestDirectMove gets invoked.
UCharacterMovementComponet has this method implemented. That’s why it is so easy to set up an AI for characters. UWheeledMovementComponent, on the other hand, does not have a default implementation. So, it is up to developers to make a custom pawn move to a needed location.
The method itself has the following signature:
void RequestDirectMove(const FVector& MoveVelocity, bool bForceMaxSpeed)
MoveVelocity parameter means the direction to a target. The length of the vector means how far the target is from the vehicle. In fact, if you multiply MoveVelocity by DeltaTimeSeconds you will get a distance between the vehicle location and target.
Usually a path between starting point and an end point consists of segments. When the vehicle is following a segment which is not final, bForceMaxSpeed is true. Basically, it is telling the vehicle: “Go as fast as you can, the target is still far from here”.
Let’s see how it works.
First, we need to create a new class based on UWheeledVehicleMovementComponent4W. Override its RequestDirectMove method like this:
Super::RequestDirectMove(MoveVelocity, bForceMaxSpeed); FVector VehicleLocation = GetOwner()->GetActorLocation(); FVector Destination = VehicleLocation + MoveVelocity * GetWorld()->GetDeltaSeconds(); DrawDebugLine(GetWorld(), GetOwner()->GetActorLocation(), Destination, FColor::Red, false, 1.f, 0, 3.f);
Don’t forget to inject this class into the vehicle class. Change your vehicle class constructor’s signature in the following way:
// declaration: AVehicleAIPawn(const FObjectInitializer& ObjectInitializer); // definition: AVehicleAIPawn::AVehicleAIPawn(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass<UAIVehicleMovement>(AWheeledVehicle::VehicleMovementComponentName))
If everything is set up correctly in the project, you’ll get a red line drawn from the vehicle to its target.
At this point, we are almost done. Instructions are clear. Now you just need to implement an algorithm 😉
There are a lot of ways how you can achieve that. I used a PID controller. It is perfectly described in this amazing book here: http://www.gameaipro.com/GameAIPro/GameAIPro_Chapter40_Racing_Vehicle_Control_Systems_using_PID_Controllers.pdf
I don’t really want to explain how PID controllers work. There is a lot of math involved. And some things are beyond my understanding. I’ll just share my implementation, so you can look at it yourself. And I highly suggest reading that article from GameAIPro – it will help you understand how to tune your PID controller if you decide to use it.
https://gist.github.com/yateam/7ce5f43515978f7f172b53801205296d – this is my implementation.
And this is how it looks in action: