Big rework to A*; factored out path linking

This commit is contained in:
Maximilian Fajnberg 2022-01-18 20:09:37 +01:00
parent 80e990c432
commit 239054df49
4 changed files with 66 additions and 70 deletions

View File

@ -7,7 +7,6 @@
#include "AdventureCharacter.h" #include "AdventureCharacter.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "Algo/Reverse.h" #include "Algo/Reverse.h"
#include <unordered_map>
// Sets default values // Sets default values
AAdventureMap::AAdventureMap() AAdventureMap::AAdventureMap()
@ -32,21 +31,17 @@ void AAdventureMap::MakeGrid()
float HexWidth = sqrt(3) * TileSize; float HexWidth = sqrt(3) * TileSize;
int QOffset = 0; int QOffset = 0;
for (int r = 1; r <= GridSize; r++) for (int r = 1; r <= GridSize; r++) {
{
float XOffset = 0.f; float XOffset = 0.f;
if (r % 2 != 0) if (r % 2 != 0) {
{ if (r > 1) {
if (r > 1)
{
QOffset--; QOffset--;
} }
} }
else { XOffset = HexWidth / 2; } else { XOffset = HexWidth / 2; }
for (int q = 1; q <= GridSize; q++) for (int q = 1; q <= GridSize; q++) {
{
NextHexAt.X = XOffset + (HexWidth * q); NextHexAt.X = XOffset + (HexWidth * q);
NextHexAt.Y = TileSize * 1.5f * r; NextHexAt.Y = TileSize * 1.5f * r;
NextHexAt.Z = 0.f; NextHexAt.Z = 0.f;
@ -61,8 +56,7 @@ void AAdventureMap::MakeGrid()
} }
} }
for (auto& tile : Grid) for (auto& tile : Grid) {
{
tile->Index = GridIndex(tile->Q, tile->R); tile->Index = GridIndex(tile->Q, tile->R);
} }
@ -95,77 +89,69 @@ TArray<AHexTile*> AAdventureMap::Neighbors(AHexTile* OfHex)
TArray<int32> Indeces; TArray<int32> Indeces;
Indeces.Append(Arr, UE_ARRAY_COUNT(Arr)); Indeces.Append(Arr, UE_ARRAY_COUNT(Arr));
Neighbors.Add(RandomHex()); Neighbors.Add(RandomHex()); // pathetic but necessary
for (auto& I : Indeces) { if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); } } for (auto& I : Indeces) { if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); } }
return Neighbors; return Neighbors;
} }
TArray<AHexTile*> AAdventureMap::FindPathAStar(AHexTile* Start, AHexTile* Goal) TArray<AHexTile*> AAdventureMap::AStar(AHexTile* Start, AHexTile* Goal)
{ {
TArray<AHexTile*> PQ; // Makeshift Priority Queue. (High value = Low priority) TArray<AHexTile*> ToExamine;
PQ.Add(Start); TArray<AHexTile*> Processed;
ToExamine.Add(Start);
// The goal of this loop is to edit the Hex->CameFrom pointers, So as to chain Hexes from Goal to Start while (!ToExamine.IsEmpty()) {
while (!PQ.IsEmpty()) AHexTile* Candidate = ToExamine[0];
{
AHexTile* Current = PQ[0];
PQ.RemoveAt(0);
// UE_LOG(LogTemp, Warning, TEXT("Popping top priority Hex %d:%d"), Current->Q, Current->R); // debug
if (Current == Goal) // early exit // estimate closest known Hex to Goal
{ for (auto& t : ToExamine) {
// UE_LOG(LogTemp, Warning, TEXT("Goal found!!!")); // debug int32 FCost = t->GCost + t->HCost;
break; if (t->FCost < Candidate->FCost || t->FCost == Candidate->FCost && t->HCost < Candidate->HCost) { Candidate = t; }
} }
// UE_LOG(LogTemp, Warning, TEXT("Expanding the frontier...")); // debug Processed.Add(Candidate);
for (AHexTile* Next : Neighbors(Current)) ToExamine.Remove(Candidate);
{
int32 NewCost = Current->CostSoFar + Next->MoveCost;
if (NewCost < Next->CostSoFar || !PQ.Contains(Next))
{
Next->CostSoFar = NewCost;
Next->CameFrom = Current;
PQ.Remove(Next);
int32 NewPrio = NewCost + Next->Distance(Goal); // Higher value = Lower priority
// no crash up to this point // exit
if (Candidate == Goal) { break; }
// UE_LOG(LogTemp, Warning, TEXT("Hex %d:%d path info updated. Readjusting priorities..."), Next->Q, Next->R); // debug // expand frontier & adjust path data
if (!PQ.IsEmpty()) for (AHexTile* Neighbor : Neighbors(Candidate)) {
{ if (Processed.Contains(Neighbor)) { continue; }
int32 OldPrio;
int32 OldIndex = PQ.Num() - 1; // default to lowest priority
for (auto& Hex : PQ) bool bInToExamine = ToExamine.Contains(Neighbor);
int32 NewGCost = Candidate->GCost + Neighbor->MoveCost;
if (NewGCost < Neighbor->GCost || !bInToExamine) {
Neighbor->GCost = NewGCost;
Neighbor->CameFrom = Candidate; // chain
if (!bInToExamine) {
Neighbor->HCost = Neighbor->Distance(Goal);
ToExamine.Add(Neighbor);
}
}
}
}
return LinkPath(Start, Goal);
}
TArray<AHexTile*> AAdventureMap::LinkPath(AHexTile* Start, AHexTile* Goal)
{ {
OldPrio = Hex->CostSoFar + Hex->Distance(Goal);
if (NewPrio <= OldPrio) // looking for 1. Hex in "PQ" with a lower priority than that of "Next"
{
PQ.Find(Hex, OldIndex); // redefine OldIndex
break;
}
}
// UE_LOG(LogTemp, Warning, TEXT("Saving at PQ index %d"), OldIndex); // debug
PQ.Insert(Next, OldIndex);
}
else
{
PQ.Add(Next);
// UE_LOG(LogTemp, Warning, TEXT("Adding (PQ was empty)")); // debug
}
}
}
}
TArray<AHexTile*> Path; TArray<AHexTile*> Path;
/* AHexTile* iPathNode = Goal;
AHexTile* Hex = Goal;
while (Hex != Start) while (iPathNode != Start) {
{ Path.Emplace(iPathNode);
Path.Emplace(Hex);
Hex = Hex->CameFrom; if (iPathNode->Distance(iPathNode->CameFrom) > 1) {
bChainBroken = true;
UE_LOG(LogTemp, Warning, TEXT("Chain is broken in LinkPath function..."));
} }
Algo::Reverse(Path);
*/ iPathNode = iPathNode->CameFrom;
}
return Path; return Path;
} }

View File

@ -43,9 +43,13 @@ public:
UFUNCTION(BlueprintCallable, Category = "Runtime") UFUNCTION(BlueprintCallable, Category = "Runtime")
TArray<AHexTile*> Neighbors(AHexTile* OfHex); TArray<AHexTile*> Neighbors(AHexTile* OfHex);
UFUNCTION(BlueprintCallable, Category = "Runtime") UFUNCTION(BlueprintCallable, Category = "Runtime")
TArray<AHexTile*> FindPathAStar(AHexTile* Start, AHexTile* Goal); TArray<AHexTile*> AStar(AHexTile* Start, AHexTile* Goal);
//UFUNCTION(BlueprintCallable, Category = "Runtime") UFUNCTION(BlueprintCallable, Category = "Runtime")
// TArray<AHexTile*> FindPathAStarPQ(AHexTile* Start, AHexTile* Goal); TArray<AHexTile*> LinkPath(AHexTile* Start, AHexTile* Goal);
UPROPERTY(BlueprintReadOnly, Category = "debug")
bool bChainBroken;
// Player spawn section // Player spawn section
UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UPROPERTY(VisibleAnywhere, BlueprintReadWrite)

View File

@ -37,7 +37,7 @@ void AAdventurePlayerController::AdvClick()
if (IsValid(Hit.GetActor())) if (IsValid(Hit.GetActor()))
{ {
AHexTile* HitHex = (AHexTile*)Hit.GetActor(); AHexTile* HitHex = (AHexTile*)Hit.GetActor();
MapRef->FindPathAStar(CurrentHex, HitHex); // MapRef->FindPathAStar(CurrentHex, HitHex);
// UE_LOG(LogTemp, Warning, TEXT("%d"), HitHex->Index); // UE_LOG(LogTemp, Warning, TEXT("%d"), HitHex->Index);
} }
} }

View File

@ -47,10 +47,16 @@ public:
// Pathfinding // Pathfinding
UPROPERTY(BlueprintReadWrite, Category = "Movement") UPROPERTY(BlueprintReadWrite, Category = "Movement")
int32 MoveCost = 1; int32 MoveCost = 1;
UPROPERTY(VisibleInstanceOnly, Category = "Movement") UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Movement")
AHexTile* CameFrom; AHexTile* CameFrom;
UPROPERTY(VisibleInstanceOnly, Category = "Movement") UPROPERTY(VisibleInstanceOnly, Category = "Movement")
int32 CostSoFar = 0; int32 CostSoFar = 0;
UPROPERTY()
int32 FCost;
UPROPERTY()
int32 GCost;
UPROPERTY()
int32 HCost;
FORCEINLINE bool operator == (const AHexTile &Other) FORCEINLINE bool operator == (const AHexTile &Other)
{ {