From 180207f4415033ceaa6a78748d86e65a5c715185 Mon Sep 17 00:00:00 2001 From: Maximilian Fajnberg Date: Sun, 16 Jan 2022 18:33:43 +0100 Subject: [PATCH] First draft of A* pathfinding implementation --- AdventureCharacter.cpp | 7 --- AdventureCharacter.h | 10 ++-- AdventureMap.cpp | 107 ++++++++++++++++++++++++++++++----------- AdventureMap.h | 4 +- HexTile.h | 39 ++++++++++----- 5 files changed, 114 insertions(+), 53 deletions(-) diff --git a/AdventureCharacter.cpp b/AdventureCharacter.cpp index 10b1877..28192a0 100644 --- a/AdventureCharacter.cpp +++ b/AdventureCharacter.cpp @@ -32,11 +32,4 @@ void AAdventureCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInput { Super::SetupPlayerInputComponent(PlayerInputComponent); -} - -void AAdventureCharacter::AStarFindPath(AHexTile* Goal) -{ - std::priority_queue Frontier; - TMap CameFrom; - TMap CostSoFar; } \ No newline at end of file diff --git a/AdventureCharacter.h b/AdventureCharacter.h index 63c5d33..7ffaf50 100644 --- a/AdventureCharacter.h +++ b/AdventureCharacter.h @@ -20,14 +20,10 @@ public: // Sets default values for this character's properties AAdventureCharacter(); - UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Category = "Runtime") - AHexTile* GridLocation; - UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Category = "Runtime") - AHexTile* SelectedHex; UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Runtime") - TArray MovementPath; - UFUNCTION(BlueprintCallable) - void AStarFindPath(AHexTile* Goal); + AHexTile* GridLocation; + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Runtime") + AHexTile* SelectedHex; protected: // Called when the game starts or when spawned diff --git a/AdventureMap.cpp b/AdventureMap.cpp index 379fb3b..5fbaf18 100644 --- a/AdventureMap.cpp +++ b/AdventureMap.cpp @@ -6,7 +6,7 @@ #include "AdventureCameraPawn.h" #include "AdventureCharacter.h" #include "Kismet/GameplayStatics.h" - +#include "Algo/Reverse.h" // Sets default values AAdventureMap::AAdventureMap() @@ -36,17 +36,14 @@ void AAdventureMap::MakeGrid() { float XOffset = 0.f; - if (r % 2 != 0) - { - if (r > 1) - { - QOffset--; // The Q axis is (i.e. columns are) oriented diagonally. - } - } - else + if (r % 2 != 0) { - XOffset = HexWidth / 2; + if (r > 1) + { + QOffset--; + } } + else { XOffset = HexWidth / 2; } for (int q = 1; q <= GridSize; q++) { @@ -86,35 +83,91 @@ int32 AAdventureMap::GridIndex(int32 qAxial, int32 rAxial) AHexTile* AAdventureMap::RandomHex() { int32 RandHex = GridIndex(FMath::RandRange(0, GridSize-1), FMath::RandRange(0, GridSize-1)); - //while (RandHex > Grid.Num()) - //{ - // RandHex = GridIndex(FMath::RandRange(0, GridSize), FMath::RandRange(0, GridSize)); - //} return Grid[RandHex]; } TArray AAdventureMap::Neighbors(AHexTile* OfHex) { TArray Neighbors; - int32 Index; + int32 I; - Index = GridIndex(OfHex->Q + 1 , OfHex->R + 0 ); - if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } + I = GridIndex(OfHex->Q + 1 , OfHex->R + 0 ); + if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); } - Index = GridIndex(OfHex->Q + 1 , OfHex->R - 1 ); - if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } + I = GridIndex(OfHex->Q + 1 , OfHex->R - 1 ); + if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); } - Index = GridIndex(OfHex->Q + 0 , OfHex->R - 1 ); - if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } + I = GridIndex(OfHex->Q + 0 , OfHex->R - 1 ); + if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); } - Index = GridIndex(OfHex->Q - 1 , OfHex->R + 0 ); - if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } + I = GridIndex(OfHex->Q - 1 , OfHex->R + 0 ); + if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); } - Index = GridIndex(OfHex->Q - 1 , OfHex->R + 1 ); - if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } + I = GridIndex(OfHex->Q - 1 , OfHex->R + 1 ); + if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); } - Index = GridIndex(OfHex->Q + 0 , OfHex->R + 1 ); - if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } + I = GridIndex(OfHex->Q + 0 , OfHex->R + 1 ); + if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); } return Neighbors; } + +// Be aware that the respective character will become relevant to this function at some point +TArray AAdventureMap::FindPathAStar(AHexTile* Start, AHexTile* Goal) +{ + TArray Priorities; + Priorities.Init(Start, 1); + + Goal->CameFrom = Start; + + // Editing Hex->CameFrom pointers, i.e. chaining Hexes + while (Priorities.IsValidIndex(0)) + { + AHexTile* Current = Priorities[0]; + Priorities.RemoveAt(0); + if (*Current == *Goal) { break; } + + // Expanding the Frontier + for (AHexTile* Next : Neighbors(Current)) + { + int32 NewCost = Current->CostSoFar + Next->MoveCost; + if (!Priorities.Contains(Next) || NewCost < Next->CostSoFar) + { + Next->CostSoFar = NewCost; + int32 NewPrio = NewCost + Next->Distance(Goal); + + // Adjust the Priority Queue + if (Priorities.Contains(Next)) { Priorities.Remove(Next); } + for (AHexTile* Hex : Priorities) + { + int32 OldPrio = Hex->CostSoFar + Hex->Distance(Goal); + int32 Index; + Priorities.Find(Hex, Index); + + if (OldPrio > NewPrio) + { + Priorities.Insert(Next, Index); + Next->CameFrom = Current; + break; + } + if (Index == Priorities.Num() - 1 && OldPrio <= NewPrio) + { + Priorities.Emplace(Next); + Next->CameFrom = Current; + } + } + } + } + } + + TArray Path; + AHexTile* Hex = Goal; + while (*Hex != *Start) + { + Path.Emplace(Hex); + Hex = Hex->CameFrom; + } + + Algo::Reverse(Path); + return Path; // currently always length of 1 +} diff --git a/AdventureMap.h b/AdventureMap.h index 9945156..f0f850f 100644 --- a/AdventureMap.h +++ b/AdventureMap.h @@ -40,6 +40,8 @@ public: AHexTile* RandomHex(); UFUNCTION(BlueprintCallable, Category = "Runtime") TArray Neighbors(AHexTile* OfHex); + UFUNCTION(BlueprintCallable, Category = "Runtime") + TArray FindPathAStar(AHexTile* Start, AHexTile* Goal); // Player spawn section UPROPERTY(VisibleAnywhere, BlueprintReadWrite) @@ -50,4 +52,4 @@ public: protected: // Called when the game starts or when spawned virtual void BeginPlay() override; -}; \ No newline at end of file +}; diff --git a/HexTile.h b/HexTile.h index 2a9ddff..bfe6ed1 100644 --- a/HexTile.h +++ b/HexTile.h @@ -23,31 +23,48 @@ public: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config") float TileSize; - - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Config") USceneComponent* SceneComponent; + UPROPERTY(BlueprintReadWrite, Category = "Config") + AAdventureMap* MapRef; UFUNCTION(BlueprintCallable, Category = "debug") FVector Corner(int32 i); - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "debug") - TArray Corners; UFUNCTION() void FillCornersArray(); + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "debug") + TArray Corners; - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Runtime") + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Coordinates") int32 Q; - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Runtime") + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Coordinates") int32 R; - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Runtime") + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Coordinates") int32 Index; - UPROPERTY(BlueprintReadWrite, Category = "Runtime") - AAdventureMap* MapRef; - UFUNCTION(BlueprintCallable, Category = "Runtime") + UFUNCTION(BlueprintCallable, Category = "Coordinates") int32 Distance(AHexTile* ToHex); + // Pathfinding + UPROPERTY(BlueprintReadWrite, Category = "Movement") + int32 MoveCost = 1; + UPROPERTY(VisibleInstanceOnly, Category = "Movement") + AHexTile* CameFrom; + UPROPERTY(VisibleInstanceOnly, Category = "Movement") + int32 CostSoFar = 0; + + FORCEINLINE bool operator == (const AHexTile &Other) + { + if (this->Q == Other.Q && this->R == Other.R) { return true; } + else { return false; } + } + FORCEINLINE bool operator != (const AHexTile &Other) + { + if (this->Q == Other.Q && this->R == Other.R) { return false; } + else { return true; } + } + protected: // Called when the game starts or when spawned virtual void BeginPlay() override; - };