From 08cd25ddc736dd8360b40d5a96a4c30be606ccfc Mon Sep 17 00:00:00 2001 From: Andrey Oleynik Date: Fri, 27 Oct 2023 23:29:54 +0200 Subject: [PATCH] Gameplay Tags Support --- Plugins/GOAPNPC/GOAPNPC.uplugin | 8 ++- .../GOAPNPC/Source/GOAPNPC/GOAPNPC.Build.cs | 9 ++- .../Source/GOAPNPC/Private/GOAPAction.cpp | 65 ++++++++++++++----- .../Source/GOAPNPC/Private/GOAPController.cpp | 20 +++--- .../Source/GOAPNPC/Private/GOAPNode.cpp | 29 ++++----- .../Source/GOAPNPC/Private/GOAPPlanner.cpp | 19 +++--- .../Source/GOAPNPC/Private/GOAPWorldState.cpp | 32 ++++----- .../Source/GOAPNPC/Public/GOAPAction.h | 62 +++++++++++++----- .../Source/GOAPNPC/Public/GOAPController.h | 4 +- .../GOAPNPC/Source/GOAPNPC/Public/GOAPNode.h | 16 ++--- .../Source/GOAPNPC/Public/GOAPPlanner.h | 2 +- .../Source/GOAPNPC/Public/GOAPWorldState.h | 20 +++--- 12 files changed, 178 insertions(+), 108 deletions(-) diff --git a/Plugins/GOAPNPC/GOAPNPC.uplugin b/Plugins/GOAPNPC/GOAPNPC.uplugin index fd17b96..afda355 100644 --- a/Plugins/GOAPNPC/GOAPNPC.uplugin +++ b/Plugins/GOAPNPC/GOAPNPC.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, "Version": 2, - "VersionName": "2.1", + "VersionName": "2.2", "FriendlyName": "GOAP NPC", "Description": "Goal-Oriented Action Planning for Non-Player Characters", "Category": "AI", @@ -23,5 +23,11 @@ "Win64" ] } + ], + "Plugins": [ + { + "Name": "GameplayTagsEditor", + "Enabled": true + } ] } diff --git a/Plugins/GOAPNPC/Source/GOAPNPC/GOAPNPC.Build.cs b/Plugins/GOAPNPC/Source/GOAPNPC/GOAPNPC.Build.cs index dad6001..c299ebe 100644 --- a/Plugins/GOAPNPC/Source/GOAPNPC/GOAPNPC.Build.cs +++ b/Plugins/GOAPNPC/Source/GOAPNPC/GOAPNPC.Build.cs @@ -31,6 +31,7 @@ public GOAPNPC(ReadOnlyTargetRules Target) : base(Target) new string[] { "Core", + "GameplayTags", // ... add other public dependencies that you statically link with here ... } ); @@ -47,8 +48,12 @@ public GOAPNPC(ReadOnlyTargetRules Target) : base(Target) // ... add private dependencies that you statically link with here ... } ); - - + + if (Target.bBuildEditor) + { + PrivateDependencyModuleNames.Add("GameplayTagsEditor"); + } + DynamicallyLoadedModuleNames.AddRange( new string[] { diff --git a/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPAction.cpp b/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPAction.cpp index 817f14e..7fe0042 100644 --- a/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPAction.cpp +++ b/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPAction.cpp @@ -7,24 +7,55 @@ */ #include "GOAPAction.h" +#if WITH_EDITORONLY_DATA +#include "GameplayTagsEditorModule.h" + +void FAtom::PostSerialize(const FArchive& Ar) +{ + if (Ar.IsLoading() && !name_DEPRECATED.IsEmpty()) + { + FString tagName = TEXT("GOAP.") + name_DEPRECATED; + tag = UGameplayTagsManager::Get().RequestGameplayTag(FName(tagName), false); + if (!tag.IsValid()) + { + IGameplayTagsEditorModule& tagsEditor = IGameplayTagsEditorModule::Get(); + bool result = tagsEditor.AddNewGameplayTagToINI(tagName); + + tag = UGameplayTagsManager::Get().RequestGameplayTag(FName(tagName)); + if (tag.IsValid()) + { + name_DEPRECATED = ""; + } + else + { + UE_LOG(LogTemp, Fatal, TEXT("Can't get Gameplay tag for (%s)"), *tagName); + } + } + } + +} +#endif // WITH_EDITORONLY_DATA -UGOAPAction::UGOAPAction() {} void UGOAPAction::create_P_E() { - for (FAtom itP : preconditions) + for (FAtom& itP : preconditions) { - wsPreconditions.addAtom(itP.name, itP.value); + wsPreconditions.addAtom(itP.tag, itP.value); } - for (FAtom itE : effects) + + for (FAtom& itE : effects) { - wsEffects.addAtom(itE.name, itE.value); + wsEffects.addAtom(itE.tag, itE.value); } - if (targetsType == NULL) + + if (targetsType == nullptr) + { UE_LOG(LogTemp, Warning, TEXT("Targets' type of '%s' action are not defined."), *name); + } } -TArray UGOAPAction::getTargetsList(APawn* p) +TArray UGOAPAction::getTargetsList(APawn* p) const { TArray actorsFound; // AVOID CRASHES, checking if targetsType is empty or not! @@ -32,39 +63,39 @@ TArray UGOAPAction::getTargetsList(APawn* p) return actorsFound; } -bool UGOAPAction::operator==(UGOAPAction& a) +bool UGOAPAction::operator==(const UGOAPAction& a) const { return this->cost == a.getCost() && target == a.getTarget() && wsPreconditions == a.getPreconditions() && wsEffects == a.getEffects(); } -bool UGOAPAction::operator!=(UGOAPAction& a) +bool UGOAPAction::operator!=(const UGOAPAction& a) const { return !(*this == a); } // GETS -FString UGOAPAction::getName() +FString UGOAPAction::getName() const { - return this->name; + return name; } -float UGOAPAction::getCost() +float UGOAPAction::getCost() const { - return this->cost; + return cost; } -AActor* UGOAPAction::getTarget() +AActor* UGOAPAction::getTarget() const { return target; } -GOAPWorldState UGOAPAction::getPreconditions() +const GOAPWorldState& UGOAPAction::getPreconditions() const { return wsPreconditions; } -GOAPWorldState UGOAPAction::getEffects() +const GOAPWorldState& UGOAPAction::getEffects() const { return wsEffects; } @@ -73,7 +104,7 @@ GOAPWorldState UGOAPAction::getEffects() void UGOAPAction::setName(FString n) { - this->name = n; + name = n; } void UGOAPAction::setCost(float c) diff --git a/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPController.cpp b/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPController.cpp index 36b9b5f..f88f752 100644 --- a/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPController.cpp +++ b/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPController.cpp @@ -24,11 +24,11 @@ void AGOAPController::BeginPlay() // Loads Current World. for (FAtom atom : currentWorld) - wsCurrentWorld.addAtom(atom.name, atom.value); + wsCurrentWorld.addAtom(atom.tag, atom.value); // Loads Desired World. for (FAtom atom : desiredWorld) - wsDesiredWorld.addAtom(atom.name, atom.value); + wsDesiredWorld.addAtom(atom.tag, atom.value); // Loads actions' preconditions and effects. for (UGOAPAction* a : auxActions) @@ -112,7 +112,7 @@ void AGOAPController::setGoal(const TArray& newGoal) void AGOAPController::updateGoal(const TArray& atoms) { for (FAtom atom : atoms) - wsDesiredWorld.addAtom(atom.name, atom.value); + wsDesiredWorld.addAtom(atom.tag, atom.value); } void AGOAPController::setCurrentWorld(const TArray& newCurrentWorld) @@ -124,26 +124,26 @@ void AGOAPController::setCurrentWorld(const TArray& newCurrentWorld) void AGOAPController::updateCurrentWorld(const TArray& atoms) { for (FAtom atom : atoms) - wsCurrentWorld.addAtom(atom.name, atom.value); + wsCurrentWorld.addAtom(atom.tag, atom.value); } -TArray AGOAPController::getCurrentWorldStateAtoms() +TArray AGOAPController::getCurrentWorldStateAtoms() const { TArray worldStateAtoms; - for (auto atoms : wsCurrentWorld.getAtoms()) + for (auto atom : wsCurrentWorld.getAtoms()) { - worldStateAtoms.Add({ atoms.first, atoms.second }); + worldStateAtoms.Add({ atom.Key, atom.Value }); } return worldStateAtoms; } -TArray AGOAPController::getDesiredWorldStateAtoms() +TArray AGOAPController::getDesiredWorldStateAtoms() const { TArray worldStateAtoms; - for (auto atoms : wsDesiredWorld.getAtoms()) + for (auto atom : wsDesiredWorld.getAtoms()) { - worldStateAtoms.Add({ atoms.first, atoms.second }); + worldStateAtoms.Add({ atom.Key, atom.Value }); } return worldStateAtoms; diff --git a/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPNode.cpp b/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPNode.cpp index c56bb8d..c4a607b 100644 --- a/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPNode.cpp +++ b/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPNode.cpp @@ -9,7 +9,6 @@ GOAPNode::GOAPNode() { - action = NULL; g = 0; h = 0; } @@ -17,43 +16,41 @@ GOAPNode::GOAPNode() GOAPNode::GOAPNode(UGOAPAction* a) { action = a; - if (a != NULL)g = a->getCost(); - else g = 0; + g = a ? a->getCost() : 0; h = 0; } -bool GOAPNode::operator==(GOAPNode n) +bool GOAPNode::operator==(const GOAPNode& n) const { return action == n.getAction(); } - -GOAPWorldState GOAPNode::getWorld() +const GOAPWorldState& GOAPNode::getWorld() const { return world; } -int GOAPNode::getH() +int GOAPNode::getH() const { return h; } -float GOAPNode::getG() +float GOAPNode::getG() const { return g; } -float GOAPNode::getF() +float GOAPNode::getF() const { return g + h; } -int GOAPNode::getParent() +int GOAPNode::getParent() const { return parent; } -UGOAPAction* GOAPNode::getAction() +UGOAPAction* GOAPNode::getAction() const { return action; } @@ -72,12 +69,14 @@ void GOAPNode::setH(GOAPWorldState w) { for (auto it : world.getAtoms()) { - auto aux = w.getAtoms().find(it.first); - if (aux != w.getAtoms().end()) + auto* value = w.getAtoms().Find(it.Key); + if (value) { - if (it.second != aux->second) ++h; + if (*value != it.Value) + ++h; } - else ++h; + else + ++h; } } diff --git a/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPPlanner.cpp b/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPPlanner.cpp index 4420ca1..6f14454 100644 --- a/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPPlanner.cpp +++ b/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPPlanner.cpp @@ -18,16 +18,16 @@ GOAPPlanner::GOAPPlanner(GOAPWorldState* c, GOAPWorldState* g, const TArray& opList) +const GOAPNode* GOAPPlanner::lowestFinList(const TArray& opList) const { - GOAPNode node; + const GOAPNode* node = nullptr; float minF = MAX_FLT; - for (GOAPNode n : opList) + for (const GOAPNode& n : opList) { if ((n.getF()) < minF) { - node = n; + node = &n; minF = n.getF(); } } @@ -35,10 +35,10 @@ GOAPNode GOAPPlanner::lowestFinList(const TArray& opList) return node; } -bool containsNode(GOAPNode node, const TArray& list) +bool containsNode(const GOAPNode& node, const TArray& list) { bool contains = false; - for (GOAPNode n : list) + for (const GOAPNode& n : list) { if (n == node) { @@ -80,7 +80,10 @@ TArray GOAPPlanner::generatePlan(APawn* p) { TArray sol; - GOAPNode start; start.setWorld(*currentWorld); start.setParent(-1); + GOAPNode start; + start.setWorld(*currentWorld); + start.setParent(-1); + GOAPNode last; openList.Empty(); closedList.Empty(); @@ -91,7 +94,7 @@ TArray GOAPPlanner::generatePlan(APawn* p) // Search and create the cheapest path between actions having into account their preconditions, effects and cost. while (continues) { - GOAPNode current = lowestFinList(openList); + GOAPNode current = *lowestFinList(openList); openList.Remove(current); closedList.Push(current); int pos = closedList.Num() - 1; diff --git a/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPWorldState.cpp b/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPWorldState.cpp index 984468d..38b2f82 100644 --- a/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPWorldState.cpp +++ b/Plugins/GOAPNPC/Source/GOAPNPC/Private/GOAPWorldState.cpp @@ -11,24 +11,24 @@ GOAPWorldState::GOAPWorldState() {} GOAPWorldState::~GOAPWorldState() {} -GOAPWorldState::GOAPWorldState(const std::map& a) +GOAPWorldState::GOAPWorldState(const TMap& a) { atoms = a; } -bool GOAPWorldState::operator==(GOAPWorldState w) +bool GOAPWorldState::operator==(const GOAPWorldState& w) const { - return atoms.size() == w.getAtoms().size() && std::equal(atoms.begin(), atoms.end(), w.getAtoms().begin()); + return atoms.OrderIndependentCompareEqual(w.getAtoms()); } -bool GOAPWorldState::isIncluded(GOAPWorldState w) +bool GOAPWorldState::isIncluded(const GOAPWorldState& w) const { for (auto requirement : w.getAtoms()) { - auto it = atoms.find(requirement.first); - if (it != atoms.end()) + const auto* value = atoms.Find(requirement.Key); + if (value) { - if (it->second != requirement.second) + if (*value != requirement.Value) return false; } else return false; @@ -37,35 +37,35 @@ bool GOAPWorldState::isIncluded(GOAPWorldState w) } -const std::map& GOAPWorldState::getAtoms() +const TMap& GOAPWorldState::getAtoms() const { return atoms; } -void GOAPWorldState::setAtoms(const std::map& a) +void GOAPWorldState::setAtoms(const TMap& a) { atoms = a; } -void GOAPWorldState::addAtom(FString name, bool value) +void GOAPWorldState::addAtom(FGameplayTag name, bool value) { - atoms[name] = value; + atoms.Add(name, value); } void GOAPWorldState::cleanAtoms() { - atoms.clear(); + atoms.Empty(); } -void GOAPWorldState::joinWorldState(GOAPWorldState w) +void GOAPWorldState::joinWorldState(const GOAPWorldState& w) { for (auto atom : w.getAtoms()) { - atoms[atom.first] = atom.second; + atoms.Add(atom.Key, atom.Value); } } -bool GOAPWorldState::isEmpty() +bool GOAPWorldState::isEmpty() const { - return atoms.size() == 0; + return atoms.Num() == 0; } diff --git a/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPAction.h b/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPAction.h index 8e768ee..158693e 100644 --- a/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPAction.h +++ b/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPAction.h @@ -12,25 +12,54 @@ #include "Engine/World.h" #include "Kismet/GameplayStatics.h" #include "UObject/NoExportTypes.h" + +#include "GameplayTags.h" + #include "GOAPAction.generated.h" /** * Auxiliary struct to get WorldState's atoms from Blueprints' description. */ -USTRUCT(BlueprintType, Blueprintable) -struct FAtom +USTRUCT(BlueprintType) +struct GOAPNPC_API FAtom { GENERATED_USTRUCT_BODY() - // Name of the atom (predicate). - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Atom) - FString name; + FAtom() = default; + + FAtom(FGameplayTag _tag, bool _value) + : tag(_tag) + , value(_value) + {} + +#if WITH_EDITORONLY_DATA + // DEPRECATED: Name of the atom (predicate). + UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Use gameplay tag instead.")) + FString name_DEPRECATED; + + void PostSerialize(const FArchive& Ar); +#endif + + // Name of the atom (predicate). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (Category = "Atom")) + FGameplayTag tag; // Value of the atom (truth value). UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Atom) - bool value; + bool value = false; + +}; +#if WITH_EDITORONLY_DATA +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithPostSerialize = true + }; }; +#endif /** * GOAPAction class contains every attribute and function needed to define an action. @@ -65,19 +94,16 @@ class GOAPNPC_API UGOAPAction : public UObject private: - AActor* target; + AActor* target = nullptr; GOAPWorldState wsPreconditions; GOAPWorldState wsEffects; public: - - UGOAPAction(); - // Search all actors of targetsType class located in the world. UFUNCTION(BlueprintCallable, Category = GOAPAction) - TArray getTargetsList(APawn* p); + TArray getTargetsList(APawn* p) const; // Optional function to check if it's possible to perform the action. UFUNCTION(BlueprintImplementableEvent, Category = GOAPAction) @@ -92,23 +118,23 @@ class GOAPNPC_API UGOAPAction : public UObject // COMPARATORS - bool operator==(UGOAPAction& action); + bool operator==(const UGOAPAction& action) const; - bool operator!=(UGOAPAction& action); + bool operator!=(const UGOAPAction& action) const; // GETS - FString getName(); + FString getName() const; - float getCost(); + float getCost() const; // Gets the chosen target from targetList or the one specific in setTarget(). UFUNCTION(BlueprintCallable, Category = GOAPAction) - AActor* getTarget(); + AActor* getTarget() const; - GOAPWorldState getPreconditions(); + const GOAPWorldState& getPreconditions() const; - GOAPWorldState getEffects(); + const GOAPWorldState& getEffects() const; // SETS diff --git a/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPController.h b/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPController.h index e508ccb..17449d8 100644 --- a/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPController.h +++ b/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPController.h @@ -104,11 +104,11 @@ class GOAPNPC_API AGOAPController : public AAIController // Returns the current world state atoms. UFUNCTION(BlueprintCallable, Category = GOAPController) - TArray getCurrentWorldStateAtoms(); + TArray getCurrentWorldStateAtoms() const; // Returns the desired world state atoms. UFUNCTION(BlueprintCallable, Category = GOAPController) - TArray getDesiredWorldStateAtoms(); + TArray getDesiredWorldStateAtoms() const; private: void debugInfo(); diff --git a/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPNode.h b/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPNode.h index 0fd16c5..ecc9405 100644 --- a/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPNode.h +++ b/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPNode.h @@ -26,7 +26,7 @@ class GOAPNPC_API GOAPNode int parent; // Chosen action to reach this node. - UGOAPAction* action; + UGOAPAction* action = nullptr; public: GOAPNode(); @@ -34,21 +34,21 @@ class GOAPNPC_API GOAPNode GOAPNode(UGOAPAction* a); //OPERATORS - bool operator==(GOAPNode n); + bool operator==(const GOAPNode& n) const; // GETS - int getH(); + int getH() const; - float getG(); + float getG() const; - float getF(); + float getF() const; - int getParent(); + int getParent() const; - GOAPWorldState getWorld(); + const GOAPWorldState& getWorld() const; - UGOAPAction* getAction(); + UGOAPAction* getAction() const; // SETS diff --git a/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPPlanner.h b/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPPlanner.h index 25a3ae4..fb94c2a 100644 --- a/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPPlanner.h +++ b/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPPlanner.h @@ -45,7 +45,7 @@ class GOAPNPC_API GOAPPlanner // Get the node with lowest F's value. // F = G (real cost at this state) + H (estimated cost from this state). - GOAPNode lowestFinList(const TArray& opList); + const GOAPNode* lowestFinList(const TArray& opList) const; // Returns the nodes adjacent to the current one. TArray getAdjacent(GOAPNode current, const TArray& vActions, APawn* p); diff --git a/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPWorldState.h b/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPWorldState.h index cfee52a..83b794f 100644 --- a/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPWorldState.h +++ b/Plugins/GOAPNPC/Source/GOAPNPC/Public/GOAPWorldState.h @@ -7,8 +7,8 @@ */ #pragma once -#include #include "CoreMinimal.h" +#include "GameplayTags.h" /** * The state of the world (the 'logic world' for reasoning with GOAP) is made up of atoms. @@ -18,7 +18,7 @@ class GOAPNPC_API GOAPWorldState { private: - std::map atoms; + TMap atoms; public: @@ -26,22 +26,22 @@ class GOAPNPC_API GOAPWorldState ~GOAPWorldState(); - GOAPWorldState(const std::map& atoms); + GOAPWorldState(const TMap& atoms); - bool operator==(GOAPWorldState w); + bool operator==(const GOAPWorldState& w) const; - bool isIncluded(GOAPWorldState w); + bool isIncluded(const GOAPWorldState& w) const; - const std::map& getAtoms(); + const TMap& getAtoms() const; - void setAtoms(const std::map& atoms); + void setAtoms(const TMap& atoms); - void addAtom(FString name, bool value); + void addAtom(FGameplayTag name, bool value); void cleanAtoms(); // Mixes two states of the world - void joinWorldState(GOAPWorldState w); + void joinWorldState(const GOAPWorldState& w); - bool isEmpty(); + bool isEmpty() const; };