Apologies! Here it is in all it’s glory, or lack of…
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "BehaviorTree/Tasks/BTTask_BlackboardBase.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Object.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Vector.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Rotator.h"
#include "BehaviorTree/Services/BTService_DefaultFocus.h"
#include "BTTask_SteerTo.generated.h"
/**
* Tells a BZ Pawn to Apply Steering towards a target location.
*/
UCLASS(Config=Game)
class BZGAME_API UBTTask_SteerTo : public UBTTask_BlackboardBase
{
GENERATED_BODY()
protected:
/* Success Condition Precision In Degrees */
UPROPERTY(config, Category = Node, EditAnywhere, meta = (ClampMin = 0.0f))
float YawAcceptanceRadius;
UPROPERTY(config, Category = Node, EditAnywhere, meta = (ClampMin = 0.0f))
float PitchAcceptanceRadius;
UPROPERTY(config, Category = Node, EditAnywhere)
bool AlignPitch;
UPROPERTY(config, Category = Node, EditAnywhere)
bool AlignYaw;
private:
/* Cached Dot Precision */
float YawPrecisionDot;
float PitchPrecisionDot;
public:
UBTTask_SteerTo(const FObjectInitializer& ObjectInitializer);
virtual void PostInitProperties() override;
virtual void PostLoad() override;
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
virtual EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual void DescribeRuntimeValues(const UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTDescriptionVerbosity::Type Verbosity, TArray<FString>& Values) const override;
virtual FString GetStaticDescription() const override;
virtual uint16 GetInstanceMemorySize() const override { return sizeof(FBTFocusMemory); }
protected:
float GetYawPrecisionDot() const { return YawPrecisionDot; }
float GetPitchPrecisionDot() const { return PitchPrecisionDot; }
void CleanUp(AAIController& AIController, uint8* NodeMemory);
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "BZGame.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "AI/Tasks//BTTask_SteerTo.h"
UBTTask_SteerTo::UBTTask_SteerTo(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
, PitchAcceptanceRadius(10.f)
, YawAcceptanceRadius(10.f)
, AlignPitch(false)
, AlignYaw(true)
{
NodeName = "SteerTo";
bNotifyTick = true;
// Accept Actors, Vectors & Rotators
BlackboardKey.AddObjectFilter(this, GET_MEMBER_NAME_CHECKED(UBTTask_SteerTo, BlackboardKey), AActor::StaticClass());
BlackboardKey.AddVectorFilter(this, GET_MEMBER_NAME_CHECKED(UBTTask_SteerTo, BlackboardKey));
BlackboardKey.AddRotatorFilter(this, GET_MEMBER_NAME_CHECKED(UBTTask_SteerTo, BlackboardKey));
}
void UBTTask_SteerTo::PostInitProperties()
{
Super::PostInitProperties();
YawPrecisionDot = FMath::Cos(FMath::DegreesToRadians(YawAcceptanceRadius));
PitchPrecisionDot = FMath::Cos(FMath::DegreesToRadians(PitchAcceptanceRadius));
}
void UBTTask_SteerTo::PostLoad()
{
Super::PostLoad();
YawPrecisionDot = FMath::Cos(FMath::DegreesToRadians(YawAcceptanceRadius));
PitchPrecisionDot = FMath::Cos(FMath::DegreesToRadians(PitchAcceptanceRadius));
}
namespace
{
FORCEINLINE_DEBUGGABLE float CalculateYawAngleDifferenceDot(const FVector& VectorA, const FVector& VectorB)
{
return VectorA.CosineAngle2D(VectorB);
}
}
namespace
{
FORCEINLINE_DEBUGGABLE float CalculatePitchAngleDifferenceDot(const FVector& VectorA, const FVector& VectorB)
{
// This may Suck Badly!
FVector A = VectorA;
FVector B = VectorB;
A.Z = 0.f;
B.Z = 0.f;
A.Normalize();
B.Normalize();
return A | B;
}
}
EBTNodeResult::Type UBTTask_SteerTo::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
AAIController* AIController = OwnerComp.GetAIOwner();
if (AIController == NULL || AIController->GetPawn() == NULL)
{
return EBTNodeResult::Failed;
}
FBTFocusMemory* MyMemory = (FBTFocusMemory*)NodeMemory;
check(MyMemory);
MyMemory->Reset();
EBTNodeResult::Type Result = EBTNodeResult::Failed;
APawn* Pawn = AIController->GetPawn();
UBZGame_GameObjectComponent* PawnGOC = UBZGameplayStatics::GetGameObjectComponentFromActor(Pawn, true);
ASSERTV_WR(PawnGOC != nullptr, Result, *FString::Printf(TEXT("UBTTask_SteerTo - Pawn Has no GameObjectComponent!")));
FVector EyepointLocation, EyepointDirection;
PawnGOC->GetEyepoint(EyepointLocation, EyepointDirection);
const UBlackboardComponent* MyBlackboard = OwnerComp.GetBlackboardComponent();
// Solve for Actor Target
if (BlackboardKey.SelectedKeyType == UBlackboardKeyType_Object::StaticClass())
{
UObject* KeyValue = MyBlackboard->GetValue<UBlackboardKeyType_Object>(BlackboardKey.GetSelectedKeyID());
AActor* ActorValue = Cast<AActor>(KeyValue);
// TODO - Should Target Other Location
if (ActorValue != NULL)
{
bool bYawMet = false;
bool bPitchMet = false;
if (AlignYaw)
{
const float YawAngleDiff = CalculateYawAngleDifferenceDot(EyepointDirection, (ActorValue->GetActorLocation() - EyepointLocation).GetSafeNormal());
if (YawAngleDiff >= YawAcceptanceRadius)
{
bYawMet = true;
}
}
else
{
bYawMet = true;
}
if (AlignPitch)
{
const float PitchAngleDiff = CalculatePitchAngleDifferenceDot(EyepointDirection, (ActorValue->GetActorLocation() - EyepointLocation).GetSafeNormal());
if (PitchAngleDiff >= PitchAcceptanceRadius)
{
bPitchMet = true;
}
}
else
{
bPitchMet = true;
}
if (bYawMet && bPitchMet)
{
Result = EBTNodeResult::Succeeded;
}
else
{
AIController->SetFocus(ActorValue, EAIFocusPriority::Gameplay);
MyMemory->FocusActorSet = ActorValue;
MyMemory->bActorSet = true;
Result = EBTNodeResult::InProgress;
}
}
}
// Solve for Vector
else if (BlackboardKey.SelectedKeyType == UBlackboardKeyType_Vector::StaticClass())
{
const FVector KeyValue = MyBlackboard->GetValue<UBlackboardKeyType_Vector>(BlackboardKey.GetSelectedKeyID());
// Direction or Location in This?
if (FAISystem::IsValidLocation(KeyValue))
{
bool bYawMet = false;
bool bPitchMet = false;
if (AlignYaw)
{
const float YawAngleDiff = CalculateYawAngleDifferenceDot(EyepointDirection, (KeyValue - EyepointLocation).GetSafeNormal());
if (YawAngleDiff >= YawAcceptanceRadius)
{
bYawMet = true;
}
}
else
{
bYawMet = true;
}
if (AlignPitch)
{
const float PitchAngleDiff = CalculatePitchAngleDifferenceDot(EyepointDirection, (KeyValue - EyepointLocation).GetSafeNormal());
if (PitchAngleDiff >= PitchAcceptanceRadius)
{
bPitchMet = true;
}
}
else
{
bPitchMet = true;
}
if (bYawMet && bPitchMet)
{
Result = EBTNodeResult::Succeeded;
}
else
{
AIController->SetFocalPoint(KeyValue, EAIFocusPriority::Gameplay);
MyMemory->FocusLocationSet = KeyValue;
Result = EBTNodeResult::InProgress;
}
}
}
// Solve For Rotation
else if (BlackboardKey.SelectedKeyType == UBlackboardKeyType_Rotator::StaticClass())
{
const FRotator KeyValue = MyBlackboard->GetValue<UBlackboardKeyType_Rotator>(BlackboardKey.GetSelectedKeyID());
if (FAISystem::IsValidRotation(KeyValue))
{
const FVector DirectionVector = KeyValue.Vector();
bool bYawMet = false;
bool bPitchMet = false;
if (AlignYaw)
{
const float YawAngleDiff = CalculateYawAngleDifferenceDot(EyepointDirection, DirectionVector);
if (YawAngleDiff >= YawAcceptanceRadius)
{
bYawMet = true;
}
}
else
{
bYawMet = true;
}
if (AlignPitch)
{
const float PitchAngleDiff = CalculatePitchAngleDifferenceDot(EyepointDirection, DirectionVector);
if (PitchAngleDiff >= PitchAcceptanceRadius)
{
bPitchMet = true;
}
}
else
{
bPitchMet = true;
}
if (bYawMet && bPitchMet)
{
Result = EBTNodeResult::Succeeded;
}
else
{
const FVector FocalPoint = EyepointLocation + DirectionVector * (MAX_FLT / 2);
AIController->SetFocalPoint(FocalPoint, EAIFocusPriority::Gameplay);
MyMemory->FocusLocationSet = FocalPoint;
Result = EBTNodeResult::InProgress;
}
}
}
return Result;
}
void UBTTask_SteerTo::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
AAIController* AIController = OwnerComp.GetAIOwner();
if (AIController == NULL || AIController->GetPawn() == NULL)
{
FinishLatentTask(OwnerComp, EBTNodeResult::Failed);
}
else
{
UBZGame_GameObjectComponent* PawnsGOC = UBZGameplayStatics::GetGameObjectComponentFromActor(AIController->GetPawn(), true);
if (PawnsGOC)
{
FVector EyeDirection, EyeLocation;
PawnsGOC->GetEyepoint(EyeLocation, EyeDirection);
const FVector FocalPoint = AIController->GetFocalPointForPriority(EAIFocusPriority::Gameplay);
bool bYawMet = false;
bool bPitchMet = false;
if (AlignYaw)
{
const float YawAngleDiff = CalculateYawAngleDifferenceDot(EyeDirection, (FocalPoint - EyeLocation));
if (YawAngleDiff >= YawAcceptanceRadius)
{
bYawMet = true;
}
}
else
{
bYawMet = true;
}
if (AlignPitch)
{
const float PitchAngleDiff = CalculatePitchAngleDifferenceDot(EyeDirection, (FocalPoint - EyeLocation));
if (PitchAngleDiff >= PitchAcceptanceRadius)
{
bPitchMet = true;
}
}
else
{
bPitchMet = true;
}
if (bYawMet && bPitchMet)
{
CleanUp(*AIController, NodeMemory);
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}
}
else
{
FinishLatentTask(OwnerComp, EBTNodeResult::Failed);
}
}
}
void UBTTask_SteerTo::CleanUp(AAIController& AIController, uint8* NodeMemory)
{
FBTFocusMemory* MyMemory = (FBTFocusMemory*)NodeMemory;
check(MyMemory);
bool bClearFocus = false;
if (MyMemory->bActorSet)
{
bClearFocus = (MyMemory->FocusActorSet == AIController.GetFocusActorForPriority(EAIFocusPriority::Gameplay));
}
else
{
bClearFocus = (MyMemory->FocusLocationSet == AIController.GetFocalPointForPriority(EAIFocusPriority::Gameplay));
}
if (bClearFocus)
{
AIController.ClearFocus(EAIFocusPriority::Gameplay);
}
}
EBTNodeResult::Type UBTTask_SteerTo::AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
AAIController* AIController = OwnerComp.GetAIOwner();
if (AIController != NULL)
{
CleanUp(*AIController, NodeMemory);
}
return EBTNodeResult::Aborted;
}
void UBTTask_SteerTo::DescribeRuntimeValues(const UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTDescriptionVerbosity::Type Verbosity, TArray<FString>& Values) const
{
FString KeyDesc = BlackboardKey.SelectedKeyName.ToString();
Values.Add(FString::Printf(TEXT("%s: %s"), *Super::GetStaticDescription(), *KeyDesc));
AAIController* AIController = OwnerComp.GetAIOwner();
if (AIController != NULL && AIController->GetPawn() != NULL)
{
UBZGame_GameObjectComponent* PawnsGOC = UBZGameplayStatics::GetGameObjectComponentFromActor(AIController->GetPawn(), true);
if (PawnsGOC)
{
FVector EyeDirection, EyeLocation;
PawnsGOC->GetEyepoint(EyeLocation, EyeDirection);
const FVector FocalPoint = AIController->GetFocalPointForPriority(EAIFocusPriority::Gameplay);
if (FocalPoint != FAISystem::InvalidLocation)
{
const float CurrentYawAngleRadians = CalculateYawAngleDifferenceDot(EyeDirection, (FocalPoint - EyeLocation).GetSafeNormal());
const float CurrentPitchAngleRadians = CalculateYawAngleDifferenceDot(EyeDirection, (FocalPoint - EyeLocation).GetSafeNormal());
Values.Add(FString::Printf(TEXT("Current Yaw: %.2f Current Pitch: %.2f"), FMath::DegreesToRadians(FMath::Acos(CurrentYawAngleRadians)), FMath::DegreesToRadians(FMath::Acos(CurrentPitchAngleRadians))));
}
else
{
Values.Add(TEXT("FocalPoint is an Invalid Location"));
}
}
else
{
Values.Add(TEXT("GameObjectComponent is Invalid"));
}
}
else
{
Values.Add(TEXT("Controller or Pawn is NULL"));
}
}
FString UBTTask_SteerTo::GetStaticDescription() const
{
FString KeyDesc = BlackboardKey.SelectedKeyName.ToString();
return FString::Printf(TEXT("%s: %s"), *Super::GetStaticDescription(), *KeyDesc);
}