From 6c1c0579cbc18a17ab5ccdada446a40e4f3a1fc5 Mon Sep 17 00:00:00 2001 From: Maximilian Fajnberg Date: Mon, 17 Jan 2022 17:53:54 +0100 Subject: [PATCH] pathfinding now working for distance 2 or less --- AdventureMap.cpp | 131 +++++++++++++++++++++++++--------- AdventureMap.h | 4 ++ AdventurePlayerController.cpp | 6 +- 3 files changed, 106 insertions(+), 35 deletions(-) diff --git a/AdventureMap.cpp b/AdventureMap.cpp index b2a1488..f0732dc 100644 --- a/AdventureMap.cpp +++ b/AdventureMap.cpp @@ -7,6 +7,7 @@ #include "AdventureCharacter.h" #include "Kismet/GameplayStatics.h" #include "Algo/Reverse.h" +#include // Sets default values AAdventureMap::AAdventureMap() @@ -17,7 +18,7 @@ AAdventureMap::AAdventureMap() void AAdventureMap::BeginPlay() { Super::BeginPlay(); - UWorld* World = GetWorld(); + World = GetWorld(); if (IsValid(BaseTileClass)) { MakeGrid(); @@ -27,7 +28,6 @@ void AAdventureMap::BeginPlay() // Called once on Begin Play void AAdventureMap::MakeGrid() { - UWorld* World = GetWorld(); FVector NextHexAt = FVector(); float HexWidth = sqrt(3) * TileSize; int QOffset = 0; @@ -112,77 +112,143 @@ TArray AAdventureMap::Neighbors(AHexTile* OfHex) return Neighbors; } -// Be aware that the respective character will become relevant to this function at some point +// Massive memory leak when Goal is more than 2 distance away from Start (leading to the Editor freezing) +// Also freezes when the Goal is on the Edge of the map (i.e. has only 3 Neighbors) TArray AAdventureMap::FindPathAStar(AHexTile* Start, AHexTile* Goal) { - TArray Priorities; - Priorities.Init(Start, 1); + TArray PQ; // Makeshift Priority Queue. (High value = Low priority) + PQ.Init(Start, 1); - // Editing Hex->CameFrom pointers, i.e. chaining Hexes - while (!Priorities.IsEmpty()) - { - AHexTile* Current = Priorities[0]; - Priorities.RemoveAt(0); - if (*Current == *Goal) + // The goal of this loop is to edit the Hex->CameFrom pointers, So as to chain Hexes from Goal to Start + while (!PQ.IsEmpty()) + { + AHexTile* Current = PQ[0]; + PQ.RemoveAt(0); + UE_LOG(LogTemp, Warning, TEXT("Popping top priority Hex Q:%d|R:%d"), Current->Q, Current->R); + + if (Current == Goal) // early exit { - // UE_LOG(LogTemp, Warning, TEXT("Goal found!")); // debug + UE_LOG(LogTemp, Warning, TEXT("Goal found!!!")); // debug break; } + UE_LOG(LogTemp, Warning, TEXT("Expanding the frontier...")); // debug + for (AHexTile* Next : Neighbors(Current)) + { + 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 + + UE_LOG(LogTemp, Warning, TEXT("Hex Q:%d|R:%d pathing updated. Readjusting priorities..."), Next->Q, Next->R); // debug + if (!PQ.IsEmpty()) + { + int32 OldPrio; + int32 OldIndex = PQ.Num() - 1; // To be inserted as lowest priority by default + + for (AHexTile* Hex : PQ) + { + 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); // redefining of "OldIndex" + break; + } + } + UE_LOG(LogTemp, Warning, TEXT("Inserting Hex at priority %d"), OldIndex); // debug + PQ.Insert(Next, OldIndex); + } + else + { + PQ.Add(Next); + UE_LOG(LogTemp, Warning, TEXT("Adding one last Candidate.")); // debug + } + } + } + } + TArray Path; + AHexTile* Hex = Goal; + while (Hex != Start) + { + Path.Emplace(Hex); + Hex = Hex->CameFrom; + } + Algo::Reverse(Path); + return Path; +} + +/* +// alternative implementation using faster built in priority queue data structure +TArray AAdventureMap::FindPathAStarPQ(AHexTile* Start, AHexTile* Goal) +{ + // TArray PQ; + std::priority_queue> PQ; // currently missing a custom comparator as third template arg (designating effective priority of pq elements) + std::unordered_map CameFrom; // if in doubt: switch to templates instead of predefined hex types + // PQ.Init(Start, 1); + PQ.push(Start); + CameFrom[Start] = Start; + + // while (!PQ.IsEmpty()) + while (!PQ.empty()) + { + AHexTile* Current = PQ.top(); + // PQ.RemoveAt(0); + PQ.pop(); + if (Current == Goal) + { + UE_LOG(LogTemp, Warning, TEXT("Goal found!")); // debug + break; + } + // Expanding the Frontier for (AHexTile* Next : Neighbors(Current)) { - int32 NewCost = Current->CostSoFar + Next->MoveCost; - + int32 NewCost = Current->CostSoFar + Next->MoveCost; // UE_LOG(LogTemp, Warning, TEXT("Cost calculated.")); // debug - if (!Priorities.Contains(Next) || NewCost < Next->CostSoFar) + if (!PQ.Contains(Next) || NewCost < Next->CostSoFar) { // UE_LOG(LogTemp, Warning, TEXT("New candidate found.")); // debug - Next->CostSoFar = NewCost; int32 NewPrio = NewCost + Next->Distance(Goal); // Adjust the Priority Queue - if (Priorities.Contains(Next)) { Priorities.Remove(Next); } - for (AHexTile* Hex : Priorities) // at this point Priorities is empty, need to make sure it's not. + if (PQ.Contains(Next)) { PQ.Remove(Next); } + for (AHexTile* Hex : PQ) // at this point PQ is empty, need to make sure it's not. { int32 OldPrio = Hex->CostSoFar + Hex->Distance(Goal); int32 Index; - Priorities.Find(Hex, Index); - + PQ.Find(Hex, Index); // UE_LOG(LogTemp, Warning, TEXT("Comparing priorities...")); // debug if (OldPrio > NewPrio) { - Priorities.Insert(Next, Index); + PQ.Insert(Next, Index); Next->CameFrom = Current; - // UE_LOG(LogTemp, Warning, TEXT("Looks promising!")); // debug - break; } - if (Index == Priorities.Num() - 1 && OldPrio <= NewPrio) + + if (Index == PQ.Num() - 1 && OldPrio <= NewPrio) { - Priorities.Emplace(Next); + PQ.Emplace(Next); Next->CameFrom = Current; - // UE_LOG(LogTemp, Warning, TEXT("Low prio added")); // debug - break; } } - if (Priorities.IsEmpty()) - { - Priorities.Emplace(Next); - } + + if (PQ.IsEmpty()) { PQ.Emplace(Next); } } } } TArray Path; AHexTile* Hex = Goal; - while (*Hex != *Start) + while (Hex != Start) { Path.Emplace(Hex); Hex = Hex->CameFrom; @@ -191,3 +257,4 @@ TArray AAdventureMap::FindPathAStar(AHexTile* Start, AHexTile* Goal) Algo::Reverse(Path); return Path; } +*/ \ No newline at end of file diff --git a/AdventureMap.h b/AdventureMap.h index f0f850f..ed320d4 100644 --- a/AdventureMap.h +++ b/AdventureMap.h @@ -26,6 +26,8 @@ public: int32 GridSize = 100; // squared is the number of Tiles UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config") int32 TileSize = 100; + UPROPERTY() + UWorld* World; UFUNCTION(BlueprintCallable, Category = "Generation") void MakeGrid(); @@ -42,6 +44,8 @@ public: TArray Neighbors(AHexTile* OfHex); UFUNCTION(BlueprintCallable, Category = "Runtime") TArray FindPathAStar(AHexTile* Start, AHexTile* Goal); + //UFUNCTION(BlueprintCallable, Category = "Runtime") + // TArray FindPathAStarPQ(AHexTile* Start, AHexTile* Goal); // Player spawn section UPROPERTY(VisibleAnywhere, BlueprintReadWrite) diff --git a/AdventurePlayerController.cpp b/AdventurePlayerController.cpp index 34fc26e..4050824 100644 --- a/AdventurePlayerController.cpp +++ b/AdventurePlayerController.cpp @@ -26,7 +26,7 @@ void AAdventurePlayerController::SetupInputComponent() Super::SetupInputComponent(); // This is initialized on startup, you can go straight to binding - InputComponent->BindAction("LeftClick", IE_Pressed, this, &AAdventurePlayerController::AdvClick); + // InputComponent->BindAction("LeftClick", IE_Pressed, this, &AAdventurePlayerController::AdvClick); } void AAdventurePlayerController::AdvClick() @@ -37,7 +37,7 @@ void AAdventurePlayerController::AdvClick() if (IsValid(Hit.GetActor())) { AHexTile* HitHex = (AHexTile*)Hit.GetActor(); - // MapRef->FindPathAStar(CurrentHex, HitHex); // this would currently cause a crash... - UE_LOG(LogTemp, Warning, TEXT("%d"), HitHex->Index); + MapRef->FindPathAStar(CurrentHex, HitHex); + // UE_LOG(LogTemp, Warning, TEXT("%d"), HitHex->Index); } } \ No newline at end of file