First draft of A* pathfinding implementation

This commit is contained in:
Maximilian Fajnberg 2022-01-16 18:33:43 +01:00
parent fc10cbfc3e
commit 180207f441
5 changed files with 114 additions and 53 deletions

View File

@ -32,11 +32,4 @@ void AAdventureCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInput
{ {
Super::SetupPlayerInputComponent(PlayerInputComponent); Super::SetupPlayerInputComponent(PlayerInputComponent);
}
void AAdventureCharacter::AStarFindPath(AHexTile* Goal)
{
std::priority_queue<int32> Frontier;
TMap<AHexTile*, AHexTile*> CameFrom;
TMap<AHexTile*, int32> CostSoFar;
} }

View File

@ -20,14 +20,10 @@ public:
// Sets default values for this character's properties // Sets default values for this character's properties
AAdventureCharacter(); AAdventureCharacter();
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Category = "Runtime")
AHexTile* GridLocation;
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Category = "Runtime")
AHexTile* SelectedHex;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Runtime") UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Runtime")
TArray<AHexTile*> MovementPath; AHexTile* GridLocation;
UFUNCTION(BlueprintCallable) UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Runtime")
void AStarFindPath(AHexTile* Goal); AHexTile* SelectedHex;
protected: protected:
// Called when the game starts or when spawned // Called when the game starts or when spawned

View File

@ -6,7 +6,7 @@
#include "AdventureCameraPawn.h" #include "AdventureCameraPawn.h"
#include "AdventureCharacter.h" #include "AdventureCharacter.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "Algo/Reverse.h"
// Sets default values // Sets default values
AAdventureMap::AAdventureMap() AAdventureMap::AAdventureMap()
@ -36,17 +36,14 @@ void AAdventureMap::MakeGrid()
{ {
float XOffset = 0.f; float XOffset = 0.f;
if (r % 2 != 0) if (r % 2 != 0)
{
if (r > 1)
{
QOffset--; // The Q axis is (i.e. columns are) oriented diagonally.
}
}
else
{ {
XOffset = HexWidth / 2; if (r > 1)
{
QOffset--;
}
} }
else { XOffset = HexWidth / 2; }
for (int q = 1; q <= GridSize; q++) for (int q = 1; q <= GridSize; q++)
{ {
@ -86,35 +83,91 @@ int32 AAdventureMap::GridIndex(int32 qAxial, int32 rAxial)
AHexTile* AAdventureMap::RandomHex() AHexTile* AAdventureMap::RandomHex()
{ {
int32 RandHex = GridIndex(FMath::RandRange(0, GridSize-1), FMath::RandRange(0, GridSize-1)); 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]; return Grid[RandHex];
} }
TArray<AHexTile*> AAdventureMap::Neighbors(AHexTile* OfHex) TArray<AHexTile*> AAdventureMap::Neighbors(AHexTile* OfHex)
{ {
TArray<AHexTile*> Neighbors; TArray<AHexTile*> Neighbors;
int32 Index; int32 I;
Index = GridIndex(OfHex->Q + 1 , OfHex->R + 0 ); I = GridIndex(OfHex->Q + 1 , OfHex->R + 0 );
if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); }
Index = GridIndex(OfHex->Q + 1 , OfHex->R - 1 ); I = GridIndex(OfHex->Q + 1 , OfHex->R - 1 );
if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); }
Index = GridIndex(OfHex->Q + 0 , OfHex->R - 1 ); I = GridIndex(OfHex->Q + 0 , OfHex->R - 1 );
if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); }
Index = GridIndex(OfHex->Q - 1 , OfHex->R + 0 ); I = GridIndex(OfHex->Q - 1 , OfHex->R + 0 );
if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); }
Index = GridIndex(OfHex->Q - 1 , OfHex->R + 1 ); I = GridIndex(OfHex->Q - 1 , OfHex->R + 1 );
if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); }
Index = GridIndex(OfHex->Q + 0 , OfHex->R + 1 ); I = GridIndex(OfHex->Q + 0 , OfHex->R + 1 );
if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } if (Grid.IsValidIndex(I) && OfHex->Distance(Grid[I]) == 1) { Neighbors.Add(Grid[I]); }
return Neighbors; return Neighbors;
} }
// Be aware that the respective character will become relevant to this function at some point
TArray<AHexTile*> AAdventureMap::FindPathAStar(AHexTile* Start, AHexTile* Goal)
{
TArray<AHexTile*> 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<AHexTile*> Path;
AHexTile* Hex = Goal;
while (*Hex != *Start)
{
Path.Emplace(Hex);
Hex = Hex->CameFrom;
}
Algo::Reverse(Path);
return Path; // currently always length of 1
}

View File

@ -40,6 +40,8 @@ public:
AHexTile* RandomHex(); AHexTile* RandomHex();
UFUNCTION(BlueprintCallable, Category = "Runtime") UFUNCTION(BlueprintCallable, Category = "Runtime")
TArray<AHexTile*> Neighbors(AHexTile* OfHex); TArray<AHexTile*> Neighbors(AHexTile* OfHex);
UFUNCTION(BlueprintCallable, Category = "Runtime")
TArray<AHexTile*> FindPathAStar(AHexTile* Start, AHexTile* Goal);
// Player spawn section // Player spawn section
UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
@ -50,4 +52,4 @@ public:
protected: 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;
}; };

View File

@ -23,31 +23,48 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config")
float TileSize; float TileSize;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Config")
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
USceneComponent* SceneComponent; USceneComponent* SceneComponent;
UPROPERTY(BlueprintReadWrite, Category = "Config")
AAdventureMap* MapRef;
UFUNCTION(BlueprintCallable, Category = "debug") UFUNCTION(BlueprintCallable, Category = "debug")
FVector Corner(int32 i); FVector Corner(int32 i);
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "debug")
TArray<FVector> Corners;
UFUNCTION() UFUNCTION()
void FillCornersArray(); void FillCornersArray();
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "debug")
TArray<FVector> Corners;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Runtime") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Coordinates")
int32 Q; int32 Q;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Runtime") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Coordinates")
int32 R; int32 R;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Runtime") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Coordinates")
int32 Index; int32 Index;
UPROPERTY(BlueprintReadWrite, Category = "Runtime") UFUNCTION(BlueprintCallable, Category = "Coordinates")
AAdventureMap* MapRef;
UFUNCTION(BlueprintCallable, Category = "Runtime")
int32 Distance(AHexTile* ToHex); 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: 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;
}; };