Okay! So this tutorial was done purely by blueprints, but i wanted to do this with C++ to learn. I will go my code step by step.
First I Created a new AI Controller in C++, derived from AIController.
Lets go to the .H file. First thing to do is to include some headers. This is what we need!
#include "Perception/AiPerceptionComponent.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardComponent.h"
First i added these:
ANPC_Ai_Controller();
virtual void BeginPlay() override;
// We can set Blackboard in BP
UPROPERTY(EditDefaultsOnly, Category = "Blackboard")
UBlackboardData* BlackboardToUse;
// We can set BehaviorTree in BP
UPROPERTY(EditDefaultsOnly, Category = "Blackboard")
UBehaviorTree* BehaviorTreeToUse;
So we basically create a pointers that can point to BehaviorTree and blackboard. Then we gonna override the original BeginPlay. Notice that BehaviorTree and Blackboard are EditDefaultsOnly. We can edit them in Blueprint later on, this is super important to notice!
Then we add these:
// This is the Blackboardcomponent that will be as Return value later.
UPROPERTY()
UBlackboardComponent* BB;
// Perception component.
UPROPERTY(VisibleAnywhere, Category = "AI")
UAIPerceptionComponent* AIPerception;
// Main function, when sensing stuff.
UFUNCTION()
void SenseStuff(AActor* UpdatedActor, FAIStimulus Stimulus);
BlackboardComponent is a component that lets us control the blackboard data. Like in Blueprints, we can set the KeyValues ETC. Notice that this is seperate from the UBlackboardComponent we added earlier. You will later see what i mean! After that we create a pointer that can point to a Perception Component. Notice that the pointer is still NULL at this point. Only a hollow shell we can fill later. Finally there is SenseStuff function that will be called everytime we sense something.
Finally, this is seperate part of the class but i added these. These are the TSubclass type. That means, we can see them as a class in BP-screen and can change them. The class is set to UAISense, so we can set any sense we want to that variable.
UPROPERTY(EditDefaultsOnly, Category = "Blackboard")
TSubclassOf<UAISense> HearingSense;
UPROPERTY(EditDefaultsOnly, Category = "Blackboard")
TSubclassOf<UAISense> SightSense;
Now that we have the Header File ready, lets go to .CPP
We have a constructor, that is empty.
ANPC_Ai_Controller::ANPC_Ai_Controller()
{
// Constructor is empty.
}
void ANPC_Ai_Controller::BeginPlay()
{
Super::BeginPlay(); // We run Blueprint-beginplay, if we need it for some reason.
if (!ensure(BlackboardToUse)) { return; } // We ensure that pointer isn't null
UseBlackboard(BlackboardToUse, BB);
if (!ensure(BehaviorTreeToUse)) { return; }// We ensure that BehaviorTree isn't null
// Run the behavior tree
RunBehaviorTree(BehaviorTreeToUse);
// We get the perception component from the components list
AIPerception = FindComponentByClass<UAIPerceptionComponent>();
if (!ensure(AIPerception)) { return; }
AIPerception->OnTargetPerceptionUpdated.AddDynamic(this, &ANPC_Ai_Controller::SenseStuff);
TArray<AActor*> ActorsSensed;
AIPerception->GetPerceivedActors(HearingSense, ActorsSensed);
}
Then comes the big one. The BeginPlay. I Am sure, that there could be other, better places to set these, but i used a BeginPlay. It works, in our scenario. First we Ensure that we have something in Blackboard and Behaviortree variables. If not, we exit BeginPlay function! Otherwise, the whole project could crash very fatally!
After we are sure, we call function UseBlackboard and feed our BlackboardToUse variable there and then we feed the BlackboardComponent there. If you look carefully in code you will see this:
Basically that means, the UseBlackboard function takes two argument. Second argument is reference argument (&). It means that it GIVES us something, So when we put the BB in there, we fill it with all the goodies we need to mess with the Blackboard.
After that we run function RunBehaviorTree and pass our BehaviorTreeToUse variable there. In bottom. We use the template function FindComponentByClass to find the PerceptionComponent and we will set that as our AIPerception variable. We do this, because there are tons of different settings, and i just preferred to create the Perception component within the blueprint and then just get the reference from it to our C++ variable. This is the way to do it.
After that we ensure, we actually got something and if yes, we use the AddDynamic macro. This means that eveytime perception updates, it will run function we want. In this case. SenseStuff function.
Below it is just a test. I learned hot to get all the perceived actors using the sense, we want. GetPerceivedActors is the way to do it! It takes two argument. Sense and the Array, we want to put the actors. Notice that again, the second argument is Reference (&), that is why we dont need ‘=’ anywhere. Reference fills out ActorsSensed array! You can call that easily anywhere now!
// We try to SenseStuff around.
void ANPC_Ai_Controller::SenseStuff(AActor* UpdatedActor, FAIStimulus Stimulus)
{
// We set Focalpoint if the sense was successfull
if (Stimulus.WasSuccessfullySensed())
{
BB->SetValueAsObject("FocalPoint", UpdatedActor);
UE_LOG(LogTemp, Warning, TEXT("Sensing Actor"));
}
else
{
BB->ClearValue("FocalPoint");
UE_LOG(LogTemp, Warning, TEXT("Ain't sensing anything"));
}
}
Finally the last one is the SenseStuff. This is easy (well easy after i used four hour to find how to use this damn functon!). I will show you how i found this. When you go AIPerceptionComponent.h you will find that there is two Delegates
With those i figured out the arguments the Dynamic Macro needs. It was Perceived actor and the FAIStimulus struct. So lets use them. Finally use the same logic as in BP. Notice that now we can use the BB variable we got earlier on!
Now you can compile the code. Go to your game and create a Blueprint Class Based on you C++. Notice that this isn’t working yet, because all the pointers are still NULL!
When that is created go into your BP and add the AIPerceptionComponent. Remember, we only have null pointer pointing to Component, we haven’t actually created any!
Then, normally add the hearing and Sight to that component, as you normally would. Final Push is to fill our NULL pointers. So lets go to Class Defaults tab and we can see them! Just set them correctly. As you can see, Hearing Sense and Sight Sense are the TSubclassOf -type we wrote in code! Here we can set them. You only need to set them once and then you can use the GetAllPerceivedActors and use sense you like :)!
Thats’s it. Now just set your Characters to use the new AI Blueprint you created from the C++. I know, this is lot more work than in Blueprints, but hey, atleast we can do stuff in Code now! And of course, when you do this once, it is literally Copy paste to other actors etc. Cheers!