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

View File

@ -106,3 +106,63 @@ protected:
// Called when the game starts or when spawned
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;
TSet<AHexTile*> Visible;
if (CurrentHex->bDiagMove) { Radius = FMath::FloorToInt(float(Radius) * (2.f / 3.f)); }
Visible = MapRef->BreadthFirstSearch(CurrentHex, Radius);
for (auto& Hex : Visible) {
if (ExploredHexes.Contains(Hex)) { continue; }
@ -71,6 +70,10 @@ TArray<AHexTile*> AAdventurePlayerController::Vision(int32 Radius)
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 DirB;
for (int32 i = 0; i < Path.Num() - 1; i++)

View File

@ -48,19 +48,15 @@ public:
// Pathfinding
UPROPERTY(BlueprintReadWrite)
float MoveCost = 10;
int32 MoveCost = 1;
UPROPERTY()
int32 FCost;
UPROPERTY()
int32 GCost = 9999;
UPROPERTY()
int32 HCost;
UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Runtime")
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
UPROPERTY(BlueprintReadWrite, VisibleAnywhere)

View File

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