diff --git a/AdventureMap.cpp b/AdventureMap.cpp index 1b5c073..f1061cc 100644 --- a/AdventureMap.cpp +++ b/AdventureMap.cpp @@ -154,41 +154,42 @@ TArray AAdventureMap::FindPathAStar(AHexTile* Start, AHexTile* Goal, // expand frontier & adjust path data for (AHexTile* Neighbor : Neighbors(Candidate, true)) { - if (Neighbor->Distance(Candidate) > 1) { continue; } if (!Neighbor->bFree) { continue; } if (Processed.Contains(Neighbor)) { continue; } bool bInToExamine = ToExamine.Contains(Neighbor); - float NewGCost = Candidate->GCost + Neighbor->MoveCost * 10.f; + float NewGCost = Candidate->GCost + Neighbor->MoveCost; if (NewGCost < Neighbor->GCost || !bInToExamine) { Neighbor->GCost = NewGCost; Neighbor->CameFrom = Candidate; // chain + Neighbor->bDiagMove = false; if (!bInToExamine) { - Neighbor->HCost = Neighbor->Distance(Goal) * 10.f; + Neighbor->HCost = Neighbor->Distance(Goal); ToExamine.Add(Neighbor); } } } + /* if (bDiags) { for (AHexTile* Diag : FreeDiagonals(Candidate)) { - if (Diag->Distance(Candidate) > 2) { continue; } if (!Diag->bFree) { continue; } if (Processed.Contains(Diag)) { continue; } bool bInToExamine = ToExamine.Contains(Diag); - float NewGCost = Candidate->GCost + 1 + Diag->MoveCost * 10.f; + float NewGCost = Candidate->GCost + Diag->MoveCost; if (NewGCost < Diag->GCost || !bInToExamine) { Diag->GCost = NewGCost; Diag->CameFrom = Candidate; // chain + Diag->bDiagMove = true; if (!bInToExamine) { - Diag->HCost = Diag->Distance(Goal) * 10.f; // not accounting for diagonals + Diag->HCost = Diag->Distance(Goal); ToExamine.Add(Diag); } } } - } + }*/ } TArray Path; if (!IsValid(Goal->CameFrom)) { return Path; } @@ -198,5 +199,106 @@ TArray AAdventureMap::FindPathAStar(AHexTile* Start, AHexTile* Goal, iPathNode = iPathNode->CameFrom; } Algo::Reverse(Path); + + if (bDiags) { + Path = ShortcutAStar(Path); + } + return Path; +} + + +TArray AAdventureMap::ShortcutAStar(TArray Path) +{ + TArray Shortcut; + int32 Len = Path.Num(); + AHexTile* Milestone; + int32 BeforeBend; + int32 AfterBend; + int32 HexIter = 1; + FHexVector pDir; + FHexVector DirA; + FHexVector DirB; + AHexTile* Current = Path[0]; // beginning of curve (starts at Start) + + while (Milestone != Path[Len - 1]) { + // find Milestone (i.e. end of first curve) & determine curve data + BeforeBend = 1; + for (HexIter; HexIter < Len; HexIter++) { + pDir = FHexVector(Path[HexIter], Path[HexIter - 1]); + DirA = FHexVector(Path[HexIter + 1], Path[HexIter]); + if (DirA == pDir) { BeforeBend++; } + else { break; } + } + AfterBend = 1; + for (HexIter; HexIter < Len; HexIter++) { + pDir = FHexVector(Path[HexIter], Path[HexIter - 1]); + DirB = FHexVector(Path[HexIter + 1], Path[HexIter]); + if (DirB == pDir) { AfterBend++; } + else { break; } + } + FHexVector Diag = UnitDiagFromUnitNB(DirA, DirB); // cardinal direction for potential shortcut + TArray WorkingSegment; // current curve + for (int32 i = Path.Find(Current); i < HexIter; i++) { WorkingSegment.Add(Path[i]); } + Milestone = Path[HexIter]; // end of curve + int32 ShoCutLen; + int32 NumDiags; // max number of tries to take a Diagonal + if (BeforeBend >= AfterBend) { + ShoCutLen = BeforeBend; + NumDiags = AfterBend; + } + if (BeforeBend < AfterBend) { + ShoCutLen = AfterBend; + NumDiags = BeforeBend; + } + + TArray NewSegment; + // link from Current to Milestone with diagonals + for (int32 i = 0; i < NumDiags; i++) { + AHexTile* NewCandidate = Grid[GridIndex(Current->Q + Diag.Q, Current->R + Diag.R)]; + + if (i==0 && !DiagIsReachable(Current, Diag) || i==0 && !NewCandidate->bFree) { + Current = WorkingSegment[i + 1]; + i++; + continue; + } + NewSegment.Add(NewCandidate); + } + // connect the rest via A* probably, think about checking whether the result is really shorter + int32 CIndex = WorkingSegment.Find(Current); + while (Current != Milestone) { + Current = WorkingSegment[CIndex]; + NewSegment.Add(Current); + CIndex++; + } + + + } + + // Construct shortcut + 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; } + AHexTile* HexA = Grid[GridIndex(InStart->Q + BlockA.Q, InStart->R + BlockA.R)]; + AHexTile* HexB = Grid[GridIndex(InStart->Q + BlockB.Q, InStart->R + BlockB.R)]; + return (HexA->bFree && HexB->bFree); } \ No newline at end of file diff --git a/AdventureMap.h b/AdventureMap.h index 25ef2be..3121fae 100644 --- a/AdventureMap.h +++ b/AdventureMap.h @@ -44,34 +44,37 @@ public: // Cardinal direction vectors UPROPERTY(BlueprintReadOnly, VisibleAnywhere) //diag - FHexVector N = FHexVector(1, -2); + FHexVector N = FHexVector(1, -2); // UPROPERTY(BlueprintReadOnly, VisibleAnywhere) FHexVector NNE = FHexVector(1, -1); UPROPERTY(BlueprintReadOnly, VisibleAnywhere) //diag - FHexVector ENE = FHexVector(2, -1); + FHexVector ENE = FHexVector(2, -1); // UPROPERTY(BlueprintReadOnly, VisibleAnywhere) FHexVector E = FHexVector(1, 0); UPROPERTY(BlueprintReadOnly, VisibleAnywhere) //diag - FHexVector ESE = FHexVector(1, 1); + FHexVector ESE = FHexVector(1, 1); // UPROPERTY(BlueprintReadOnly, VisibleAnywhere) FHexVector SSE = FHexVector(0, 1); UPROPERTY(BlueprintReadOnly, VisibleAnywhere) //diag - FHexVector S = FHexVector(-1, 2); + FHexVector S = FHexVector(-1, 2); // UPROPERTY(BlueprintReadOnly, VisibleAnywhere) FHexVector SSW = FHexVector(-1, 1); UPROPERTY(BlueprintReadOnly, VisibleAnywhere) //diag - FHexVector WSW = FHexVector(-2, 1); + FHexVector WSW = FHexVector(-2, 1); // UPROPERTY(BlueprintReadOnly, VisibleAnywhere) FHexVector W = FHexVector(-1, 0); UPROPERTY(BlueprintReadOnly, VisibleAnywhere) //diag - FHexVector WNW = FHexVector(-1, -1); + FHexVector WNW = FHexVector(-1, -1); // UPROPERTY(BlueprintReadOnly, VisibleAnywhere) FHexVector NNW = FHexVector(0, -1); - UPROPERTY(BlueprintReadOnly, VisibleAnywhere) TArray NeighborUnitVectors; UPROPERTY(BlueprintReadOnly, VisibleAnywhere) TArray DiagonalUnitVectors; + UFUNCTION(BlueprintCallable, Category = "Utility") + FHexVector UnitDiagFromUnitNB(FHexVector InVecA, FHexVector InVecB); + UFUNCTION(BlueprintCallable, Category = "Utility") + bool DiagIsReachable(AHexTile* InStart, FHexVector InDiagUnitVec); UFUNCTION(BlueprintCallable, Category = "Runtime") TArray Neighbors(AHexTile* OfHex, bool bFreeOnly); @@ -81,6 +84,8 @@ public: TSet BreadthFirstSearch(AHexTile* Start, int32 Radius); UFUNCTION(BlueprintCallable, Category = "Runtime") TArray FindPathAStar(AHexTile* Start, AHexTile* Goal, bool bDiags); + UFUNCTION(BlueprintCallable, Category = "Runtime") + TArray ShortcutAStar(TArray Path); // considering a MapObjectManager class or moving pathfinding & search to PlayerController diff --git a/AdventurePlayerController.cpp b/AdventurePlayerController.cpp index fbc5ef2..f344265 100644 --- a/AdventurePlayerController.cpp +++ b/AdventurePlayerController.cpp @@ -45,10 +45,12 @@ void AAdventurePlayerController::LeftClick() if (bInPlacementMode) { PlaceObject(PlaceObjClass, HoveredHex); } } -TArray AAdventurePlayerController::Vision() +TArray AAdventurePlayerController::Vision(int32 Radius) { TArray Results; - TSet Visible = MapRef->BreadthFirstSearch(CurrentHex, 4); + TSet Visible; + if (CurrentHex->bDiagMove) { Radius = FMath::FloorToInt(float(Radius) * (2.f / 3.f)); } + Visible = MapRef->BreadthFirstSearch(CurrentHex, Radius); for (auto& Hex : Visible) { if (ExploredHexes.Contains(Hex)) { continue; } Results.Add(Hex); diff --git a/AdventurePlayerController.h b/AdventurePlayerController.h index b42e5d3..05ad914 100644 --- a/AdventurePlayerController.h +++ b/AdventurePlayerController.h @@ -42,9 +42,9 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite) TSet ExploredHexes; UPROPERTY(EditAnywhere, BlueprintReadWrite) - int32 VisionRadius = 7; + int32 VisionRadius = 6; UFUNCTION(BlueprintCallable) - TArray Vision(); + TArray Vision(int32 Radius); protected: virtual void BeginPlay() override; diff --git a/HexTile.h b/HexTile.h index 951fdae..b8ca315 100644 --- a/HexTile.h +++ b/HexTile.h @@ -45,10 +45,14 @@ public: int32 Distance(AHexTile* ToHex); // Pathfinding - UPROPERTY(BlueprintReadWrite, Category = "Movement") + UPROPERTY(BlueprintReadWrite) float MoveCost = 10; - UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Movement") + UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Runtime") AHexTile* CameFrom; + UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Runtime") + AHexTile* LeadsTo; + UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = "Runtime") + bool bDiagMove = false; UPROPERTY() float FCost; UPROPERTY() diff --git a/HexVector.h b/HexVector.h new file mode 100644 index 0000000..5c2ff64 --- /dev/null +++ b/HexVector.h @@ -0,0 +1,58 @@ +#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; + + TArray Related; +}; + +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; } +} \ No newline at end of file diff --git a/MapObject.cpp b/MapObject.cpp new file mode 100644 index 0000000..eaed6ed --- /dev/null +++ b/MapObject.cpp @@ -0,0 +1,46 @@ +// 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(TEXT("Scene")); + RootComponent = SceneComponent; + OrientHexMesh = CreateDefaultSubobject(TEXT("Orient")); + OrientHexMesh->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform); +} + +// Called when the game starts or when spawned +void AMapObject::BeginPlay() +{ + Super::BeginPlay(); + Occupy(); +} + +// Called every frame +void AMapObject::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); +} + +void AMapObject::Touch() +{ +} + +void AMapObject::Activate() +{ +} + +void AMapObject::Occupy() +{ + for (auto& V : Occupying) { + MapRef->Grid[MapRef->GridIndex(Origin->Q + V.Q, Origin->R + V.R)]->bFree = false; + } +} diff --git a/MapObject.h b/MapObject.h new file mode 100644 index 0000000..63b3b65 --- /dev/null +++ b/MapObject.h @@ -0,0 +1,59 @@ +// 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, VisibleAnywhere, Category = "Config") + bool bCollectable; + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Config") + bool bActivatable; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Generation") + int32 ID; + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Generation") + class AHexTile* Origin; // very important + UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "Generation") + TArray 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; + + void Occupy(); + +};