Last active
November 23, 2023 18:07
-
-
Save CoffeeVampir3/2551c4001a5a6d71e8a52ddecce7df87 to your computer and use it in GitHub Desktop.
Spline Based Actor Pathfinding
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <coroutine> | |
#include <utility> | |
struct CoroTask | |
{ | |
struct promise_type; | |
using HandleType = std::coroutine_handle<promise_type>; | |
struct promise_type | |
{ | |
std::suspend_always initial_suspend() { return {}; } | |
std::suspend_always final_suspend() noexcept { return {}; } | |
CoroTask get_return_object() { return { HandleType::from_promise(*this)}; } | |
void unhandled_exception() noexcept {}; | |
void return_void() noexcept {}; | |
}; | |
void Destroy() const noexcept | |
{ | |
if(Handle) | |
{ | |
Handle.destroy(); | |
} | |
} | |
void operator()() const | |
{ | |
if(!Handle) return; | |
if(Handle.done()) | |
{ | |
Destroy(); | |
return; | |
} | |
Handle(); | |
} | |
HandleType Handle; | |
CoroTask(const HandleType H): Handle{H}{} | |
explicit operator HandleType() const { return Handle; } | |
explicit operator bool() const { return Handle && !Handle.done(); } | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
bool UNecroAiComponent::UpdateActorTargetSpline(FVector& LastPathingPoint, | |
bool& UpdatedSpline, | |
const AActor* TargetActor, | |
const float NewPathRadiusSquared = 10000.f, | |
const bool ForceInitialized = false) const | |
{ | |
const auto ActorPosition = TargetActor->GetActorLocation(); | |
const auto PositionDeltaSq = FVector::DistSquared(LastPathingPoint, ActorPosition); | |
UpdatedSpline = false; | |
if(!ForceInitialized && (PositionDeltaSq < NewPathRadiusSquared)) | |
{ | |
return true; | |
} | |
if(const auto Path = UNavigationSystemV1::FindPathToLocationSynchronously(GetWorld(), | |
ControlledCharacter->GetActorLocation(), | |
ActorPosition)) | |
{ | |
SplineComponent->ClearSplinePoints(); | |
for (const auto& Point : Path->PathPoints) | |
{ | |
SplineComponent->AddSplinePoint(Point, ESplineCoordinateSpace::World, false); | |
DrawDebugSphere(ControlledCharacter->GetWorld(), Point, 8.f, 8, FColor::Green, false, 5.f); | |
} | |
SplineComponent->UpdateSpline(); | |
UpdatedSpline = true; | |
} else | |
{ | |
return false; | |
} | |
LastPathingPoint = ActorPosition; | |
return true; | |
} | |
bool WithinAcceptanceRadiusSquared(const ACharacter* FirstActor, const AActor* SecondActor, const float AcceptanceRadiusSq) | |
{ | |
return FVector::DistSquared(FirstActor->GetActorLocation(), SecondActor->GetActorLocation()) <= AcceptanceRadiusSq; | |
} | |
CoroTask UNecroAiComponent::MoveToActor(const AActor* TargetActor, const float AcceptanceRadius = 30.0f) const | |
{ | |
if(!TargetActor) co_return; | |
const auto AcceptanceRadiusSq = AcceptanceRadius*AcceptanceRadius; | |
bool SplineUpdated = false; | |
float DeltaSinceLastUpdate = 1.0f; | |
if(WithinAcceptanceRadiusSquared(ControlledCharacter, TargetActor, AcceptanceRadiusSq)) | |
{ | |
co_return; | |
} | |
FVector LastPathingPoint = TargetActor->GetActorLocation(); | |
if(!UpdateActorTargetSpline(LastPathingPoint, SplineUpdated, TargetActor, 0.0f, true)) | |
{ | |
co_return; | |
} | |
const auto LogAcceptance = FMath::LogX(10, AcceptanceRadius); | |
while(true) | |
{ | |
if(WithinAcceptanceRadiusSquared(ControlledCharacter, TargetActor, AcceptanceRadiusSq)) { | |
UE_LOG(LogTemp, Warning, TEXT("Reached target location.")); | |
co_return; | |
} | |
DeltaSinceLastUpdate += GetWorld()->GetDeltaSeconds(); | |
const auto ClosestSplinePoint = SplineComponent->FindLocationClosestToWorldLocation(ControlledCharacter->GetActorLocation(), ESplineCoordinateSpace::World); | |
const auto SplineDirection = SplineComponent->FindDirectionClosestToWorldLocation(ClosestSplinePoint, ESplineCoordinateSpace::World); | |
ControlledCharacter->AddMovementInput(SplineDirection); | |
co_await std::suspend_always{}; | |
if(!TargetActor) { | |
co_return; | |
} | |
//Use a modulation for the acceptance to update the spline that decays with time, so if we updated recently | |
//we're more forgiving with updating again, you can imagine this as temporarily increasing | |
//the minimum radius that we will use as a metric to recalculate paths again as the actor will take time to move. | |
const auto ModulatedAcceptance = FMath::Max((LogAcceptance * AcceptanceRadius / DeltaSinceLastUpdate), AcceptanceRadius); | |
if(!UpdateActorTargetSpline(LastPathingPoint, SplineUpdated, TargetActor, ModulatedAcceptance * ModulatedAcceptance, false)) | |
{ | |
co_return; | |
} | |
if(SplineUpdated) { | |
DeltaSinceLastUpdate = 1.0f; | |
SplineUpdated = false; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment