Fixed freezes on controller calling MarkPath() to invalid locations

This commit is contained in:
Maximilian Fajnberg 2022-06-08 11:39:41 +02:00
parent 28a226e35d
commit 26d3289962
5 changed files with 177 additions and 73 deletions

View File

@ -7,6 +7,7 @@
#include "AdventurePlayerController.h" #include "AdventurePlayerController.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "Algo/Reverse.h" #include "Algo/Reverse.h"
#include "Util/IndexPriorityQueue.h"
// Sets default values // Sets default values
AAdventureMap::AAdventureMap() AAdventureMap::AAdventureMap()
@ -85,12 +86,12 @@ AHexTile* AAdventureMap::RandomHex()
TArray<AHexTile*> AAdventureMap::Neighbors(AHexTile* OfHex, bool bFreeOnly = false) TArray<AHexTile*> AAdventureMap::Neighbors(AHexTile* OfHex, bool bFreeOnly = false)
{ {
TArray<AHexTile*> Results; TArray<AHexTile*> Results;
for (auto& V : UnitVectors) { for (FHexVector NeighborVector : UnitVectors) {
int32 I = GridIndex(OfHex->Q + V.Q, OfHex->R + V.R); int32 Index = GridIndex(OfHex->Q + NeighborVector.Q, OfHex->R + NeighborVector.R);
if (Grid.IsValidIndex(I)) { if (Grid.IsValidIndex(Index)) {
AHexTile* H = Grid[I]; AHexTile* Hex = Grid[Index];
if (bFreeOnly && !H->bFree) { continue; } if (bFreeOnly && !Hex->bFree) { continue; }
if (H->Distance(OfHex) == 1) { Results.Add(H); } if (Hex->Distance(OfHex) == 1) { Results.Add(Hex); }
} }
} }
return Results; return Results;
@ -120,66 +121,66 @@ TArray<AHexTile*> AAdventureMap::FreeDiagonals(AHexTile* OfHex)
TSet<AHexTile*> AAdventureMap::BreadthFirstSearch(AHexTile* Start, int32 Radius) TSet<AHexTile*> AAdventureMap::BreadthFirstSearch(AHexTile* Start, int32 Radius)
{ {
TSet<AHexTile*> Results; TSet<AHexTile*> Results;
TArray<AHexTile*> ToExamine; TArray<AHexTile*> Frontier;
TSet<AHexTile*> Processed; TSet<AHexTile*> Processed;
Results.Add(Start); Results.Add(Start);
ToExamine.Add(Start); Frontier.Add(Start);
while (!ToExamine.IsEmpty()) { while (!Frontier.IsEmpty()) {
AHexTile* Candidate = ToExamine[0]; AHexTile* Current = Frontier[0];
Processed.Add(Candidate); Processed.Add(Current);
ToExamine.Remove(Candidate); Frontier.Remove(Current);
for (AHexTile* Neighbor : Neighbors(Candidate)) { for (AHexTile* Neighbor : Neighbors(Current)) {
if (Neighbor->Distance(Candidate) > 1) { continue; } if (Neighbor->Distance(Current) > 1) { continue; }
if (Processed.Contains(Neighbor)) { continue; } if (Processed.Contains(Neighbor)) { continue; }
if (Neighbor->Distance(Start) > Radius) { continue; } if (Neighbor->Distance(Start) > Radius) { continue; }
ToExamine.Add(Neighbor); Frontier.Add(Neighbor);
Results.Add(Neighbor); Results.Add(Neighbor);
} }
} }
return Results; return Results;
} }
/* // Faulty implementation which uses an actual PriorityQueue
TArray<AHexTile*> AAdventureMap::FindPathAStar(AHexTile* Start, AHexTile* Goal, bool bDiags) TArray<AHexTile*> AAdventureMap::FindPathAStar(AHexTile* Start, AHexTile* Goal, bool bDiags)
{ {
TArray<AHexTile*> ToExamine;
TSet<AHexTile*> Processed; TSet<AHexTile*> Processed;
ToExamine.Add(Start); TSet<AHexTile*> ToSearch;
TPriorityQueue<AHexTile*> Frontier;
while (!ToExamine.IsEmpty()) { Start->GCost = 0;
AHexTile* Candidate = ToExamine[0]; Frontier.Push(Start, .0f);
ToExamine.Remove(Candidate); ToSearch.Add(Start);
// find Hex with lower estimatet F-cost
for (AHexTile* t : ToExamine) {
t->FCost = t->GCost + t->HCost;
if (t->FCost < Candidate->FCost || t->FCost == Candidate->FCost && t->HCost < Candidate->HCost) { Candidate = t; }
}
Processed.Add(Candidate); while (!Frontier.IsEmpty()) {
// exit AHexTile* Current = Frontier.Pop();
if (Candidate == Goal) { break; } ToSearch.Remove(Current);
// expand frontier & adjust path data if (Current == Goal) { break; }
for (AHexTile* Neighbor : Neighbors(Candidate, true)) { Processed.Add(Current);
if (!Neighbor->bFree) { continue; }
for (AHexTile* Neighbor : Neighbors(Current, true)) {
if (Processed.Contains(Neighbor)) { continue; } if (Processed.Contains(Neighbor)) { continue; }
bool bInToSearch = ToSearch.Contains(Start);
bool bInToExamine = ToExamine.Contains(Neighbor); int32 NewGCost = Current->GCost + Neighbor->MoveCost;
float NewGCost = Candidate->GCost + Neighbor->MoveCost;
if (NewGCost < Neighbor->GCost || !bInToExamine) { if (!bInToSearch || NewGCost < Neighbor->GCost) {
Neighbor->GCost = NewGCost; Neighbor->GCost = NewGCost;
Neighbor->CameFrom = Candidate; // chain Neighbor->CameFrom = Current;
Neighbor->bDiagMove = false;
if (!bInToExamine) { if (!bInToSearch) {
Neighbor->HCost = Neighbor->Distance(Goal); Neighbor->HCost = Neighbor->Distance(Goal);
ToExamine.Add(Neighbor); Frontier.Push(Neighbor, NewGCost + Neighbor->HCost);
} } ToSearch.Add(Neighbor);
} }
} }
}
}
TArray<AHexTile*> Path; TArray<AHexTile*> Path;
if (!IsValid(Goal->CameFrom)) { return Path; } if (!IsValid(Goal->CameFrom)) { return Path; }
AHexTile* iPathNode = Goal; AHexTile* iPathNode = Goal;
@ -188,10 +189,62 @@ TArray<AHexTile*> AAdventureMap::FindPathAStar(AHexTile* Start, AHexTile* Goal,
iPathNode = iPathNode->CameFrom; iPathNode = iPathNode->CameFrom;
} }
Algo::Reverse(Path); Algo::Reverse(Path);
return Path;
}
*/
if (bDiags) { // DO NOT USE TArray<AHexTile*> AAdventureMap::FindPathAStar(AHexTile * Start, AHexTile * Goal, bool bDiags)
Path = ShortcutAStar(Path); {
TArray<AHexTile*> Frontier;
TSet<AHexTile*> Processed;
Frontier.Add(Start);
while (!Frontier.IsEmpty()) {
// Pop
AHexTile* Candidate = Frontier[0];
// Exit
if (Candidate == Goal) { break; }
// Find contender with an even lower F-cost
for (AHexTile* Other : Frontier) {
if (Other->FCost < Candidate->FCost
|| Other->FCost == Candidate->FCost && Other->HCost < Candidate->HCost)
{ Candidate = Other; }
} }
Frontier.Remove(Candidate);
Processed.Add(Candidate);
// Expand frontier, make connections when appropriate
for (AHexTile* Neighbor : Neighbors(Candidate, true)) {
if (Processed.Contains(Neighbor)) { continue; }
bool bInFrontier = Frontier.Contains(Neighbor);
int32 NewGCost = Candidate->GCost + Neighbor->MoveCost;
if (NewGCost < Neighbor->GCost || !bInFrontier) {
Neighbor->GCost = NewGCost;
Neighbor->HCost = Neighbor->Distance(Goal);
Neighbor->FCost = Neighbor->GCost + Neighbor->HCost;
Neighbor->CameFrom = Candidate; // chain
if (!bInFrontier) {
Frontier.Add(Neighbor);
}
}
}
}
// Build and return path
TArray<AHexTile*> Path;
AHexTile* iPathNode = Goal;
while (iPathNode != Start) {
if (!IsValid(iPathNode->CameFrom) || !iPathNode->bFree) {
Path.Empty();
return Path;
}
Path.Emplace(iPathNode);
iPathNode = iPathNode->CameFrom;
}
Algo::Reverse(Path);
return Path; return Path;
} }
@ -246,11 +299,11 @@ TArray<AHexTile*> AAdventureMap::ShortcutAStar(TArray<AHexTile*> Path)
if (DiagIsReachable(CurrentHex, UnitDiag)) { if (DiagIsReachable(CurrentHex, UnitDiag)) {
int32 CanIndex = GridIndex(CurrentHex->Q + UnitDiag.Q, CurrentHex->R + UnitDiag.R); int32 CanIndex = GridIndex(CurrentHex->Q + UnitDiag.Q, CurrentHex->R + UnitDiag.R);
if (Grid.IsValidIndex(CanIndex)) { if (Grid.IsValidIndex(CanIndex)) {
AHexTile* Candidate = Grid[CanIndex]; AHexTile* Current = Grid[CanIndex];
if (Candidate->bFree) { if (Current->bFree) {
Shortcut.Add(Candidate); Shortcut.Add(Current);
bDiagAdded = true; bDiagAdded = true;
CurrentHex = Candidate; CurrentHex = Current;
NumDiags--; NumDiags--;
continue; continue;
} } } } } }
@ -299,3 +352,4 @@ bool AAdventureMap::DiagIsReachable(AHexTile* InStart, FHexVector InDiagUnitVec)
AHexTile* HexB = Grid[IndexB]; AHexTile* HexB = Grid[IndexB];
return (HexA->bFree && HexB->bFree); return (HexA->bFree && HexB->bFree);
} }

View File

@ -106,3 +106,63 @@ protected:
// Called when the game starts or when spawned // Called when the game starts or when spawned
virtual void BeginPlay() override; virtual void BeginPlay() override;
}; };
// only used for an experimental implementation of A*
template <typename InElementType>
struct TPriorityQueueNode {
InElementType Element;
float Priority;
TPriorityQueueNode()
{
}
TPriorityQueueNode(InElementType InElement, float InPriority)
{
Element = InElement;
Priority = InPriority;
}
bool operator<(const TPriorityQueueNode<InElementType> Other) const
{
return Priority < Other.Priority;
}
};
template <typename InElementType>
class TPriorityQueue {
public:
TPriorityQueue()
{
Array.Heapify();
}
public:
// Always check if IsEmpty() before Pop-ing!
InElementType Pop()
{
TPriorityQueueNode<InElementType> Node;
Array.HeapPop(Node);
return Node.Element;
}
TPriorityQueueNode<InElementType> PopNode()
{
TPriorityQueueNode<InElementType> Node;
Array.HeapPop(Node);
return Node;
}
void Push(InElementType Element, float Priority)
{
Array.HeapPush(TPriorityQueueNode<InElementType>(Element, Priority));
}
bool IsEmpty() const
{
return Array.Num() == 0;
}
public: // make private later on
TArray<TPriorityQueueNode<InElementType>> Array;
};

View File

@ -59,7 +59,6 @@ TArray<AHexTile*> AAdventurePlayerController::Vision(int32 Radius)
{ {
TArray<AHexTile*> Results; TArray<AHexTile*> Results;
TSet<AHexTile*> Visible; TSet<AHexTile*> Visible;
if (CurrentHex->bDiagMove) { Radius = FMath::FloorToInt(float(Radius) * (2.f / 3.f)); }
Visible = MapRef->BreadthFirstSearch(CurrentHex, Radius); Visible = MapRef->BreadthFirstSearch(CurrentHex, Radius);
for (auto& Hex : Visible) { for (auto& Hex : Visible) {
if (ExploredHexes.Contains(Hex)) { continue; } if (ExploredHexes.Contains(Hex)) { continue; }
@ -71,6 +70,10 @@ TArray<AHexTile*> AAdventurePlayerController::Vision(int32 Radius)
void AAdventurePlayerController::MarkPath(TArray<AHexTile*> Path) void AAdventurePlayerController::MarkPath(TArray<AHexTile*> Path)
{ {
if (Path.IsEmpty()) { return; }
else if (!IsValid(Path[0])) { return; }
else if (CurrentHex->Distance(Path[0])>1) { return; }
FHexVector DirA = FHexVector(Path[0]->Q - CurrentHex->Q, Path[0]->R - CurrentHex->R); FHexVector DirA = FHexVector(Path[0]->Q - CurrentHex->Q, Path[0]->R - CurrentHex->R);
FHexVector DirB; FHexVector DirB;
for (int32 i = 0; i < Path.Num() - 1; i++) for (int32 i = 0; i < Path.Num() - 1; i++)

View File

@ -48,19 +48,15 @@ public:
// Pathfinding // Pathfinding
UPROPERTY(BlueprintReadWrite) UPROPERTY(BlueprintReadWrite)
float MoveCost = 10; int32 MoveCost = 1;
UPROPERTY()
int32 FCost;
UPROPERTY()
int32 GCost = 9999;
UPROPERTY()
int32 HCost;
UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Runtime") UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Runtime")
AHexTile* CameFrom; AHexTile* CameFrom;
UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Runtime")
AHexTile* LeadsTo;
UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Runtime")
bool bDiagMove = false;
UPROPERTY()
float FCost;
UPROPERTY()
float GCost;
UPROPERTY()
float HCost = 9999;
// MapObject Placement // MapObject Placement
UPROPERTY(BlueprintReadWrite, VisibleAnywhere) UPROPERTY(BlueprintReadWrite, VisibleAnywhere)

View File

@ -26,60 +26,51 @@ AMovementArrow::AMovementArrow()
} }
void AMovementArrow::SetVariant(FHexVector InVector, FHexVector OutVector) { void AMovementArrow::SetVariant(FHexVector InVector, FHexVector OutVector) {
int32 InVectorIndex = MapRef->UnitVectors.Find(InVector); int32 InVectorIndex = MapRef->UnitVectors.Find(InVector);
int32 OutVectorIndex = MapRef->UnitVectors.Find(OutVector); int32 OutVectorIndex = MapRef->UnitVectors.Find(OutVector);
int32 VariantIndex; int32 VariantIndex;
if (InVectorIndex == 0) {
if (InVectorIndex == 0)
{
if (OutVectorIndex == 0) { VariantIndex = 0; } if (OutVectorIndex == 0) { VariantIndex = 0; }
if (OutVectorIndex == 1) { VariantIndex = 1; } if (OutVectorIndex == 1) { VariantIndex = 1; }
if (OutVectorIndex == 2) { VariantIndex = 2; } if (OutVectorIndex == 2) { VariantIndex = 2; }
if (OutVectorIndex == 4) { VariantIndex = 3; } if (OutVectorIndex == 4) { VariantIndex = 3; }
if (OutVectorIndex == 5) { VariantIndex = 4; } if (OutVectorIndex == 5) { VariantIndex = 4; }
} }
if (InVectorIndex == 1) if (InVectorIndex == 1) {
{
if (OutVectorIndex == 0) { VariantIndex = 4; } if (OutVectorIndex == 0) { VariantIndex = 4; }
if (OutVectorIndex == 1) { VariantIndex = 0; } if (OutVectorIndex == 1) { VariantIndex = 0; }
if (OutVectorIndex == 2) { VariantIndex = 1; } if (OutVectorIndex == 2) { VariantIndex = 1; }
if (OutVectorIndex == 3) { VariantIndex = 2; } if (OutVectorIndex == 3) { VariantIndex = 2; }
if (OutVectorIndex == 5) { VariantIndex = 3; } if (OutVectorIndex == 5) { VariantIndex = 3; }
} }
if (InVectorIndex == 2) if (InVectorIndex == 2) {
{
if (OutVectorIndex == 0) { VariantIndex = 3; } if (OutVectorIndex == 0) { VariantIndex = 3; }
if (OutVectorIndex == 1) { VariantIndex = 4; } if (OutVectorIndex == 1) { VariantIndex = 4; }
if (OutVectorIndex == 2) { VariantIndex = 0; } if (OutVectorIndex == 2) { VariantIndex = 0; }
if (OutVectorIndex == 3) { VariantIndex = 1; } if (OutVectorIndex == 3) { VariantIndex = 1; }
if (OutVectorIndex == 4) { VariantIndex = 2; } if (OutVectorIndex == 4) { VariantIndex = 2; }
} }
if (InVectorIndex == 3) if (InVectorIndex == 3) {
{
if (OutVectorIndex == 1) { VariantIndex = 3; } if (OutVectorIndex == 1) { VariantIndex = 3; }
if (OutVectorIndex == 2) { VariantIndex = 4; } if (OutVectorIndex == 2) { VariantIndex = 4; }
if (OutVectorIndex == 3) { VariantIndex = 0; } if (OutVectorIndex == 3) { VariantIndex = 0; }
if (OutVectorIndex == 4) { VariantIndex = 1; } if (OutVectorIndex == 4) { VariantIndex = 1; }
if (OutVectorIndex == 5) { VariantIndex = 2; } if (OutVectorIndex == 5) { VariantIndex = 2; }
} }
if (InVectorIndex == 4) if (InVectorIndex == 4) {
{
if (OutVectorIndex == 0) { VariantIndex = 2; } if (OutVectorIndex == 0) { VariantIndex = 2; }
if (OutVectorIndex == 2) { VariantIndex = 3; } if (OutVectorIndex == 2) { VariantIndex = 3; }
if (OutVectorIndex == 3) { VariantIndex = 4; } if (OutVectorIndex == 3) { VariantIndex = 4; }
if (OutVectorIndex == 4) { VariantIndex = 0; } if (OutVectorIndex == 4) { VariantIndex = 0; }
if (OutVectorIndex == 5) { VariantIndex = 1; } if (OutVectorIndex == 5) { VariantIndex = 1; }
} }
if (InVectorIndex == 5) if (InVectorIndex == 5) {
{
if (OutVectorIndex == 0) { VariantIndex = 1; } if (OutVectorIndex == 0) { VariantIndex = 1; }
if (OutVectorIndex == 1) { VariantIndex = 2; } if (OutVectorIndex == 1) { VariantIndex = 2; }
if (OutVectorIndex == 3) { VariantIndex = 3; } if (OutVectorIndex == 3) { VariantIndex = 3; }
if (OutVectorIndex == 4) { VariantIndex = 4; } if (OutVectorIndex == 4) { VariantIndex = 4; }
if (OutVectorIndex == 5) { VariantIndex = 0; } if (OutVectorIndex == 5) { VariantIndex = 0; }
} }
SceneComponent->SetRelativeRotation(FRotator(0, InVectorIndex * 60.f, 0)); SceneComponent->SetRelativeRotation(FRotator(0, InVectorIndex * 60.f, 0));
MeshVariants[VariantIndex]->ToggleVisibility(); MeshVariants[VariantIndex]->ToggleVisibility();
} }