Skip to content

Instantly share code, notes, and snippets.

@CoffeeVampir3
Last active November 23, 2023 18:07
Show Gist options
  • Save CoffeeVampir3/2551c4001a5a6d71e8a52ddecce7df87 to your computer and use it in GitHub Desktop.
Save CoffeeVampir3/2551c4001a5a6d71e8a52ddecce7df87 to your computer and use it in GitHub Desktop.
Spline Based Actor Pathfinding
#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(); }
};
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