Disclaimer right up front. This isn’t complete, I’ll edit it to fill it out. Also, I’m on a slightly older build of UE4 and I’m not sure what’s changed with today’s version.
Edit: In the tread that follows, Epic devs share many things about the UE4 Behavior Tree that point out problems with how I show to do them here. I was intending to update my post, but I switched to using Blueprints only for my Behavior Trees. Go to post #5 in this thread for step by step insructions to set up Simple Wandering AI. https://forums.unrealengine.com/showthread.php?1270-UE4-Pathfinding-basic-AI&highlight=pathing
UE4 brings a new Editor, the Behavior Tree Editor. As far as I last knew it’s experimental so using it comes with the understanding it might be completely changed and your work will have to be at least partially redone that involved it. It’s also un-documented and incomplete.
Then, why would you want to use it? Well, because it’s awesome that’s why. Here’s a screenshot.
Being able to change the AI behaviors in a visual Editor is really cool. Also, debugging in it works. You can see the execution paths through the Tree.
Edit: You can now right click and create these assets from the content browser, BlackBoard is DataAsset and BehaviorTree is Miscellaneous.
ShooterGame is the example to use to get yourself up and running with a BehviorTree, but you’ll also have to copy paste two uassets in from the project. At the time of the last build you could not create a new Behavior Tree or Blackboard component in the Editor. So, copy those two items from the ShooterGame into your own project. You then can rename them and clear everything out from their editor.
There are some concepts that are pretty much considered common terminology in Behavior Trees in game programming, but each implementation will vary slightly.
For a really great introduction to Behavior Tree’s in general refer to AiGameDev’s website: http://aigamedev.com/insider/tutorial/second-generation-bt/
I’ll just cover the basics before I show which classes you need at a minimum to support using UE4’s built in Behavior Tree system.
There are two main types of nodes. Composite Nodes and Leaf Nodes. Composites have children, leaf’s do not. There is a 3rd type which is a decorator, you can see on in the screenshot labled CoolDown, but I’ll just focus on the two main types. All the nodes return a value, if if succeeded, failed, or is running are 3 main types. There are two main types of composite nodes, Sequences and Selectors. These are the building blocks of all your decision making logic.
Both of them evaluate their children from left to right, but a Sequence Node will only go on to the next child if the previous one returned a success. The sequence node itself will only return a success if all of it’s children return a success.
The Selector node is similiar in that it evaluates it’s chlidren from left to right, but it will only try the next one if the previous failed. It will try each chlid in turn seeing if it returns a success. On the first success it finds, it stops evaluating the children and returns a success.
The leaf nodes are like the leaves on a tree, where the composite nodes are like the branches. The leaf nodes do not have children. There are two main types of leaf nodes. A condition and an Action. You use the Conditions to find out something, like the state of a variable ( Is health low? ) and and Action, you guessed it, performs an action.
You need to add some things to your Bot’s Character and Controller Classes in C++, and then hook them up in the Blueprint Editor to the uassets. The Tree itself you make in the Editor and it comes with several nodes, to make custom conditions and actions you extend UBTTask_BlackboardBase. It’s simpler than it sounds.
I have some extra things in here that you may want to do a different way for handling animations. This is not the only way to do it. I’ll leave it in here for now.
Here is what you need to add to the Character’s header:
#pragma once
#include "Bot.generated.h"
/**
* Base class for all Bots
*/
UCLASS()
class ABot : ACharacter
{
GENERATED_UCLASS_BODY()
UPROPERTY(EditAnywhere, Category=Behavior)
class UBehaviorTree* BotBehavior;
//-- Animations
//-- play anim montage
virtual float PlayAnimMontage(class UAnimMontage* AnimMontage, float InPlayRate = 1.f, FName StartSectionName = NAME_None) OVERRIDE;
//-- play anim montage
virtual float PlayAnimMontage(const FString AnimMontageName, float InPlayRate = 1.f, FName StartSectionName = NAME_None);
//-- stop playing montage
virtual void StopAnimMontage(class UAnimMontage* AnimMontage) OVERRIDE;
//-- stop playing all montages
void StopAllAnimMontages();
//-- Reading data
//-- get mesh component
USkeletalMeshComponent* GetPawnMesh() const;
protected:
//-- search anim
UPROPERTY(EditDefaultsOnly, Category=Pawn)
UAnimMontage* SearchAnim;
//-- clean anim
UPROPERTY(EditDefaultsOnly, Category=Pawn)
UAnimMontage* CleanAnim;
//-- pounce anim
UPROPERTY(EditDefaultsOnly, Category=Pawn)
UAnimMontage* PounceAnim;
//-- turn left anim
UPROPERTY(EditDefaultsOnly, Category=Pawn)
UAnimMontage* TurnLeftAnim;
//-- turn right anim
UPROPERTY(EditDefaultsOnly, Category=Pawn)
UAnimMontage* TurnRightAnim;
//-- turn right anim
UPROPERTY(EditDefaultsOnly, Category=Pawn)
UAnimMontage* TrotAnim;
//-- time when we will finish playing animation
float AnimationEndTime;
//-- if pawn is playing animation
bool bIsPlayingAnimation;
};
FORCEINLINE USkeletalMeshComponent* ABot::GetPawnMesh() const
{
return Mesh;
}
The .cpp to go with it.
#include "YourGame.h"
ABot::ABot(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
,AnimationEndTime(0)
,bIsPlayingAnimation(false)
{
}
//////////////////////////////////////////////////////////////////////////
// Animations
float ABot::PlayAnimMontage(class UAnimMontage* AnimMontage, float InPlayRate, FName StartSectionName)
{
USkeletalMeshComponent* UseMesh = GetPawnMesh();
if (AnimMontage && UseMesh && UseMesh->AnimScriptInstance)
{
if(UseMesh->AnimScriptInstance->Montage_IsPlaying(AnimMontage))
{
return true;
}
return (UseMesh->AnimScriptInstance->Montage_Play(AnimMontage, InPlayRate));
}
return false;
}
float ABot::PlayAnimMontage(FString AnimMontageName, float InPlayRate, FName StartSectionName)
{
UAnimMontage* AnimMontage = NULL;
if(AnimMontageName == "CleanSelf")
{
AnimMontage = this->CleanAnim;
}
else if(AnimMontageName == "SearchLaser")
{
AnimMontage = this->SearchAnim;
}
else if(AnimMontageName == "Pounce")
{
AnimMontage = this->PounceAnim;
}
return PlayAnimMontage(AnimMontage, InPlayRate, StartSectionName);
}
void ABot::StopAnimMontage(class UAnimMontage* AnimMontage)
{
USkeletalMeshComponent* UseMesh = GetPawnMesh();
if (AnimMontage && UseMesh && UseMesh->AnimScriptInstance &&
UseMesh->AnimScriptInstance->Montage_IsPlaying(AnimMontage))
{
UseMesh->AnimScriptInstance->Montage_Stop(AnimMontage->BlendOutTime);
}
}
void ABot::StopAllAnimMontages()
{
USkeletalMeshComponent* UseMesh = GetPawnMesh();
if (UseMesh && UseMesh->AnimScriptInstance)
{
UseMesh->AnimScriptInstance->Montage_Stop(0.0f);
}
}
The controller class header:
#pragma once
#include "YourController.generated.h"
/**
*
*/
UCLASS()
class ABotController : AAIController
{
GENERATED_UCLASS_BODY()
UPROPERTY(transient)
TSubobjectPtr<class UBlackboardComponent> BlackboardComp;
UPROPERTY(transient)
TSubobjectPtr<class UBehaviorTreeComponent> BehaviorComp;
virtual void Possess(class APawn* InPawn) OVERRIDE;
virtual void BeginInactiveState() OVERRIDE;
/** This sets a Blackboard component*/
void SetLaser(class ALaserAttractor* InLaser);
/* If there is line of sight to current enemy, start firing at them */
UFUNCTION(BlueprintCallable, Category=Behavior)
void ChaseTarget();
/* This returns the Blackboard component, I'll update the tutorial to make it more generic */
UFUNCTION(BlueprintCallable, Category=Behavior)
class ALaserAttractor* GetLazor() const;
protected:
int32 LaserKeyID;
};
and here is the Controller’s .cpp functions.
#include "YourGame.h"
//#include "ABotController.h"
ABotController::ABotController(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
BlackboardComp = PCIP.CreateDefaultSubobject<UBlackboardComponent>(this, TEXT("BlackBoardComp"));
Components.Add(BlackboardComp);
BehaviorComp = PCIP.CreateDefaultSubobject<UBehaviorTreeComponent>(this, TEXT("BehaviorComp"));
Components.Add(BehaviorComp);
bWantsPlayerState = true;
PrimaryActorTick.bCanEverTick = true;
}
void ABotController::Possess(APawn* InPawn)
{
Super::Possess(InPawn);
ABot* Bot = Cast<ABot>(InPawn);
// start behavior
if (Bot && Bot->BotBehavior)
{
BlackboardComp->InitializeBlackboard(Bot->BotBehavior->BlackboardAsset);
LaserKeyID = BlackboardComp->GetKeyID("Laser");
BehaviorComp->StartTree(Bot->BotBehavior);
}
}
void ABotController::BeginInactiveState()
{
Super::BeginInactiveState();
}
void ABotController::SetLaser(class ALaserAttractor* InLaser)
{
if (BlackboardComp)
{
BlackboardComp->SetValueAsObject(LaserKeyID, InLaser);
SetFocus(InLaser);
}
}
class ALaserAttractor* ACatAIController::GetLazor() const
{
if (BlackboardComp)
{
return Cast<ALaserAttractor>(BlackboardComp->GetValueAsObject(LaserKeyID));
}
return NULL;
}
Hit text limit, to be continued…