From 279669ebcdfdde0ae911543adccad99d4105a0b1 Mon Sep 17 00:00:00 2001 From: PHILIP White Date: Mon, 20 Mar 2023 07:30:39 +0000 Subject: [PATCH] Added GOAPAgent Functionality --- .../Content/Blueprints/BP_Enemy.uasset | 4 +- COMP250_1_2101327_AI/Content/Main.umap | 4 +- .../Blueprints/Blueprint_CeilingLight.uasset | 4 +- .../Particles/P_Ambient_Dust.uasset | 4 +- .../COMP250_1_2101327_AI/GOAP/GOAPAction.cpp | 16 ++-- .../COMP250_1_2101327_AI/GOAP/GOAPAction.h | 18 +++- .../COMP250_1_2101327_AI/GOAP/GOAPAgent.cpp | 96 ++++++++++++++++++- .../COMP250_1_2101327_AI/GOAP/GOAPAgent.h | 42 ++++++++ .../Player/PlayerCharacter.h | 2 +- .../TurnBasedCombatV2/TurnBaseCombatV2.h | 2 +- 10 files changed, 165 insertions(+), 27 deletions(-) diff --git a/COMP250_1_2101327_AI/Content/Blueprints/BP_Enemy.uasset b/COMP250_1_2101327_AI/Content/Blueprints/BP_Enemy.uasset index 9efb07d..51b2b36 100644 --- a/COMP250_1_2101327_AI/Content/Blueprints/BP_Enemy.uasset +++ b/COMP250_1_2101327_AI/Content/Blueprints/BP_Enemy.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb612e0a38a91147a38ab4fd2ee051e27a07fb91377b0494595af2a7f7263b20 -size 29891 +oid sha256:f0b8e1bebdd8b3648c287b544d3a1617ba39496750c9aac31bc5a927f710e9da +size 30776 diff --git a/COMP250_1_2101327_AI/Content/Main.umap b/COMP250_1_2101327_AI/Content/Main.umap index 13490b0..b2c7b41 100644 --- a/COMP250_1_2101327_AI/Content/Main.umap +++ b/COMP250_1_2101327_AI/Content/Main.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85d51dd4abb6c5d7fb44fd0b03b6cc5198d66632ab01a333c791a04018ea6d6a -size 39819 +oid sha256:9afc72fdb57e571cd6825ba08df57e5d348f4d0c637a1dc9e749b73856d25832 +size 40282 diff --git a/COMP250_1_2101327_AI/Content/StarterContent/Blueprints/Blueprint_CeilingLight.uasset b/COMP250_1_2101327_AI/Content/StarterContent/Blueprints/Blueprint_CeilingLight.uasset index c5c3b84..fe3dc98 100644 --- a/COMP250_1_2101327_AI/Content/StarterContent/Blueprints/Blueprint_CeilingLight.uasset +++ b/COMP250_1_2101327_AI/Content/StarterContent/Blueprints/Blueprint_CeilingLight.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a60a29ad596546d481e43dfb8698842a78cc07f4a4b1000fa397cfba4e72331 -size 158206 +oid sha256:05f0f54c9bd76fa02141c2e0c2afb6728419f333f39c645391bd0dfdc34da9b2 +size 43745 diff --git a/COMP250_1_2101327_AI/Content/StarterContent/Particles/P_Ambient_Dust.uasset b/COMP250_1_2101327_AI/Content/StarterContent/Particles/P_Ambient_Dust.uasset index 61a42c1..bf2e439 100644 --- a/COMP250_1_2101327_AI/Content/StarterContent/Particles/P_Ambient_Dust.uasset +++ b/COMP250_1_2101327_AI/Content/StarterContent/Particles/P_Ambient_Dust.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f4f9de3e23ca1581b34ae27f09764daa4b35d4b62b05819d4541165aced43fd -size 53268 +oid sha256:01ef616c7a8bd90cd1b7a13efb18a56f33346efbae51efa31f09804478b7621d +size 43456 diff --git a/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAction.cpp b/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAction.cpp index 84087c7..02e6da8 100644 --- a/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAction.cpp +++ b/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAction.cpp @@ -3,29 +3,25 @@ #include "GOAPAction.h" -bool UGOAPAction::CheckPreConditions(TMap WorldState) +bool UGOAPAction::CheckPreConditions(TMap WorldState) { - for (TPair PreCondition : PreConditions) + for (TPair 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 UGOAPAction::ApplyEffects(TMap WorldState) +TMap UGOAPAction::ApplyEffects(TMap WorldState) { - for (TPair Effect : Effects) + for (TPair 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; diff --git a/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAction.h b/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAction.h index c3311ea..8cfb3bc 100644 --- a/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAction.h +++ b/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAction.h @@ -15,10 +15,18 @@ class COMP250_1_2101327_AI_API UGOAPAction : public UObject GENERATED_BODY() public: - virtual bool CheckPreConditions(TMap WorldState); - TMap ApplyEffects(TMap WorldState); + float ActionCost = 1.0f; + + UFUNCTION() + bool CheckPreConditions(TMap WorldState); + UFUNCTION() + TMap ApplyEffects(TMap WorldState); -protected: - TMap PreConditions; - TMap Effects; + UPROPERTY() + TMap PreConditions; + UPROPERTY() + TMap Effects; + + UFUNCTION() + virtual void Init(){} }; diff --git a/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAgent.cpp b/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAgent.cpp index b960949..9a8e43b 100644 --- a/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAgent.cpp +++ b/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAgent.cpp @@ -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(GetWorld()->GetGameState()); + + UGOAPAction* DefaultAttack = NewObject(GetWorld(), UDefaultAttack::StaticClass()); + DefaultAttack->Init(); + AvailableActions.Add(DefaultAttack); + UGOAPAction* Combo_P = NewObject(GetWorld(), UCombo_P::StaticClass()); + Combo_P->Init(); + AvailableActions.Add(Combo_P); } +TMap UGOAPAgent::GetWorldState() +{ + TMap 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& SuccessfulLeaves, TMap WorldState, TMap AgentGoals) +{ + bool bFoundAPlan = false; + + for (UGOAPAction* Action : AvailableActions) + { + if (Action->CheckPreConditions(WorldState)) + { + TMap NewWorldState = Action->ApplyEffects(WorldState); + UActionNode* NewNode = NewObject(); + NewNode->Init(Parent, Action, NewWorldState, Parent->Cost + Action->ActionCost); + + if (NewWorldState["PlayerHealth"] <= 0) + { + SuccessfulLeaves.Add(NewNode); + bFoundAPlan = true; + } + else + { + TArray 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 UGOAPAgent::Plan(TMap WorldState, TMap AgentGoals) +{ + TArray ActionPlan; + UActionNode* RootActionNode = NewObject(); + RootActionNode->Init(nullptr, nullptr, WorldState, 0); + TArray 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 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; +} diff --git a/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAgent.h b/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAgent.h index 9dc3500..b1caddd 100644 --- a/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAgent.h +++ b/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/GOAP/GOAPAgent.h @@ -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 State; + float Cost; + + void Init(UActionNode* ParentNode, UGOAPAction* GOAPAction, TMap 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 AvailableActions; + + UPROPERTY() + TMap Goals + { + {"PlayerHealth", 0} + }; + protected: // Called when the game starts virtual void BeginPlay() override; + UPROPERTY() + ATurnBaseCombatV2* CombatSystem; + + UFUNCTION() + bool BuildActionGraph(UActionNode* Parent, TArray& SuccessfulLeaves, TMap WorldState, TMap AgentGoals); + public: // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + + UFUNCTION() + TArray Plan(TMap WorldState, TMap AgentGoals); + + UFUNCTION() + TMap GetWorldState(); }; diff --git a/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/Player/PlayerCharacter.h b/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/Player/PlayerCharacter.h index c6951b5..dc8b8e0 100644 --- a/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/Player/PlayerCharacter.h +++ b/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/Player/PlayerCharacter.h @@ -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 diff --git a/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/TurnBasedCombatV2/TurnBaseCombatV2.h b/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/TurnBasedCombatV2/TurnBaseCombatV2.h index 7b63cc6..04d32e2 100644 --- a/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/TurnBasedCombatV2/TurnBaseCombatV2.h +++ b/COMP250_1_2101327_AI/Source/COMP250_1_2101327_AI/TurnBasedCombatV2/TurnBaseCombatV2.h @@ -24,7 +24,7 @@ public: ATurnBaseCombatV2(); int* EnemyHealth = nullptr; - float* PlayerHealth = nullptr; + int* PlayerHealth = nullptr; UPROPERTY(EditDefaultsOnly) int DefaultActionPoints = 3; UPROPERTY(EditDefaultsOnly)