AI Controller not going back at top of Behavior Tree when task succeeded

Hey guys,

I’m a starter at UE and trying to make a little game with some Ai controlled Minions that attack each other depending on the team they are on. I did a behavior tree and an AiController and everything seems to be working fine, until a minion wants to attack another one. First, here is a picture of my behavior tree:

It seems like only one minion is actually attacking while the other only receives damage. Actually, if I send only a minion at a time from each side(team), the behavior seems normal,

Find the enemy base → go to it → attack any minion on your way → once you killed it, continue to the enemy base → attack it

But it seems like when a spawn one every 5 seconds, they start to act weird, not dying or taking way too long to do so, not attacking or just completely ignoring the other minions… Even destroying the enemy base while being clearly not in range!

I think my problem might be around the “EBTNodeResult::Succeeded” that I might not use properly… Or around the BTTask_Cooldown.cpp… i juste wanted a timer that i could control but it turned out to be more complicated than I thought…

So here is some code:

BTTask_Attack.cpp

#include "ProjetDEC.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/BlackBoard/BlackboardKeyAllTypes.h"
#include "Minion.h"
#include "Building.h"
#include "MinionAI.h"
#include "BTTask_Attack.h"


EBTNodeResult::Type UBTTask_Attack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) {
	//getting the controller
	AMinionAI *MinionPC = Cast<AMinionAI>(OwnerComp.GetAIOwner());
	MinionPC->StopMovement();
	//getting the minion controlled by the MinionAI controller
	AMinion *ThisMinion = Cast<AMinion>(MinionPC->GetPawn());
	int temp=0;
	//get enemy actor (minion or building)
	AActor *Enemy = Cast<AActor>(OwnerComp.GetBlackboardComponent()->GetValue<UBlackboardKeyType_Object>(MinionPC->EnemyKeyID));
	if (dynamic_cast<AMinion*>(Enemy)){
		AMinion *EnemyMinion = Cast<AMinion>(Enemy);
		if (EnemyMinion->GetHealth() <= ThisMinion->GetPhysicalDamage()) {
			EnemyMinion->ReceiveDamage(ThisMinion->GetPhysicalDamage());
			OwnerComp.GetBlackboardComponent()->SetValue<UBlackboardKeyType_Object>(MinionPC->EnemyKeyID, nullptr);
			OwnerComp.GetBlackboardComponent()->SetValueAsBool("EnemyClose", false);
			OwnerComp.GetBlackboardComponent()->SetValueAsBool("InRange", false);
			return EBTNodeResult::Succeeded;
		}
		else {
			EnemyMinion->ReceiveDamage(ThisMinion->GetPhysicalDamage());
			return EBTNodeResult::Succeeded;
		}
	}
	else {
		ABuilding *EnemyBuilding = Cast<ABuilding>(Enemy);
		if (EnemyBuilding->GetHealth() <= ThisMinion->GetPhysicalDamage()) {
			EnemyBuilding->ReceiveDamage(ThisMinion->GetPhysicalDamage());
			OwnerComp.GetBlackboardComponent()->SetValue<UBlackboardKeyType_Object>("Target", nullptr);
			OwnerComp.GetBlackboardComponent()->SetValueAsBool("EnemyClose", false);
			OwnerComp.GetBlackboardComponent()->SetValueAsBool("InRange", false);
			return EBTNodeResult::Succeeded;
		}
		else {
			EnemyBuilding->ReceiveDamage(ThisMinion->GetPhysicalDamage());
			return EBTNodeResult::Succeeded;
		}
	}
	

	return EBTNodeResult::Failed;
}

BTTask_Cooldown

#include "ProjetDEC.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/BlackBoard/BlackboardKeyAllTypes.h"
#include "Minion.h"
#include "MinionAI.h"
#include "BTTask_Cooldown.h"


EBTNodeResult::Type UBTTask_Cooldown::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) {
	//getting the controller
	TaskOwnerComp = &OwnerComp;
	AMinionAI *MinionPC = Cast<AMinionAI>(OwnerComp.GetAIOwner());
	//getting the minion controlled by the MinionAI controller
	AMinion *ThisMinion = Cast<AMinion>(MinionPC->GetPawn());
	OwnerComp.GetBlackboardComponent()->SetValueAsBool("OnCooldown", true);
	GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &UBTTask_Cooldown::OnTimerEnd, ThisMinion->GetAttackDelay(),false);

	return EBTNodeResult::Succeeded;

}

void UBTTask_Cooldown::OnTimerEnd() {
	TaskOwnerComp->GetBlackboardComponent()->SetValueAsBool("OnCooldown", false);
}

Minion.cpp

#include "ProjetDEC.h"
#include "Minion.h"

AMinion::AMinion()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
}

// Called when the game starts or when spawned
void AMinion::BeginPlay()
{
	Super::BeginPlay();
}

// Called every frame
void AMinion::Tick( float DeltaTime )
{
	Super::Tick( DeltaTime );
	if (Health <= 0) {
		Die();
	}
}

void AMinion::Die() {
	Destroy();
}

// Called to bind functionality to input
void AMinion::SetupPlayerInputComponent(class UInputComponent* inputComponent)
{
	Super::SetupPlayerInputComponent(InputComponent);
}

Minion.h

#pragma once

#include "GameFramework/Character.h"
#include "Minion.generated.h"

UCLASS()
class PROJETDEC_API AMinion : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMinion();

	UPROPERTY(EditAnywhere, Category = Behavior)
	class UBehaviorTree *BotBehavior;

	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	
	// Called every frame
	virtual void Tick( float DeltaSeconds ) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* inputComponent) override;

	//custom functions
	virtual void Die();
	void ReceiveDamage(int DamageDealt) { Health -= DamageDealt; };

	//getters and setters
	float GetMouvementSpeed() { return MouvementSpeed; };
	float GetAttackRange() { return AttackRange; };
	float GetVisionRange() { return VisionRange; };
	int GetAttackDelay() { return AttackDelay; };
	int GetPhysicalDamage() { return PhysicalDamage; };
	int GetPhysicalResistance() { return PhysicalResistance; };
	bool GetTeam() { return teamA; };
	int GetHealth() { return Health; };
	void SetTeam(bool team) { teamA = team; };

private:
	float RunningTime;
	float MouvementSpeed = 0.5;
	float AttackRange = 500.0f;
	float VisionRange = 2000.0f;
	int AttackDelay = 1; //bigger is slower
	int Health = 200;
	int PhysicalDamage = 40;
	int PhysicalResistance = 10;
	bool teamA;
};

I realize this might be a tough question. Any help will be appreciated. Thanks!

Hey,

First of all, since you’re still on 4.13, you might be suffering from a bug in Blackboard editor, that results in newly added keys being configured as “shared” keys, which usually is not what you want. Go to your BB asset and make sure your keys are not “synced”.

Regarding EBTNodeResult yeah, you’re not using it right. When you return EBTNodeResult::Succeeded the task ends it activity and BT proceeds to the next task, so it’s not what you want to return from UBTTask_Cooldown::ExecuteTask. Look at how UBTTask_Wait does its thing.

Let me know if applying these two nuggets of knowledge to your case didn’t help.

Cheers,

–mieszko

Wow thanks that helped!

So I indeed looked at UBTTask_Wait code and adapted my Cooldown task. But now for some reason the task won’t tick…

Cooldown.cpp

#include "ProjetDEC.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/BlackBoard/BlackboardKeyAllTypes.h"
#include "Minion.h"
#include "MinionAI.h"
#include "BTTask_Cooldown.h"

EBTNodeResult::Type UBTTask_Cooldown::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) {
	//getting the controller
	TaskOwnerComp = &OwnerComp;
	AMinionAI *MinionPC = Cast<AMinionAI>(OwnerComp.GetAIOwner());
	//getting the minion controlled by the MinionAI controller
	AMinion *ThisMinion = Cast<AMinion>(MinionPC->GetPawn());

	WaitTime = ThisMinion->GetAttackDelay();

	return EBTNodeResult::InProgress;
}

void UBTTask_Cooldown::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	WaitTime -= DeltaSeconds;

   if (WaitTime <= 0.0f)
	{
		// continue execution from this node
		FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
	}
}

Cooldown.h

#pragma once

#include "BehaviorTree/Tasks/BTTask_BlackboardBase.h"
#include "BTTask_Cooldown.generated.h"

UCLASS()
class PROJETDEC_API UBTTask_Cooldown : public UBTTask_BlackboardBase
{
	GENERATED_BODY()
	
public:
	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;

protected:
	virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
	
private:
	UBehaviorTreeComponent* TaskOwnerComp;
	float WaitTime;
};

Thanks again!

Wow thanks that helped!

So I indeed looked at UBTTask_Wait code and adapted my Cooldown task. But now for some reason the task won’t tick…

Cooldown.cpp

#include "ProjetDEC.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/BlackBoard/BlackboardKeyAllTypes.h"
#include "Minion.h"
#include "MinionAI.h"
#include "BTTask_Cooldown.h"

EBTNodeResult::Type UBTTask_Cooldown::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) {
	//getting the controller
	TaskOwnerComp = &OwnerComp;
	AMinionAI *MinionPC = Cast<AMinionAI>(OwnerComp.GetAIOwner());
	//getting the minion controlled by the MinionAI controller
	AMinion *ThisMinion = Cast<AMinion>(MinionPC->GetPawn());

	WaitTime = ThisMinion->GetAttackDelay();

	return EBTNodeResult::InProgress;
}

void UBTTask_Cooldown::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	WaitTime -= DeltaSeconds;

   if (WaitTime <= 0.0f)
	{
		// continue execution from this node
		FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
	}
}

Cooldown.h

#pragma once

#include "BehaviorTree/Tasks/BTTask_BlackboardBase.h"
#include "BTTask_Cooldown.generated.h"

UCLASS()
class PROJETDEC_API UBTTask_Cooldown : public UBTTask_BlackboardBase
{
	GENERATED_BODY()
	
public:
	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;

protected:
	virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
	
private:
	UBehaviorTreeComponent* TaskOwnerComp;
	float WaitTime;
};

Thanks again!