Compare commits

...

21 Commits

Author SHA1 Message Date
276f8e2b50 Fixed vision blocking; MapObject::Occupy() now blueprint implementable 2022-06-20 15:45:30 +02:00
068beeacfd Drafted Vision blocking functionality and respective MapRef->HelperFunctions 2022-06-18 01:20:01 +02:00
18af6a56be Added reference to harbored map object to tiles; minor refactoring 2022-06-10 16:15:12 +02:00
26d3289962 Fixed freezes on controller calling MarkPath() to invalid locations 2022-06-08 11:39:41 +02:00
28a226e35d Don't write clever code 2022-06-05 22:44:34 +02:00
aa02fb8093 MovementArrow functionality; StarFog implemented; Refactoring 2022-06-01 20:25:33 +02:00
21f7dd4e67 port to UE5 and new development environment; consequential leeway 2022-05-25 20:07:51 +02:00
1485621698 prepared Shortcut rework for A* (working segment data) 2022-02-05 14:26:46 +01:00
1573598c2a new algorithm draft for diag moves; committed missing files 2022-02-03 01:11:06 +01:00
2bf072e3c6 implemented diagonal movement 2022-01-28 21:58:04 +01:00
70ef60f08e storing 12 cardinal dir vectors; Diagonals function 2022-01-26 18:09:39 +01:00
b7b9963a68 Camera now following Pawn 2022-01-26 16:48:58 +01:00
986233d960 Breadth First Search; considering use of diagonals for A*PF 2022-01-25 18:30:49 +01:00
237c056b30 Base functionality for MapObject placement 2022-01-24 21:26:06 +01:00
74eab48a6e propagated the temporary fix for Neighbors up to A* 2022-01-18 20:19:30 +01:00
239054df49 Big rework to A*; factored out path linking 2022-01-18 20:09:37 +01:00
80e990c432 A* algorithm now working as intended 2022-01-18 17:01:31 +01:00
5c1535da32 removed alternative implemenation of A* for now 2022-01-17 17:58:30 +01:00
6c1c0579cb pathfinding now working for distance 2 or less 2022-01-17 17:53:54 +01:00
32f4c6a278 small changes to pathfinding; plan to fix bad performance 2022-01-16 21:19:34 +01:00
180207f441 First draft of A* pathfinding implementation 2022-01-16 18:33:43 +01:00
19 changed files with 1137 additions and 247 deletions

View File

@ -18,9 +18,10 @@ AAdventureCameraPawn::AAdventureCameraPawn()
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it. // Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bCanEverTick = true;
BaseScrollSpeed = 20;
ScrollAccelleration = BaseScrollSpeed * 3;
ESASize = .01; ESASize = .01;
BaseScrollSpeed = 16;
ScrollAccelleration = BaseScrollSpeed * 2.4;
ESAMaxBoundSlope = 1 / (1 - (1-ESASize)); ESAMaxBoundSlope = 1 / (1 - (1-ESASize));
ESAMaxBoundIntercept = 1 - ESAMaxBoundSlope; ESAMaxBoundIntercept = 1 - ESAMaxBoundSlope;
@ -28,15 +29,16 @@ AAdventureCameraPawn::AAdventureCameraPawn()
ESAMinBoundSlope = -1 / ESASize; ESAMinBoundSlope = -1 / ESASize;
ESAMinBoundIntercept = 0 - (ESAMinBoundSlope * ESASize); ESAMinBoundIntercept = 0 - (ESAMinBoundSlope * ESASize);
AutoPossessPlayer = EAutoReceiveInput::Player0;
} }
// Called when the game starts or when spawned // Called when the game starts or when spawned
void AAdventureCameraPawn::BeginPlay() void AAdventureCameraPawn::BeginPlay()
{ {
Super::BeginPlay(); Super::BeginPlay();
ControllerRef = (AAdventurePlayerController*)UGameplayStatics::GetPlayerController(GetWorld(), 0); OwningPC = (AAdventurePlayerController*)UGameplayStatics::GetPlayerController(GetWorld(), 0); // for now
// The Viewport properties are inaccurate right after BeginPlay, so we need to wait a short time before saving them here. // Viewport properties not accurate on BeginPlay (must wait before accessing)
FTimerHandle UnusedHandle; FTimerHandle UnusedHandle;
GetWorldTimerManager().SetTimer( GetWorldTimerManager().SetTimer(
UnusedHandle, this, &AAdventureCameraPawn::GetTheDamnViewport, 1, false); UnusedHandle, this, &AAdventureCameraPawn::GetTheDamnViewport, 1, false);
@ -52,7 +54,11 @@ void AAdventureCameraPawn::GetTheDamnViewport()
void AAdventureCameraPawn::Tick(float DeltaTime) void AAdventureCameraPawn::Tick(float DeltaTime)
{ {
Super::Tick(DeltaTime); Super::Tick(DeltaTime);
if (IsValid(FollowPawn)) {
if (FollowPawn->bIsMoving) {
FollowAdvPawn(DeltaTime);
}
}
} }
// Called to bind functionality to input // Called to bind functionality to input
@ -63,54 +69,68 @@ void AAdventureCameraPawn::SetupPlayerInputComponent(UInputComponent* PlayerInpu
{ {
PlayerInputComponent->BindAxis("Mouse X", this, &AAdventureCameraPawn::EdgeScrollSide); PlayerInputComponent->BindAxis("Mouse X", this, &AAdventureCameraPawn::EdgeScrollSide);
PlayerInputComponent->BindAxis("Mouse Y", this, &AAdventureCameraPawn::EdgeScrollVert); PlayerInputComponent->BindAxis("Mouse Y", this, &AAdventureCameraPawn::EdgeScrollVert);
PlayerInputComponent->BindAxis("Move AD", this, &AAdventureCameraPawn::ScrollSide);
PlayerInputComponent->BindAxis("Move WS", this, &AAdventureCameraPawn::ScrollVert);
} }
} }
void AAdventureCameraPawn::ScrollSide(float AxisValue)
{
float DeltaLoc = AxisValue * BaseScrollSpeed * 1.7;
AddActorLocalOffset(FVector(DeltaLoc, 0, 0));
}
void AAdventureCameraPawn::ScrollVert(float AxisValue)
{
float DeltaLoc = AxisValue * BaseScrollSpeed * -1.7;
AddActorLocalOffset(FVector(0, DeltaLoc, 0));
}
void AAdventureCameraPawn::EdgeScrollSide(float AxisValue) void AAdventureCameraPawn::EdgeScrollSide(float AxisValue)
{ {
float mouseX; float mouseX;
float mouseY; float mouseY;
ControllerRef->GetMousePosition(mouseX, mouseY); OwningPC->GetMousePosition(mouseX, mouseY);
float mousePosX = mouseX / ViewSize.X; float mousePosX = mouseX / ViewSize.X;
float mousePosY = mouseY / ViewSize.Y; float mousePosY = mouseY / ViewSize.Y;
float VertAccelleration = GetInputAxisValue("Mouse Y") * (ScrollAccelleration * .75); float VertAccelleration = GetInputAxisValue("Mouse Y") * (ScrollAccelleration * .75);
if (mousePosX <= ESASize) // if in LEFT area if (mousePosX <= ESASize) // in LEFT area
{ {
////// Scroll LEFT with Vert Accelleration ////// Scroll LEFT
float SpeedX = (mousePosX * ESAMinBoundSlope + ESAMinBoundIntercept) * BaseScrollSpeed; float SpeedX = (mousePosX * ESAMinBoundSlope + ESAMinBoundIntercept) * BaseScrollSpeed;
AddActorLocalOffset(FVector(0, -SpeedX, 0)); AddActorLocalOffset(FVector(-SpeedX, 0, 0));
if (mousePosY > .005) // if not touching EDGE if (mousePosY > .005) // not touching TOP EDGE
{ {
AddActorLocalOffset(FVector(VertAccelleration, 0, 0)); AddActorLocalOffset(FVector(0, -VertAccelleration, 0));
} }
if (AxisValue < .0 && mousePosX <= .005) // if mouse moving LEFT and touching EDGE if (AxisValue < .0 && mousePosX <= .005) // mouse moving LEFT && touching EDGE
{ {
////// Add LEFT accelleration ////// Add LEFT accelleration
SpeedX = AxisValue * ScrollAccelleration; SpeedX = AxisValue * ScrollAccelleration;
AddActorLocalOffset(FVector(0, SpeedX, 0)); AddActorLocalOffset(FVector(SpeedX, 0, 0));
} }
} }
if (mousePosX >= 1-ESASize) // if in RIGHT area if (mousePosX >= 1-ESASize) // in RIGHT area
{ {
////// Scroll RIGHT with Vert Accelleration ////// Scroll RIGHT
float SpeedX = (mousePosX * ESAMaxBoundSlope + ESAMaxBoundIntercept) * BaseScrollSpeed; float SpeedX = (mousePosX * ESAMaxBoundSlope + ESAMaxBoundIntercept) * BaseScrollSpeed;
AddActorLocalOffset(FVector(0, SpeedX, 0)); AddActorLocalOffset(FVector(SpeedX, 0, 0));
if (mousePosY < .995) // if not touching EDGE if (mousePosY < .995) // not touching BOT EDGE
{ {
AddActorLocalOffset(FVector(VertAccelleration, 0, 0)); AddActorLocalOffset(FVector(0, -VertAccelleration, 0));
} }
if (AxisValue > .0 && mousePosX >= .995) // if mouse moving RIGHT and touching EDGE if (AxisValue > .0 && mousePosX >= .995) // mouse moving RIGHT && touching EDGE
{ {
////// Add RIGHT accelleration ////// Add RIGHT accelleration
SpeedX = AxisValue * ScrollAccelleration; SpeedX = AxisValue * ScrollAccelleration;
AddActorLocalOffset(FVector(0, SpeedX, 0)); AddActorLocalOffset(FVector(SpeedX, 0, 0));
} }
} }
} }
@ -119,61 +139,53 @@ void AAdventureCameraPawn::EdgeScrollVert(float AxisValue)
{ {
float mouseX; float mouseX;
float mouseY; float mouseY;
ControllerRef->GetMousePosition(mouseX, mouseY); OwningPC->GetMousePosition(mouseX, mouseY);
float mousePosY = mouseY / ViewSize.Y; float mousePosY = mouseY / ViewSize.Y;
float mousePosX = mouseX / ViewSize.X; float mousePosX = mouseX / ViewSize.X;
float SideAccelleration = GetInputAxisValue("Mouse X") * (ScrollAccelleration * .75); float SideAccelleration = GetInputAxisValue("Mouse X") * (ScrollAccelleration * .75);
if (mousePosY <= ESASize) // if in TOP area if (mousePosY <= ESASize) // in TOP area
{ {
////// Scroll TOP with Side Accelleration ////// Scroll TOP
float SpeedY = (mousePosY * ESAMinBoundSlope + ESAMinBoundIntercept) * BaseScrollSpeed; float SpeedY = (mousePosY * ESAMinBoundSlope + ESAMinBoundIntercept) * BaseScrollSpeed;
AddActorLocalOffset(FVector(SpeedY, SideAccelleration, 0)); AddActorLocalOffset(FVector(0, -SpeedY, 0));
if (mousePosX > .005) // if not touching EDGE if (mousePosX > .005) // not touching EDGE
{ {
AddActorLocalOffset(FVector(0, SideAccelleration, 0)); AddActorLocalOffset(FVector(SideAccelleration, 0, 0));
} }
if (AxisValue > .0 && mousePosY <= .005) // if mouse moving TOP and touching EDGE if (AxisValue > .0 && mousePosY <= .005) // mouse moving TOP && touching EDGE
{ {
////// Add TOP accelleration ////// Add TOP accelleration
SpeedY = AxisValue * ScrollAccelleration; SpeedY = AxisValue * ScrollAccelleration;
AddActorLocalOffset(FVector(SpeedY, 0, 0)); AddActorLocalOffset(FVector(0, -SpeedY, 0));
} }
} }
if (mousePosY >= 1-ESASize) // if in BOTTOM area if (mousePosY >= 1-ESASize) // in BOTTOM area
{ {
////// Scroll BOTTOM with Side Accelleration ////// Scroll BOTTOM
float SpeedY = (mousePosY * ESAMaxBoundSlope + ESAMaxBoundIntercept) * BaseScrollSpeed; float SpeedY = (mousePosY * ESAMaxBoundSlope + ESAMaxBoundIntercept) * BaseScrollSpeed;
AddActorLocalOffset(FVector(-SpeedY, SideAccelleration, 0)); AddActorLocalOffset(FVector(0, SpeedY, 0));
if (mousePosX < .995) // if not touching EDGE if (mousePosX < .995) // not touching BOT EDGE
{ {
AddActorLocalOffset(FVector(0, SideAccelleration, 0)); AddActorLocalOffset(FVector(SideAccelleration, 0, 0));
} }
if (AxisValue < .0 && mousePosY >= .995) // if mouse moving BOTTOM and touching EDGE if (AxisValue < .0 && mousePosY >= .995) // mouse moving BOTTOM && touching EDGE
{ {
////// add BOTTOM accelleration ////// add BOTTOM accelleration
SpeedY = AxisValue * ScrollAccelleration; SpeedY = AxisValue * ScrollAccelleration;
AddActorLocalOffset(FVector(SpeedY, 0, 0)); AddActorLocalOffset(FVector(0, -SpeedY, 0));
} }
} }
} }
void AAdventureCameraPawn::FollowAdvPawn() void AAdventureCameraPawn::FollowAdvPawn(float DeltaSeconds)
{ {
UE_LOG(LogTemp, Warning, TEXT("following...")); bool MapPawnInViewX = (FMath::Abs(FollowPawn->GetActorTransform().GetLocation().X - this->GetActorLocation().X)) < ViewSize.X;
bool MapPawnInViewY = (FMath::Abs(FollowPawn->GetActorTransform().GetLocation().Y - this->GetActorLocation().Y)) < ViewSize.Y;
AdvPawnLocationCurrent = AdvPawnRef->GetActorLocation(); if (MapPawnInViewX && MapPawnInViewY) { AddActorLocalOffset(FollowPawn->GetVelocity() * DeltaSeconds); }
AdvPawnLocationDelta = AdvPawnLocationCurrent - AdvPawnLocationPrevious;
UE_LOG(LogTemp, Warning, TEXT("%s"), *AdvPawnLocationDelta.ToString());
AddActorLocalOffset(AdvPawnLocationDelta);
AdvPawnLocationPrevious = AdvPawnLocationCurrent;
} }

View File

@ -19,35 +19,24 @@ public:
UGameViewportClient* Viewport; UGameViewportClient* Viewport;
UPROPERTY() UPROPERTY()
FIntPoint ViewSize; FIntPoint ViewSize;
UPROPERTY()
int32 TickIncrement = 0;
UPROPERTY(BlueprintReadWrite, Category = "Config") UPROPERTY(BlueprintReadWrite, Category = "Config")
class AAdventureMap* AdvMapRef; class AAdventureMap* AdvMapRef;
UPROPERTY(BlueprintReadWrite, Category = "Config") UPROPERTY(BlueprintReadWrite, Category = "Config")
class AAdventurePlayerController* ControllerRef; class AAdventurePlayerController* OwningPC;
UPROPERTY(BlueprintReadWrite, Category = "Config") UPROPERTY(BlueprintReadWrite, Category = "Config")
class AAdventureCharacter* AdvPawnRef; class AAdventureCharacter* FollowPawn;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input") UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Input")
float ESASize; float ESASize;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input") UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Input")
float ScrollAccelleration; float ScrollAccelleration;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input") UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Input")
float BaseScrollSpeed; float BaseScrollSpeed;
UPROPERTY()
FVector AdvPawnLocationPrevious;
UPROPERTY()
FVector AdvPawnLocationCurrent;
UPROPERTY()
FVector AdvPawnLocationDelta;
UPROPERTY(BlueprintReadWrite, Category = "Runtime")
bool bAdvPawnIsMoving;
UPROPERTY(BlueprintReadWrite, Category = "Runtime")
class AHexTile* SelectedHex;
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;
@ -67,11 +56,15 @@ public:
// Called to bind functionality to input // Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UFUNCTION()
void ScrollSide(float AxisValue);
UFUNCTION()
void ScrollVert(float AxisValue);
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
void EdgeScrollSide(float AxisValue); void EdgeScrollSide(float AxisValue);
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
void EdgeScrollVert(float AxisValue); void EdgeScrollVert(float AxisValue);
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
void FollowAdvPawn(); void FollowAdvPawn(float DeltaSeconds);
}; };

View File

@ -31,12 +31,4 @@ void AAdventureCharacter::Tick(float DeltaTime)
void AAdventureCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) void AAdventureCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{ {
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,12 @@ public:
// Sets default values for this character's properties // Sets default values for this character's properties
AAdventureCharacter(); AAdventureCharacter();
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Category = "Runtime") UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Config")
AHexTile* GridLocation; class AAdventureMap* MapRef;
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Category = "Runtime")
AHexTile* SelectedHex;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Runtime") UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Runtime")
TArray<AHexTile*> MovementPath; AHexTile* Location;
UFUNCTION(BlueprintCallable) UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Runtime")
void AStarFindPath(AHexTile* Goal); bool bIsMoving;
protected: protected:
// Called when the game starts or when spawned // Called when the game starts or when spawned

View File

@ -3,118 +3,383 @@
#include "AdventureMap.h" #include "AdventureMap.h"
#include "HexTile.h" #include "HexTile.h"
#include "AdventureCameraPawn.h" #include "StarFog.h"
#include "AdventureCharacter.h" #include "AdventurePlayerController.h"
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
#include "Algo/Reverse.h"
#include "Util/IndexPriorityQueue.h"
#include <tgmath.h>
// Sets default values // Sets default values
AAdventureMap::AAdventureMap() AAdventureMap::AAdventureMap()
{ {
FHexVector NBs[] = { E, SSE, SSW, W, NNW, NNE };
UnitVectors.Append(NBs, UE_ARRAY_COUNT(NBs));
FHexVector DNBs[] = { N, ENE, ESE, S, WSW, WNW };
DiagonalUnitVectors.Append(DNBs, UE_ARRAY_COUNT(DNBs));
} }
// Called when the game starts or when spawned // Called when the game starts or when spawned
void AAdventureMap::BeginPlay() void AAdventureMap::BeginPlay()
{ {
Super::BeginPlay(); Super::BeginPlay();
UWorld* World = GetWorld(); World = GetWorld();
if (IsValid(BaseTileClass))
{ if (IsValid(BaseTileClass)) {
MakeGrid(); MakeGrid();
} }
for (auto& Tile : Grid) {
AStarFog* Fog = World->SpawnActor<AStarFog>(BaseFogClass, Tile->GetActorTransform());
Fog->CoveredHex = Tile;
Tile->CoveringFog = Fog;
}
} }
// Called once on Begin Play // Called once on Begin Play
void AAdventureMap::MakeGrid() void AAdventureMap::MakeGrid()
{ {
UWorld* World = GetWorld();
FVector NextHexAt = FVector(); FVector NextHexAt = FVector();
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 > 1) { QOffset--; } }
else { XOffset = HexWidth / 2; }
if (r % 2 != 0) for (int q = 1; q <= GridSize; q++) {
{
if (r > 1)
{
QOffset--; // The Q axis is (i.e. columns are) oriented diagonally.
}
}
else
{
XOffset = HexWidth / 2;
}
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;
FTransform SpawnTransform = FTransform(NextHexAt); FTransform SpawnTransform = FTransform(NextHexAt);
AHexTile* Tile = World->SpawnActor<AHexTile>(BaseTileClass, SpawnTransform); AHexTile* Tile = World->SpawnActor<AHexTile>(BaseTileClass, SpawnTransform);
Grid.Add(Tile);
Tile->Q = q - 1 + QOffset; Tile->Q = q - 1 + QOffset;
Tile->R = r - 1; Tile->R = r - 1;
Grid.Add(Tile);
} }
} }
for (auto& tile : Grid) {
for (auto& tile : Grid)
{
tile->Index = GridIndex(tile->Q, tile->R); tile->Index = GridIndex(tile->Q, tile->R);
} }
bHexGridReady = true; bHexGridReady = true;
} }
// Every Hex Tile's index within the Grid Array can be derived from its Q and R coordinates // Every Hex Tile's index within the Grid Array can be derived from its Axial Q and R coordinates
int32 AAdventureMap::GridIndex(int32 qAxial, int32 rAxial) int32 AAdventureMap::GridIndex(int32 qAxial, int32 rAxial)
{ {
/* /*
* The Q axis is (i.e. columns are) oriented diagonally. * The Q axis is (i.e. columns are) oriented diagonally.
* The Hex Grid has a rough square shape, hence the Q coordinates must be offset by -1 every other row. * The Hex Grid has a rough square shape, hence the Q coordinates must be offset by -1 every other row.
*/ */
int32 column = qAxial + FMath::FloorToInt(rAxial / 2); int32 column = qAxial + FMath::FloorToInt(rAxial / 2.);
return (rAxial * GridSize) + column; return (rAxial * GridSize) + column;
} }
AHexTile* AAdventureMap::RandomHex() AHexTile* AAdventureMap::RandomHex()
{ {
int32 RandHex = GridIndex(FMath::RandRange(0, GridSize-1), FMath::RandRange(0, GridSize-1)); //debug
//while (RandHex > Grid.Num()) GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("Picking A Random Hex"));
//{
// RandHex = GridIndex(FMath::RandRange(0, GridSize), FMath::RandRange(0, GridSize)); int32 RandHex = FMath::RandRange(0, GridSize*GridSize-1);
//}
return Grid[RandHex]; return Grid[RandHex];
} }
TArray<AHexTile*> AAdventureMap::Neighbors(AHexTile* OfHex) FHexVector AAdventureMap::AxialRound(float qf, float rf)
{ {
TArray<AHexTile*> Neighbors; float sf = -qf - rf;
int32 Index;
Index = GridIndex(OfHex->Q + 1 , OfHex->R + 0 ); int32 q = round(qf);
if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } int32 r = round(rf);
int32 s = round(sf);
Index = GridIndex(OfHex->Q + 1 , OfHex->R - 1 ); float q_diff = abs(q - qf);
if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } float r_diff = abs(r - rf);
float s_diff = abs(s - sf);
Index = GridIndex(OfHex->Q + 0 , OfHex->R - 1 ); if (q_diff > r_diff && q_diff > s_diff)
if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); } { q = -r - s; }
else if (r_diff > s_diff)
{ r = -q - s; }
else
{ s = -q - r; }
Index = GridIndex(OfHex->Q - 1 , OfHex->R + 0 ); return FHexVector(q, r);
if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); }
Index = GridIndex(OfHex->Q - 1 , OfHex->R + 1 );
if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); }
Index = GridIndex(OfHex->Q + 0 , OfHex->R + 1 );
if (Index >= 0 && Index < Grid.Num() && OfHex->Distance(Grid[Index]) == 1) { Neighbors.Add(Grid[Index]); }
return Neighbors;
} }
float AAdventureMap::Lerp(int32 a, int32 b, float t)
{
return float(a + (b - a) * t);
}
TArray<AHexTile*> AAdventureMap::Neighbors(AHexTile* OfHex, bool bFreeOnly = false)
{
TArray<AHexTile*> Results;
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;
}
TArray<AHexTile*> AAdventureMap::FreeDiagonals(AHexTile* OfHex)
{
TArray<AHexTile*> Results;
for (auto& V : DiagonalUnitVectors) {
int32 I = GridIndex(OfHex->Q + V.Q, OfHex->R + V.R);
if (!Grid.IsValidIndex(I)) { continue; }
else {
bool bReachable = true;
for (AHexTile* PotentialBlock : Neighbors(OfHex)) {
if (PotentialBlock->Distance(Grid[I]) != 1) { continue; }
if (!PotentialBlock->bFree) {
bReachable = false;
break;
}
}
if (bReachable) { Results.Add(Grid[I]); }
}
}
return Results;
}
TSet<AHexTile*> AAdventureMap::BreadthFirstSearch(AHexTile* Start, int32 Radius)
{
TSet<AHexTile*> Results;
TArray<AHexTile*> Frontier;
TSet<AHexTile*> Processed;
Results.Add(Start);
Frontier.Add(Start);
while (!Frontier.IsEmpty()) {
AHexTile* Current = Frontier[0];
Processed.Add(Current);
Frontier.Remove(Current);
for (AHexTile* Neighbor : Neighbors(Current)) {
if (Neighbor->Distance(Current) > 1) { continue; }
if (Processed.Contains(Neighbor)) { continue; }
if (Neighbor->Distance(Start) > Radius) { continue; }
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)
{
TSet<AHexTile*> Processed;
TSet<AHexTile*> ToSearch;
TPriorityQueue<AHexTile*> Frontier;
Start->GCost = 0;
Frontier.Push(Start, .0f);
ToSearch.Add(Start);
while (!Frontier.IsEmpty()) {
AHexTile* Current = Frontier.Pop();
ToSearch.Remove(Current);
if (Current == Goal) { break; }
Processed.Add(Current);
for (AHexTile* Neighbor : Neighbors(Current, true)) {
if (Processed.Contains(Neighbor)) { continue; }
bool bInToSearch = ToSearch.Contains(Start);
int32 NewGCost = Current->GCost + Neighbor->MoveCost;
if (!bInToSearch || NewGCost < Neighbor->GCost) {
Neighbor->GCost = NewGCost;
Neighbor->CameFrom = Current;
if (!bInToSearch) {
Neighbor->HCost = Neighbor->Distance(Goal);
Frontier.Push(Neighbor, NewGCost + Neighbor->HCost);
ToSearch.Add(Neighbor);
}
}
}
}
TArray<AHexTile*> Path;
if (!IsValid(Goal->CameFrom)) { return Path; }
AHexTile* iPathNode = Goal;
while (iPathNode != Start) {
Path.Emplace(iPathNode);
iPathNode = iPathNode->CameFrom;
}
Algo::Reverse(Path);
return 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;
}
// very bro-sciency approach to pathfinding for diagonal Hex-movement
// DO NOT USE
TArray<AHexTile*> AAdventureMap::ShortcutAStar(TArray<AHexTile*> Path)
{
TArray<AHexTile*> Shortcut;
int32 Len = Path.Num();
TArray<AHexTile*> WorkingSegment;
AHexTile* CurrentHex = PCRef->CurrentHex;
WorkingSegment.Add(CurrentHex);
int32 h = 0;
// create segments for each bend
FHexVector PrevDir = FHexVector(Path[0], CurrentHex);
FHexVector DirASave = PrevDir;
FHexVector DirA;
int32 HexesBeforeBend = 1;
for (h; h < Len-1; h++) {
WorkingSegment.Add(Path[h]);
DirA = FHexVector(Path[h+1], Path[h]);
if (DirA != PrevDir) { break; } // save Path[h] into Array of Bends
HexesBeforeBend++;
PrevDir = DirA;
}
PrevDir = DirA;
FHexVector DirB;
int32 HexesAfterBend = 0;
for (h; h < Len - 1; h++) {
WorkingSegment.Add(Path[h+1]);
DirB = FHexVector(Path[h+1], Path[h]);
if (DirB != PrevDir) { break; }
HexesAfterBend++;
PrevDir = DirB;
}
if (HexesAfterBend == 0)
{ return Path; }
FHexVector UnitDiag = UnitDiagFromUnitNB(DirASave, DirB);
AHexTile* Milestone = WorkingSegment.Last();
int32 NumDiags = (HexesBeforeBend >= HexesAfterBend) ? HexesAfterBend : HexesBeforeBend;
int32 NumTries = (HexesBeforeBend >= HexesAfterBend) ? HexesBeforeBend : HexesAfterBend;
bool bDiagAdded = false;
for (int i = 0; i < NumTries; i++) {
if (NumDiags == 0) {
Shortcut.Append(FindPathAStar(CurrentHex, Milestone, false));
break;
}
if (DiagIsReachable(CurrentHex, UnitDiag)) {
int32 CanIndex = GridIndex(CurrentHex->Q + UnitDiag.Q, CurrentHex->R + UnitDiag.R);
if (Grid.IsValidIndex(CanIndex)) {
AHexTile* Current = Grid[CanIndex];
if (Current->bFree) {
Shortcut.Add(Current);
bDiagAdded = true;
CurrentHex = Current;
NumDiags--;
continue;
} } }
if (!bDiagAdded && !DiagIsReachable(CurrentHex, UnitDiag)) {
Shortcut.Add(CurrentHex);
CurrentHex = WorkingSegment[i + 1];
NumDiags--;
continue;
}
if (bDiagAdded && !DiagIsReachable(CurrentHex, UnitDiag)) {
Shortcut.Append(FindPathAStar(CurrentHex, Milestone, true));
break;
}
}
if (Milestone != Path.Last()) { Shortcut.Append(FindPathAStar(Milestone, Path.Last(), true)); }
UE_LOG(LogTemp, Warning, TEXT("Hexes before bend: %d"), HexesBeforeBend);
UE_LOG(LogTemp, Warning, TEXT("Hexes after bend: %d"), HexesAfterBend);
return Shortcut;
}
FHexVector AAdventureMap::UnitDiagFromUnitNB(FHexVector InVecA, FHexVector InVecB) {
if (InVecA == NNW && InVecB == NNE||InVecB == NNW && InVecA == NNE) { return N; }
if (InVecA == NNE && InVecB == E ||InVecB == NNE && InVecA == E) { return ENE; }
if (InVecA == E && InVecB == SSE||InVecB == E && InVecA == SSE) { return ESE; }
if (InVecA == SSE && InVecB == SSW||InVecB == SSE && InVecA == SSW) { return S; }
if (InVecA == SSW && InVecB == W ||InVecB == SSW && InVecA == W) { return WSW; }
if (InVecA == W && InVecB == NNW||InVecB == W && InVecA == NNW) { return WNW; }
return FHexVector();
}
bool AAdventureMap::DiagIsReachable(AHexTile* InStart, FHexVector InDiagUnitVec) {
FHexVector BlockA;
FHexVector BlockB;
if (InDiagUnitVec == N) { BlockA = NNW, BlockB = NNE; }
if (InDiagUnitVec == ENE) { BlockA = NNE, BlockB = E; }
if (InDiagUnitVec == ESE) { BlockA = E, BlockB = SSE; }
if (InDiagUnitVec == S) { BlockA = SSE, BlockB = SSW; }
if (InDiagUnitVec == WSW) { BlockA = SSW, BlockB = W; }
if (InDiagUnitVec == WNW) { BlockA = W, BlockB = NNW; }
int32 IndexA = GridIndex(InStart->Q + BlockA.Q, InStart->R + BlockA.R);
int32 IndexB = GridIndex(InStart->Q + BlockB.Q, InStart->R + BlockB.R);
if (!Grid.IsValidIndex(IndexA) || !Grid.IsValidIndex(IndexB)) { return false; }
AHexTile* HexA = Grid[IndexA];
AHexTile* HexB = Grid[IndexB];
return (HexA->bFree && HexB->bFree);
}

View File

@ -1,13 +1,15 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "GameFramework/Actor.h" #include "GameFramework/Actor.h"
#include "Containers/Map.h"
#include "HexVector.h"
#include "AdventureMap.generated.h" #include "AdventureMap.generated.h"
class AHexTile; class AHexTile;
class AAdventureCharacter; class AStarFog;
class AMapObject;
class AAdventurePlayerController;
UCLASS() UCLASS()
class FRAY_API AAdventureMap : public AActor class FRAY_API AAdventureMap : public AActor
@ -18,36 +20,154 @@ public:
// Sets default values for this actor's properties // Sets default values for this actor's properties
AAdventureMap(); AAdventureMap();
UPROPERTY()
UWorld* World;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config")
TSubclassOf<AHexTile> BaseTileClass; TSubclassOf<AHexTile> BaseTileClass;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config")
TSubclassOf<ACharacter> BasePartyCharacterClass; TSubclassOf<AStarFog> BaseFogClass;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config")
int32 GridSize = 100; // squared is the number of Tiles int32 GridSize = 60;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config")
int32 TileSize = 100; int32 TileSize = 100;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
AAdventurePlayerController* PCRef;
UFUNCTION(BlueprintCallable, Category = "Generation") UFUNCTION(BlueprintCallable, Category = "Generation")
void MakeGrid(); void MakeGrid();
UPROPERTY(BlueprintReadOnly, Category = "Generation") UPROPERTY(BlueprintReadOnly, Category = "Generation")
bool bHexGridReady; bool bHexGridReady;
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, Category = "Generation") UPROPERTY(BlueprintReadOnly, Category = "Generation")
TArray<AHexTile*> Grid; TArray<AHexTile*> Grid;
UFUNCTION(BlueprintCallable, Category = "Runtime") UFUNCTION(BlueprintCallable, Category = "Utility")
int32 GridIndex(int32 q, int32 r); int32 GridIndex(int32 q, int32 r);
UFUNCTION(BlueprintCallable, Category = "Runtime") UFUNCTION(BlueprintCallable, Category = "Utility")
AHexTile* RandomHex(); AHexTile* RandomHex();
UFUNCTION(BlueprintCallable, Category = "Runtime")
TArray<AHexTile*> Neighbors(AHexTile* OfHex);
// Player spawn section // Cardinal direction vectors
UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UPROPERTY(BlueprintReadOnly, VisibleAnywhere) //diag
APawn* CameraPawn; FHexVector N = FHexVector(1, -2); //
UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
AAdventureCharacter* PlayerCharacter; FHexVector NNE = FHexVector(1, -1);
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) //diag
FHexVector ENE = FHexVector(2, -1); //
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
FHexVector E = FHexVector(1, 0);
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) //diag
FHexVector ESE = FHexVector(1, 1); //
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
FHexVector SSE = FHexVector(0, 1);
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) //diag
FHexVector S = FHexVector(-1, 2); //
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
FHexVector SSW = FHexVector(-1, 1);
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) //diag
FHexVector WSW = FHexVector(-2, 1); //
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
FHexVector W = FHexVector(-1, 0);
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) //diag
FHexVector WNW = FHexVector(-1, -1); //
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
FHexVector NNW = FHexVector(0, -1);
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TArray<FHexVector> UnitVectors;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TArray<FHexVector> DiagonalUnitVectors;
UFUNCTION(BlueprintCallable, Category = "Utility")
FHexVector UnitDiagFromUnitNB(FHexVector InVecA, FHexVector InVecB);
UFUNCTION(BlueprintCallable, Category = "Utility")
bool DiagIsReachable(AHexTile* InStart, FHexVector InDiagUnitVec);
UFUNCTION(BlueprintCallable, Category = "Utility")
float Lerp(int32 a, int32 b, float t);
UFUNCTION(BlueprintCallable, Category = "Utility")
FHexVector AxialRound(float q, float r);
UFUNCTION(BlueprintCallable, Category = "Utility")
TArray<AHexTile*> Neighbors(AHexTile* OfHex, bool bFreeOnly);
UFUNCTION(BlueprintCallable, Category = "Utility")
TArray<AHexTile*> FreeDiagonals(AHexTile* OfHex);
UFUNCTION(BlueprintCallable, Category = "Utility")
TSet<AHexTile*> BreadthFirstSearch(AHexTile* Start, int32 Radius);
UFUNCTION(BlueprintCallable, Category = "Runtime")
TArray<AHexTile*> FindPathAStar(AHexTile* Start, AHexTile* Goal, bool bDiags);
UFUNCTION(BlueprintCallable, Category = "Runtime")
TArray<AHexTile*> ShortcutAStar(TArray<AHexTile*> Path);
// considering a MapObjectManager class or moving pathfinding & search to PlayerController
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TMap<AHexTile*, AMapObject*> MapObjects;
UPROPERTY(BlueprintReadOnly)
TMap<int32, AMapObject*> MapObjectsByID;
UPROPERTY(BlueprintReadWrite, VisibleAnywhere)
int32 IncID = -1;
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;
};
// 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

@ -6,11 +6,141 @@
#include "HexTile.h" #include "HexTile.h"
#include "AdventureCameraPawn.h" #include "AdventureCameraPawn.h"
#include "AdventureCharacter.h" #include "AdventureCharacter.h"
#include "MapObject.h"
#include "MovementArrow.h"
AAdventurePlayerController::AAdventurePlayerController() AAdventurePlayerController::AAdventurePlayerController()
{ {
PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = true; PrimaryActorTick.bStartWithTickEnabled = true;
AutoReceiveInput = EAutoReceiveInput::Player0;
}
void AAdventurePlayerController::BeginPlay()
{
Super::BeginPlay();
World = GetWorld();
HoveredHex = CurrentHex;
}
// Called every frame
void AAdventurePlayerController::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (bInPlacementMode) { FitOnGrid(PlaceObj); }
}
void AAdventurePlayerController::SetupInputComponent()
{
// Always call this.
Super::SetupInputComponent();
// This is initialized on startup, you can go straight to binding
InputComponent->BindAction("LeftClick", IE_Pressed, this, &AAdventurePlayerController::LeftClick);
InputComponent->BindAction("DebugAlt", IE_Pressed, this, &AAdventurePlayerController::EnablePlacing); // Change binding eventually
InputComponent->BindAction("DebugAlt", IE_Released, this, &AAdventurePlayerController::DisablePlacing); // Change binding eventually
}
void AAdventurePlayerController::LeftClick()
{
if (IsValid(HoveredHex)) {
if (!bInPlacementMode) {
}
else { PlaceObject(PlaceObjClass, HoveredHex); }
}
else { return; }
}
TArray<AHexTile*> AAdventurePlayerController::Vision(int32 Radius)
{
TSet<AHexTile*> InRange = MapRef->BreadthFirstSearch(CurrentHex, Radius);
TArray<AHexTile*> Results;
for (auto& Hex : InRange) {
if (Hex->Distance(CurrentHex) == Radius)
{
for (int32 i = 1; i <= Radius; i++)
{
float t = 1.0f / Radius * i;
FHexVector Sample;
Sample = MapRef->AxialRound(MapRef->Lerp(CurrentHex->Q, Hex->Q, t), MapRef->Lerp(CurrentHex->R, Hex->R, t));
AHexTile* SampleHex = MapRef->Grid[MapRef->GridIndex(Sample.Q, Sample.R)];
Results.Add(SampleHex);
if (!ExploredHexes.Contains(SampleHex))
{ ExploredHexes.Add(Hex); }
if (!SampleHex->bFree)
{ break; }
}
}
}
return Results;
}
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++)
{
DirB = FHexVector(Path[i + 1]->Q - Path[i]->Q, Path[i + 1]->R - Path[i]->R);
AMovementArrow* Arrow = World->SpawnActor<AMovementArrow>(MoveArrowClass, Path[i]->GetActorTransform());
Arrow->MapRef = MapRef;
Arrow->SetVariant(DirA, DirB);
PathArrows.Add(Arrow);
DirA = DirB;
}
}
void AAdventurePlayerController::ClearPath()
{
for (AMovementArrow* Arrow : PathArrows) {
Arrow->Destroy();
}
PathArrows.Empty();
}
void AAdventurePlayerController::EnablePlacing()
{
bInPlacementMode = true;
PlaceObj = World->SpawnActor<AMapObject>(PlaceObjClass, FTransform());
}
void AAdventurePlayerController::DisablePlacing()
{
bInPlacementMode = false;
// if (IsValid(PlaceObj)) { PlaceObj->Destroy(); }
}
void AAdventurePlayerController::FitOnGrid(AMapObject* MapObject)
{
if (!IsValid(HoveredHex)) { return; }
if (HoveredHex->bFree) { MapObject->SetActorLocation(FVector(HoveredHex->GetActorLocation())); }
}
// To-Do: factor out core functionality to a seperate (more neutral) class like AdventureMap.
void AAdventurePlayerController::PlaceObject(TSubclassOf<AMapObject> MapObjClass, AHexTile* HoveredTile)
{
AMapObject* SpawnedObj = World->SpawnActor<AMapObject>(MapObjClass, FTransform(HoveredTile->GetActorTransform().GetLocation()));
SpawnedObj->MapRef = MapRef;
SpawnedObj->Origin = HoveredTile;
SpawnedObj->bPlaced = true;
HoveredTile->bFree = false;
HoveredTile->MapObject = SpawnedObj;
MapRef->MapObjects.Add(HoveredTile, SpawnedObj);
MapRef->IncID++;
SpawnedObj->ID = MapRef->IncID;
MapRef->MapObjectsByID.Add(MapRef->IncID, SpawnedObj);
SpawnedObj->Occupy();
} }

View File

@ -1,5 +1,3 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
@ -11,6 +9,8 @@ class AAdventureMap;
class AHexTile; class AHexTile;
class AAdventureCameraPawn; class AAdventureCameraPawn;
class AAdventureCharacter; class AAdventureCharacter;
class AMapObject;
class AMovementArrow;
/** /**
* *
@ -23,5 +23,66 @@ class FRAY_API AAdventurePlayerController : public APlayerController
public: public:
AAdventurePlayerController(); AAdventurePlayerController();
// General
UPROPERTY()
UWorld* World;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
AAdventureMap* MapRef;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
AAdventureCameraPawn* CamRef;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
AHexTile* SpawnHex;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Runtime")
AHexTile* CurrentHex;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
AHexTile* HoveredHex;
UFUNCTION(BlueprintCallable)
void LeftClick();
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly)
TSubclassOf<AMovementArrow> MoveArrowClass;
UFUNCTION(BlueprintCallable)
void MarkPath(TArray<AHexTile*> Path);
UPROPERTY(BlueprintReadWrite, VisibleAnywhere)
TArray<AMovementArrow*> PathArrows;
UFUNCTION(BlueprintCallable)
void ClearPath();
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 VisionRadius = 6;
UFUNCTION(BlueprintCallable)
TArray<AHexTile*> Vision(int32 Radius);
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TSet<AHexTile*> ExploredHexes;
protected:
virtual void BeginPlay() override;
virtual void SetupInputComponent() override;
public: public:
// Object Placement
UPROPERTY(BlueprintReadWrite, VisibleAnywhere)
bool bInPlacementMode;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TSubclassOf<AMapObject> PlaceObjClass;
UPROPERTY(BlueprintReadWrite, VisibleAnywhere)
AMapObject* PlaceObj;
UFUNCTION(BlueprintCallable)
void EnablePlacing();
UFUNCTION(BlueprintCallable)
void DisablePlacing();
UFUNCTION(BlueprintCallable)
void FitOnGrid(AMapObject* MapObject);
UFUNCTION()
void PlaceObject(TSubclassOf<AMapObject> MapObjClass, AHexTile* OnHex);
// Called every frame
virtual void Tick(float DeltaTime) override;
}; };

View File

@ -1,34 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Clickable.h"
// Sets default values for this component's properties
UClickable::UClickable()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
// ...
}
// Called when the game starts
void UClickable::BeginPlay()
{
Super::BeginPlay();
// ...
}
// Called every frame
void UClickable::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// ...
}

View File

@ -1,28 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Clickable.generated.h"
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class FRAY_API UClickable : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UClickable();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
};

View File

@ -10,40 +10,26 @@
AHexTile::AHexTile() AHexTile::AHexTile()
{ {
TileSize = 100.f; TileSize = 100.f;
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Scene")); RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));
this->FillCornersArray(); this->FillCornersArray();
} }
void AHexTile::BeginPlay() void AHexTile::BeginPlay() { Super::BeginPlay(); }
{
Super::BeginPlay();
}
FVector AHexTile::Corner(int32 i) FVector AHexTile::Corner(int32 i)
{ {
FVector TileCenter = this->GetActorTransform().GetLocation(); FVector TileCenter = this->GetActorTransform().GetLocation();
int32 Angle_Deg = 60 * i - 30; int32 Angle_Deg = 60 * i - 30;
float Angle_Rad = UKismetMathLibrary::GetPI()/180 * Angle_Deg; float Angle_Rad = UKismetMathLibrary::GetPI()/180 * Angle_Deg;
float X = TileCenter.X + TileSize * cos(Angle_Rad); float X = TileCenter.X + TileSize * cos(Angle_Rad);
float Y = TileCenter.Y + TileSize * sin(Angle_Rad); float Y = TileCenter.Y + TileSize * sin(Angle_Rad);
return FVector(X, Y, 0.f); return FVector(X, Y, 0.f);
} }
void AHexTile::FillCornersArray() void AHexTile::FillCornersArray() { for (int32 i = 0 ; i < 6; i++) { Corners.Emplace(Corner(i + 1)); } }
{
for (int32 i = 0 ; i < 6; i++)
{
Corners.Emplace(Corner(i + 1));
}
}
int32 AHexTile::Distance(AHexTile* ToHex) int32 AHexTile::Distance(AHexTile* ToHex)
{ {
int32 CubeS1 = -this->Q - this->R; int32 CubeS1 = -this->Q - this->R;
int32 CubeS2 = -ToHex->Q - ToHex->R; int32 CubeS2 = -ToHex->Q - ToHex->R;
return (abs(this->Q - ToHex->Q) + abs(this->R - ToHex->R) + abs(CubeS1 - CubeS2)) / 2; return (abs(this->Q - ToHex->Q) + abs(this->R - ToHex->R) + abs(CubeS1 - CubeS2)) / 2;
} }

View File

@ -1,5 +1,3 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
@ -10,6 +8,8 @@
class USceneComponent; class USceneComponent;
class UStaticMeshComponent; class UStaticMeshComponent;
class AAdventureMap; class AAdventureMap;
class AMapObject;
class AStarFog;
class AAdventurePlayerController; class AAdventurePlayerController;
UCLASS() UCLASS()
@ -23,31 +23,65 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Config")
float TileSize; float TileSize;
//UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Config")
// USceneComponent* SceneComponent;
UPROPERTY(BlueprintReadWrite, Category = "Config")
AAdventureMap* MapRef;
UPROPERTY(BlueprintReadWrite, Category = "Config")
AStarFog* CoveringFog;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
USceneComponent* SceneComponent;
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)
int32 MoveCost = 1;
UPROPERTY()
int32 FCost;
UPROPERTY()
int32 GCost = 9999;
UPROPERTY()
int32 HCost;
UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Runtime")
AHexTile* CameFrom;
// MapObject Placement
UPROPERTY(BlueprintReadWrite, VisibleAnywhere)
bool bFree = true;
UPROPERTY(BlueprintReadWrite, VisibleAnywhere)
AMapObject* MapObject;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
bool bCanActivate = false;
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
bool bEvent = false;
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;
}; };

56
HexVector.h Normal file
View File

@ -0,0 +1,56 @@
#pragma once
#include "HexTile.h"
#include "HexVector.generated.h"
USTRUCT(BlueprintType)
struct FHexVector
{
GENERATED_BODY()
FORCEINLINE FHexVector();
FORCEINLINE explicit FHexVector(int32 InQ, int32 InR);
FORCEINLINE explicit FHexVector(int32 InQ, int32 InR, bool bInDiag, bool bInUnit);
FORCEINLINE explicit FHexVector(AHexTile* InHex);
FORCEINLINE explicit FHexVector(AHexTile* InHexA, AHexTile* InHexB);
UPROPERTY(BlueprintReadWrite)
int32 Q;
UPROPERTY(BlueprintReadWrite)
int32 R;
UPROPERTY(BlueprintReadWrite)
int32 S;
UPROPERTY(BlueprintReadWrite)
bool bIsDiagonal;
UPROPERTY(BlueprintReadWrite)
bool bUnit;
};
FORCEINLINE FHexVector::FHexVector()
{}
FORCEINLINE FHexVector::FHexVector(int32 InQ, int32 InR)
: Q(InQ), R(InR) {}
FORCEINLINE FHexVector::FHexVector(int32 InQ, int32 InR, bool InIsDiag, bool InIsUnit)
: Q(InQ), R(InR), bIsDiagonal(InIsDiag), bUnit(InIsUnit) {}
FORCEINLINE FHexVector::FHexVector(AHexTile* InHex)
: Q(InHex->Q), R(InHex->R) {}
FORCEINLINE FHexVector::FHexVector(AHexTile* InHexA, AHexTile* InHexB)
: Q(InHexA->Q - InHexB->Q), R(InHexA->R - InHexB->R) {}
FORCEINLINE bool operator==(const FHexVector& A, const FHexVector& B)
{
if (A.Q == B.Q && A.R == B.R) { return true; }
else { return false; }
}
FORCEINLINE bool operator!=(const FHexVector& A, const FHexVector& B)
{
if (A.Q == B.Q && A.R == B.R) { return false; }
else { return true; }
}
FORCEINLINE uint32 GetTypeHash(const FHexVector& Thing)
{
uint32 Hash = FCrc::MemCrc32(&Thing, sizeof(FHexVector));
return Hash;
}

47
MapObject.cpp Normal file
View File

@ -0,0 +1,47 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "MapObject.h"
#include "AdventureMap.h"
#include "HexTile.h"
// Sets default values
AMapObject::AMapObject()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));
RootComponent = SceneComponent;
OrientHexMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Orient"));
OrientHexMesh->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform);
}
// Called when the game starts or when spawned
void AMapObject::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AMapObject::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AMapObject::Touch()
{
}
void AMapObject::Activate()
{
}
// Any subclass of MapObject has a defined array of Vectors relative to its origin which has to occupy() upon being placed on the map.
//void AMapObject::Occupy(int32 Q, int32 R)
//{
// AHexTile* OccupiedHex = MapRef->Grid[MapRef->GridIndex(Q, R)];
//
// OccupiedHex->bFree = false;
// OccupiedHex->MapObject = this;
//}

62
MapObject.h Normal file
View File

@ -0,0 +1,62 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "HexVector.h"
#include "MapObject.generated.h"
class USceneComponent;
class UStaticMeshComponent;
class AAdventureMap;
UCLASS()
class FRAY_API AMapObject : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMapObject();
UPROPERTY(BlueprintReadOnly, Category = "Config")
AAdventureMap* MapRef;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Config")
USceneComponent* SceneComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Config")
UStaticMeshComponent* OrientHexMesh;
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Config")
bool bCollectable;
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Config")
bool bActivatable;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Generation")
int32 ID;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Generation")
class AHexTile* Origin; // very important
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Generation")
bool bPlaced;
UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "Generation")
TArray<FHexVector> Occupying;
UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "Runtime")
AController* FlaggedBy;
UFUNCTION()
virtual void Touch();
UFUNCTION()
virtual void Activate();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UFUNCTION(BlueprintImplementableEvent)
void Occupy();
};

90
MovementArrow.cpp Normal file
View File

@ -0,0 +1,90 @@
#include "MovementArrow.h"
#include "AdventureMap.h"
// Sets default values
AMovementArrow::AMovementArrow()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));
RootComponent = SceneComponent;
MeshStraight = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Straight"));
MeshRight = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Right"));
MeshRightSharp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("RightSharp"));
MeshLeftSharp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LeftSharp"));
MeshLeft = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Left"));
MeshVariants.Add(MeshStraight);
MeshVariants.Add(MeshRight);
MeshVariants.Add(MeshRightSharp);
MeshVariants.Add(MeshLeftSharp);
MeshVariants.Add(MeshLeft);
for (UStaticMeshComponent* Mesh : MeshVariants) {
Mesh->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform);
Mesh->ToggleVisibility();
}
}
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 (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 (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 (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 (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 (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 (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();
}
// Called when the game starts or when spawned
void AMovementArrow::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AMovementArrow::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}

48
MovementArrow.h Normal file
View File

@ -0,0 +1,48 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "HexVector.h"
#include "MovementArrow.generated.h"
class USceneComponent;
class UStaticMeshComponent;
class AAdventureMap;
UCLASS()
class FRAY_API AMovementArrow : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMovementArrow();
UPROPERTY(BlueprintReadOnly, Category = "Config")
AAdventureMap* MapRef;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Config")
USceneComponent* SceneComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Variant")
UStaticMeshComponent* MeshStraight;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Variant")
UStaticMeshComponent* MeshRight;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Variant")
UStaticMeshComponent* MeshRightSharp;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Variant")
UStaticMeshComponent* MeshLeftSharp;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Variant")
UStaticMeshComponent* MeshLeft;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Variant")
TArray<UStaticMeshComponent*> MeshVariants;
UFUNCTION(BlueprintCallable, Category = "Variant")
void SetVariant(FHexVector InVector, FHexVector OutVector);
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};

27
StarFog.cpp Normal file
View File

@ -0,0 +1,27 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "StarFog.h"
// Sets default values
AStarFog::AStarFog()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void AStarFog::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AStarFog::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}

31
StarFog.h Normal file
View File

@ -0,0 +1,31 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "StarFog.generated.h"
class AHexTile;
UCLASS()
class FRAY_API AStarFog : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AStarFog();
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
AHexTile* CoveredHex;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};