Added GOAPAgent Functionality

This commit is contained in:
Philip W 2023-03-20 07:30:39 +00:00
parent 2e03f7cf19
commit 279669ebcd
10 changed files with 165 additions and 27 deletions

Binary file not shown.

BIN
COMP250_1_2101327_AI/Content/Main.umap (Stored with Git LFS)

Binary file not shown.

View File

@ -3,29 +3,25 @@
#include "GOAPAction.h"
bool UGOAPAction::CheckPreConditions(TMap<FString, int32> WorldState)
bool UGOAPAction::CheckPreConditions(TMap<FString, int> WorldState)
{
for (TPair<FString, int32> PreCondition : PreConditions)
for (TPair<FString, int> PreCondition : PreConditions)
{
if (WorldState.Contains(PreCondition.Key))
{
if (WorldState[PreCondition.Key] != PreCondition.Value) return false;
if (WorldState[PreCondition.Key] < PreCondition.Value) return false;
}
}
return true;
}
TMap<FString, int32> UGOAPAction::ApplyEffects(TMap<FString, int32> WorldState)
TMap<FString, int> UGOAPAction::ApplyEffects(TMap<FString, int> WorldState)
{
for (TPair<FString, int32> Effect : Effects)
for (TPair<FString, int> Effect : Effects)
{
if (WorldState.Contains(Effect.Key))
{
WorldState[Effect.Key] = Effect.Value;
}
else
{
WorldState.Add(Effect.Key, Effect.Value);
WorldState[Effect.Key] -= Effect.Value;
}
}
return WorldState;

View File

@ -15,10 +15,18 @@ class COMP250_1_2101327_AI_API UGOAPAction : public UObject
GENERATED_BODY()
public:
virtual bool CheckPreConditions(TMap<FString, int32> WorldState);
TMap<FString, int32> ApplyEffects(TMap<FString, int32> WorldState);
float ActionCost = 1.0f;
protected:
TMap<FString, int32> PreConditions;
TMap<FString, int32> Effects;
UFUNCTION()
bool CheckPreConditions(TMap<FString, int> WorldState);
UFUNCTION()
TMap<FString, int> ApplyEffects(TMap<FString, int> WorldState);
UPROPERTY()
TMap<FString, int> PreConditions;
UPROPERTY()
TMap<FString, int> Effects;
UFUNCTION()
virtual void Init(){}
};

View File

@ -3,6 +3,9 @@
#include "GOAPAgent.h"
#include "Actions/Combo_P.h"
#include "Actions/DefaultAttack.h"
// Sets default values for this component's properties
UGOAPAgent::UGOAPAgent()
@ -20,11 +23,61 @@ void UGOAPAgent::BeginPlay()
{
Super::BeginPlay();
// ...
CombatSystem = Cast<ATurnBaseCombatV2>(GetWorld()->GetGameState());
UGOAPAction* DefaultAttack = NewObject<UGOAPAction>(GetWorld(), UDefaultAttack::StaticClass());
DefaultAttack->Init();
AvailableActions.Add(DefaultAttack);
UGOAPAction* Combo_P = NewObject<UGOAPAction>(GetWorld(), UCombo_P::StaticClass());
Combo_P->Init();
AvailableActions.Add(Combo_P);
}
TMap<FString, int> UGOAPAgent::GetWorldState()
{
TMap<FString, int> WorldState;
WorldState.Add("PlayerHealth", *CombatSystem->PlayerHealth);
WorldState.Add("EnemyHealth", *CombatSystem->EnemyHealth);
WorldState.Add("ProbertiumResource", CombatSystem->EnemyProbertiumResource);
WorldState.Add("EisResource", CombatSystem->EnemyEisResource);
WorldState.Add("AzosResource", CombatSystem->EnemyAzosResource);
WorldState.Add("IroquoidResource", CombatSystem->EnemyIroquoidResource);
return WorldState;
}
bool UGOAPAgent::BuildActionGraph(UActionNode* Parent, TArray<UActionNode*>& SuccessfulLeaves, TMap<FString, int> WorldState, TMap<FString, int> AgentGoals)
{
bool bFoundAPlan = false;
for (UGOAPAction* Action : AvailableActions)
{
if (Action->CheckPreConditions(WorldState))
{
TMap<FString, int> NewWorldState = Action->ApplyEffects(WorldState);
UActionNode* NewNode = NewObject<UActionNode>();
NewNode->Init(Parent, Action, NewWorldState, Parent->Cost + Action->ActionCost);
if (NewWorldState["PlayerHealth"] <= 0)
{
SuccessfulLeaves.Add(NewNode);
bFoundAPlan = true;
}
else
{
TArray<UActionNode*> NewLeaves;
if (BuildActionGraph(NewNode, NewLeaves, NewWorldState, AgentGoals))
{
SuccessfulLeaves.Append(NewLeaves);
bFoundAPlan = true;
}
}
}
}
return bFoundAPlan;
}
// Called every frame
void UGOAPAgent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
@ -33,3 +86,42 @@ void UGOAPAgent::TickComponent(float DeltaTime, ELevelTick TickType, FActorCompo
// ...
}
TArray<UGOAPAction*> UGOAPAgent::Plan(TMap<FString, int> WorldState, TMap<FString, int> AgentGoals)
{
TArray<UGOAPAction*> ActionPlan;
UActionNode* RootActionNode = NewObject<UActionNode>();
RootActionNode->Init(nullptr, nullptr, WorldState, 0);
TArray<UActionNode*> SuccessfulLeaves;
if (BuildActionGraph(RootActionNode, SuccessfulLeaves, WorldState, AgentGoals))
{
//Find the cheapest leaf
float LowestRunningCost = 9999999;
UActionNode* CheapestLeaf = nullptr;
for (UActionNode* Leaf : SuccessfulLeaves)
{
if (Leaf->Cost < LowestRunningCost)
{
LowestRunningCost = Leaf->Cost;
CheapestLeaf = Leaf;
}
}
//Get the action plan
TArray<UActionNode*> Result;
UActionNode* ActionNode = CheapestLeaf;
while (ActionNode != nullptr)
{
Result.Add(ActionNode);
ActionNode = ActionNode->Parent;
}
//Reverse the array and only get the actions
for (int i = Result.Num() - 1; i >= 0; i--)
{
ActionPlan.Add(Result[i]->Action);
}
}
return ActionPlan;
}

View File

@ -3,9 +3,30 @@
#pragma once
#include "CoreMinimal.h"
#include "GOAPAction.h"
#include "COMP250_1_2101327_AI/TurnBasedCombatV2/TurnBaseCombatV2.h"
#include "Components/ActorComponent.h"
#include "GOAPAgent.generated.h"
UCLASS()
class UActionNode : public UObject
{
GENERATED_BODY()
public:
UActionNode* Parent;
UGOAPAction* Action;
TMap<FString, int> State;
float Cost;
void Init(UActionNode* ParentNode, UGOAPAction* GOAPAction, TMap<FString, int> ProceduralState, float TotalCost)
{
Parent = ParentNode;
Action = GOAPAction;
State = ProceduralState;
Cost = TotalCost;
}
};
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class COMP250_1_2101327_AI_API UGOAPAgent : public UActorComponent
@ -16,11 +37,32 @@ public:
// Sets default values for this component's properties
UGOAPAgent();
UPROPERTY(EditAnywhere)
TArray<UGOAPAction*> AvailableActions;
UPROPERTY()
TMap<FString, int> Goals
{
{"PlayerHealth", 0}
};
protected:
// Called when the game starts
virtual void BeginPlay() override;
UPROPERTY()
ATurnBaseCombatV2* CombatSystem;
UFUNCTION()
bool BuildActionGraph(UActionNode* Parent, TArray<UActionNode*>& SuccessfulLeaves, TMap<FString, int> WorldState, TMap<FString, int> AgentGoals);
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
UFUNCTION()
TArray<UGOAPAction*> Plan(TMap<FString, int> WorldState, TMap<FString, int> AgentGoals);
UFUNCTION()
TMap<FString, int> GetWorldState();
};

View File

@ -16,7 +16,7 @@ public:
APlayerCharacter();
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float Health = 100.0f;
int Health = 100.0f;
protected:
// Called when the game starts or when spawned

View File

@ -24,7 +24,7 @@ public:
ATurnBaseCombatV2();
int* EnemyHealth = nullptr;
float* PlayerHealth = nullptr;
int* PlayerHealth = nullptr;
UPROPERTY(EditDefaultsOnly)
int DefaultActionPoints = 3;
UPROPERTY(EditDefaultsOnly)