You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Tested compiling fortnite, unrealeditor, lyra, qagame with non-unity/pch #preflight 63635997876630122adeab9f #rb none [CL 22958990 by henrik karlsson in ue5-main branch]
719 lines
26 KiB
C++
719 lines
26 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "GameplayTask_MoveTo.h"
|
|
#include "AISystem.h"
|
|
#include "GameFramework/Pawn.h"
|
|
#include "VisualLogger/VisualLogger.h"
|
|
#include "AIResources.h"
|
|
#include "GameplayTasksComponent.h"
|
|
#include "NavigationData.h"
|
|
#include "NavigationSystem.h"
|
|
#include "GameplayActuationComponent.h"
|
|
#include "GameFramework/CharacterMovementComponent.h"
|
|
#include "GameplayInteractionsTypes.h"
|
|
#include "GameplayTaskTransition.h"
|
|
#include "Navigation/PathFollowingComponent.h" // LogPathHelper
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(GameplayTask_MoveTo)
|
|
|
|
UGameplayTask_MoveTo::UGameplayTask_MoveTo(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
bTickingTask = true;
|
|
bIsPausable = true;
|
|
|
|
MoveRequest.SetAcceptanceRadius(GET_AI_CONFIG_VAR(AcceptanceRadius));
|
|
MoveRequest.SetReachTestIncludesAgentRadius(GET_AI_CONFIG_VAR(bFinishMoveOnGoalOverlap));
|
|
MoveRequest.SetAllowPartialPath(GET_AI_CONFIG_VAR(bAcceptPartialPaths));
|
|
MoveRequest.SetUsePathfinding(true);
|
|
|
|
AddRequiredResource<UAIResource_Movement>();
|
|
AddClaimedResource<UAIResource_Movement>();
|
|
|
|
ResourceOverlapPolicy = ETaskResourceOverlapPolicy::RequestCancelAndStartAtEnd;
|
|
|
|
Result = EGameplayTaskActuationResult::None;
|
|
bUseContinuousTracking = false;
|
|
bCompleteCalled = false;
|
|
bIsAtLastCorridor = false;
|
|
bEndOfPathTransitionTried = false;
|
|
}
|
|
|
|
UGameplayTask_MoveTo* UGameplayTask_MoveTo::MoveTo(AActor* Actor, FVector InGoalLocation, AActor* InGoalActor, const EGameplayTaskMoveToIntent InEndOfPathIntent,
|
|
float AcceptanceRadius, EAIOptionFlag::Type StopOnOverlap, EAIOptionFlag::Type AcceptPartialPath,
|
|
bool bUsePathfinding, bool bUseContinuousGoalTracking, EAIOptionFlag::Type ProjectGoalOnNavigation)
|
|
{
|
|
if (Actor == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
UGameplayTasksComponent* TaskComponent = Actor->FindComponentByClass<UGameplayTasksComponent>();
|
|
if (TaskComponent == nullptr)
|
|
{
|
|
UE_VLOG(Actor, LogGameplayTasks, Error, TEXT("Expecting Pawn to have Gameplay Tasks Component"));
|
|
return nullptr;
|
|
}
|
|
|
|
UGameplayTask_MoveTo* Task = NewTask<UGameplayTask_MoveTo>(*Cast<IGameplayTaskOwnerInterface>(TaskComponent));
|
|
if (Task == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FAIMoveRequest MoveReq;
|
|
if (InGoalActor)
|
|
{
|
|
MoveReq.SetGoalActor(InGoalActor);
|
|
}
|
|
else
|
|
{
|
|
MoveReq.SetGoalLocation(InGoalLocation);
|
|
}
|
|
|
|
MoveReq.SetAcceptanceRadius(AcceptanceRadius);
|
|
MoveReq.SetReachTestIncludesAgentRadius(FAISystem::PickAIOption(StopOnOverlap, MoveReq.IsReachTestIncludingAgentRadius()));
|
|
MoveReq.SetAllowPartialPath(FAISystem::PickAIOption(AcceptPartialPath, MoveReq.IsUsingPartialPaths()));
|
|
MoveReq.SetUsePathfinding(bUsePathfinding);
|
|
MoveReq.SetProjectGoalLocation(FAISystem::PickAIOption(ProjectGoalOnNavigation, MoveReq.IsProjectingGoal()));
|
|
|
|
Task->SetUp(MoveReq);
|
|
Task->SetContinuousGoalTracking(bUseContinuousGoalTracking);
|
|
Task->SetEndOfPathIntent(InEndOfPathIntent);
|
|
|
|
return Task;
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::SetUp(const FAIMoveRequest& InMoveRequest)
|
|
{
|
|
MoveRequest = InMoveRequest;
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::SetContinuousGoalTracking(bool bEnable)
|
|
{
|
|
bUseContinuousTracking = bEnable;
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::SetEndOfPathIntent(const EGameplayTaskMoveToIntent InEndOfPathIntent)
|
|
{
|
|
EndOfPathIntent = InEndOfPathIntent;
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::FinishTask(const EGameplayTaskActuationResult InResult)
|
|
{
|
|
ResetObservedPath();
|
|
|
|
Result = InResult;
|
|
|
|
if (!bCompleteCalled)
|
|
{
|
|
bCompleteCalled = true;
|
|
|
|
if (Result == EGameplayTaskActuationResult::RequestFailed)
|
|
{
|
|
OnRequestFailed.Broadcast();
|
|
}
|
|
else
|
|
{
|
|
APawn* Pawn = Cast<APawn>(GetAvatarActor());
|
|
OnCompleted.Broadcast(Result, Pawn);
|
|
}
|
|
}
|
|
|
|
// Intentionally not calling EndTask() here, as we expect ExternalCancel() to get called when next task is added.
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::ExternalCancel()
|
|
{
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Log, TEXT("MoveTo: ExternalCancel. Start:%s End:%s"), *GetNameSafe(StartTransitionTask), *GetNameSafe(EndTransitionTask));
|
|
|
|
if (StartTransitionTask != nullptr)
|
|
{
|
|
StartTransitionTask->ExternalCancel();
|
|
}
|
|
if (EndTransitionTask != nullptr)
|
|
{
|
|
EndTransitionTask->ExternalCancel();
|
|
}
|
|
|
|
EndTask();
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::OnTransitionCompleted(const EGameplayTransitionResult InResult, UGameplayTask* InTask)
|
|
{
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Log, TEXT("MoveTo: OnTransitionCompleted. Start:%s End:%s"), *GetNameSafe(StartTransitionTask), *GetNameSafe(EndTransitionTask));
|
|
|
|
if (InTask == EndTransitionTask)
|
|
{
|
|
FinishTask(InResult == EGameplayTransitionResult::Succeeded ? EGameplayTaskActuationResult::Succeeded : EGameplayTaskActuationResult::Failed);
|
|
EndTransitionTask = nullptr;
|
|
}
|
|
if (InTask == StartTransitionTask)
|
|
{
|
|
UGameplayTask* TaskToEnd = StartTransitionTask;
|
|
StartTransitionTask = nullptr;
|
|
TaskToEnd->EndTask();
|
|
}
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::Activate()
|
|
{
|
|
Super::Activate();
|
|
|
|
APawn* Pawn = Cast<APawn>(GetAvatarActor());
|
|
check(Pawn);
|
|
|
|
MovementComponent = Pawn->FindComponentByClass<UCharacterMovementComponent>();
|
|
if (MovementComponent == nullptr)
|
|
{
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Error, TEXT("Failed to find movement component"));
|
|
return;
|
|
}
|
|
ActuationComponent = Pawn->FindComponentByClass<UGameplayActuationComponent>();
|
|
if (ActuationComponent == nullptr)
|
|
{
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Error, TEXT("Failed to find actuation component"));
|
|
return;
|
|
}
|
|
|
|
UE_CVLOG(bUseContinuousTracking, GetGameplayTasksComponent(), LogGameplayTasks, Log, TEXT("Continuous goal tracking requested, moving to: %s"),
|
|
MoveRequest.IsMoveToActorRequest() ? TEXT("actor => looping successful moves!") : TEXT("location => will NOT loop"));
|
|
|
|
if (FindPath() == false)
|
|
{
|
|
FinishTask(EGameplayTaskActuationResult::RequestFailed);
|
|
}
|
|
|
|
InitPathFollowing();
|
|
}
|
|
|
|
bool UGameplayTask_MoveTo::FindPath()
|
|
{
|
|
APawn* Pawn = Cast<APawn>(GetAvatarActor());
|
|
check(Pawn);
|
|
|
|
// Reset
|
|
ResetObservedPath();
|
|
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Log, TEXT("MoveTo: %s"), *MoveRequest.ToString());
|
|
|
|
// Early out if we cannot access needed systems.
|
|
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
|
|
if (NavSys == nullptr)
|
|
{
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Warning, TEXT("%s: failed due to no NavigationSystem present. Note that even pathfinding-less movement requires presence of NavigationSystem."), ANSI_TO_TCHAR(__FUNCTION__));
|
|
return false;
|
|
}
|
|
|
|
const ANavigationData* NavData = (NavSys == nullptr) ? nullptr :
|
|
MoveRequest.IsUsingPathfinding() ? NavSys->GetNavDataForProps(Pawn->GetNavAgentPropertiesRef(), Pawn->GetNavAgentLocation()) :
|
|
NavSys->GetAbstractNavData();
|
|
if (NavData == nullptr)
|
|
{
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Warning, TEXT("%s: Unable to find NavigationData instance"), ANSI_TO_TCHAR(__FUNCTION__));
|
|
return false;
|
|
}
|
|
|
|
if (MoveRequest.IsValid() == false)
|
|
{
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Error, TEXT("%s: MoveTo request failed due MoveRequest not being valid. Most probably desired Goal Actor not longer exists. MoveRequest: '%s'"), ANSI_TO_TCHAR(__FUNCTION__), *MoveRequest.ToString());
|
|
return false;
|
|
}
|
|
|
|
// Validate target
|
|
if (MoveRequest.IsMoveToActorRequest() == false)
|
|
{
|
|
if (MoveRequest.GetGoalLocation().ContainsNaN() || FAISystem::IsValidLocation(MoveRequest.GetGoalLocation()) == false)
|
|
{
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Error, TEXT("%s: Destination is not valid! Goal(%s)"), ANSI_TO_TCHAR(__FUNCTION__), TEXT_AI_LOCATION(MoveRequest.GetGoalLocation()));
|
|
return false;
|
|
}
|
|
|
|
// fail if projection to navigation is required but it failed
|
|
if (MoveRequest.IsProjectingGoal())
|
|
{
|
|
const FNavAgentProperties& AgentProps = Pawn->GetNavAgentPropertiesRef();
|
|
FNavLocation ProjectedLocation;
|
|
|
|
if (NavSys->ProjectPointToNavigation(MoveRequest.GetGoalLocation(), ProjectedLocation, INVALID_NAVEXTENT, &AgentProps) == false)
|
|
{
|
|
if (MoveRequest.IsUsingPathfinding())
|
|
{
|
|
UE_VLOG_LOCATION(GetGameplayTasksComponent(), LogGameplayTasks, Error, MoveRequest.GetGoalLocation(), 30.f, FColor::Red, TEXT("%s: failed to project destination location to navmesh"), ANSI_TO_TCHAR(__FUNCTION__));
|
|
}
|
|
else
|
|
{
|
|
UE_VLOG_LOCATION(GetGameplayTasksComponent(), LogGameplayTasks, Error, MoveRequest.GetGoalLocation(), 30.f, FColor::Red, TEXT("%s: failed to project destination location to navmesh, path finding is disabled perhaps disable goal projection ?"), ANSI_TO_TCHAR(__FUNCTION__));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
MoveRequest.UpdateGoalLocation(ProjectedLocation.Location);
|
|
}
|
|
}
|
|
|
|
FPathFindingQuery PathQuery;
|
|
|
|
FVector GoalLocation = MoveRequest.GetGoalLocation();
|
|
if (MoveRequest.IsMoveToActorRequest())
|
|
{
|
|
const INavAgentInterface* NavGoal = Cast<const INavAgentInterface>(MoveRequest.GetGoalActor());
|
|
if (NavGoal)
|
|
{
|
|
const FVector Offset = NavGoal->GetMoveGoalOffset(Pawn);
|
|
GoalLocation = FQuatRotationTranslationMatrix(MoveRequest.GetGoalActor()->GetActorQuat(), NavGoal->GetNavAgentLocation()).TransformPosition(Offset);
|
|
}
|
|
else
|
|
{
|
|
GoalLocation = MoveRequest.GetGoalActor()->GetActorLocation();
|
|
}
|
|
}
|
|
|
|
const FSharedConstNavQueryFilter NavFilter = UNavigationQueryFilter::GetQueryFilter(*NavData, Pawn, MoveRequest.GetNavigationFilter());
|
|
PathQuery = FPathFindingQuery(*Pawn, *NavData, Pawn->GetNavAgentLocation(), GoalLocation, NavFilter);
|
|
PathQuery.SetAllowPartialPaths(MoveRequest.IsUsingPartialPaths());
|
|
|
|
FPathFindingResult PathResult = NavSys->FindPathSync(PathQuery);
|
|
|
|
if (!PathResult.IsSuccessful() || !PathResult.Path.IsValid())
|
|
{
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Error, TEXT("Trying to find path to %s resulted in Error")
|
|
, MoveRequest.IsMoveToActorRequest() ? *GetNameSafe(MoveRequest.GetGoalActor()) : *MoveRequest.GetGoalLocation().ToString());
|
|
UE_VLOG_SEGMENT(GetGameplayTasksComponent(), LogGameplayTasks, Error, Pawn->GetActorLocation()
|
|
, MoveRequest.GetGoalLocation(), FColor::Red, TEXT("Failed move to %s"), *GetNameSafe(MoveRequest.GetGoalActor()));
|
|
return false;
|
|
}
|
|
|
|
if (MoveRequest.IsMoveToActorRequest())
|
|
{
|
|
PathResult.Path->SetGoalActorObservation(*MoveRequest.GetGoalActor(), 100.0f);
|
|
}
|
|
|
|
PathResult.Path->EnableRecalculationOnInvalidation(true);
|
|
SetObservedPath(PathResult.Path);
|
|
|
|
return true;
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::Resume()
|
|
{
|
|
Super::Resume();
|
|
|
|
// @todo: should repath or fail if InitPathFollowing() fails.
|
|
InitPathFollowing();
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::SetObservedPath(FNavPathSharedPtr InPath)
|
|
{
|
|
if (PathUpdateDelegateHandle.IsValid() && ActuationState.Path.IsValid())
|
|
{
|
|
ActuationState.Path->RemoveObserver(PathUpdateDelegateHandle);
|
|
}
|
|
|
|
PathUpdateDelegateHandle.Reset();
|
|
|
|
ActuationState.Path = InPath;
|
|
if (ActuationState.Path.IsValid())
|
|
{
|
|
// disable auto repaths, it will be handled by move task to include ShouldPostponePathUpdates condition
|
|
ActuationState.Path->EnableRecalculationOnInvalidation(false);
|
|
PathUpdateDelegateHandle = ActuationState.Path->AddObserver(FNavigationPath::FPathObserverDelegate::FDelegate::CreateUObject(this, &UGameplayTask_MoveTo::OnPathEvent));
|
|
}
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::ResetObservedPath()
|
|
{
|
|
if (ActuationState.Path.IsValid())
|
|
{
|
|
ActuationState.Path->DisableGoalActorObservation();
|
|
}
|
|
|
|
if (PathUpdateDelegateHandle.IsValid())
|
|
{
|
|
if (ActuationState.Path.IsValid())
|
|
{
|
|
ActuationState.Path->RemoveObserver(PathUpdateDelegateHandle);
|
|
}
|
|
|
|
PathUpdateDelegateHandle.Reset();
|
|
}
|
|
|
|
ActuationState.Path = nullptr;
|
|
ActuationState.Corridor = nullptr;
|
|
LastCorridorPathPoint = INDEX_NONE;
|
|
CorridorPathPointIndex = INDEX_NONE;
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::OnDestroy(bool bInOwnerFinished)
|
|
{
|
|
Super::OnDestroy(bInOwnerFinished);
|
|
|
|
ResetObservedPath();
|
|
|
|
// clear the shared pointer now to make sure other systems
|
|
// don't think this path is still being used
|
|
ActuationState.Path = nullptr;
|
|
ActuationState.Corridor = nullptr;
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::OnPathEvent(FNavigationPath* InPath, ENavPathEvent::Type Event)
|
|
{
|
|
const APawn* Pawn = Cast<APawn>(GetAvatarActor());
|
|
|
|
const static UEnum* NavPathEventEnum = StaticEnum<ENavPathEvent::Type>();
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Log, TEXT("%s> Path event: %s"), *GetName(), *NavPathEventEnum->GetNameStringByValue(Event));
|
|
|
|
switch (Event)
|
|
{
|
|
case ENavPathEvent::NewPath:
|
|
case ENavPathEvent::UpdatedDueToGoalMoved:
|
|
case ENavPathEvent::UpdatedDueToNavigationChanged:
|
|
if (InPath && InPath->IsPartial() && !MoveRequest.IsUsingPartialPaths())
|
|
{
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Log, TEXT(">> partial path is not allowed, aborting"));
|
|
UPathFollowingComponent::LogPathHelper(Pawn, InPath, MoveRequest.GetGoalActor());
|
|
FinishTask(EGameplayTaskActuationResult::Failed);
|
|
}
|
|
else
|
|
{
|
|
InitPathFollowing();
|
|
}
|
|
break;
|
|
|
|
case ENavPathEvent::Invalidated:
|
|
ConditionalUpdatePath();
|
|
break;
|
|
|
|
case ENavPathEvent::Cleared:
|
|
case ENavPathEvent::RePathFailed:
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Log, TEXT(">> no path, aborting!"));
|
|
FinishTask(EGameplayTaskActuationResult::Failed);
|
|
break;
|
|
|
|
case ENavPathEvent::MetaPathUpdate:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::ConditionalUpdatePath()
|
|
{
|
|
ANavigationData* NavData = ActuationState.Path.IsValid() ? ActuationState.Path->GetNavigationDataUsed() : nullptr;
|
|
if (NavData)
|
|
{
|
|
NavData->RequestRePath(ActuationState.Path, ENavPathUpdateType::NavigationChanged);
|
|
}
|
|
else
|
|
{
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Log, TEXT("%s> unable to repath, aborting!"), *GetName());
|
|
FinishTask(EGameplayTaskActuationResult::Failed);
|
|
}
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::InitPathFollowing()
|
|
{
|
|
// Move character along the path.
|
|
if (!ActuationState.Path.IsValid() || MovementComponent == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Init actor from current orientation.
|
|
const FVector CurrentLocation = MovementComponent->GetActorFeetLocation();
|
|
const FVector CurrentForward = MovementComponent->GetActorTransform().GetRotation().GetForwardVector();
|
|
const FVector CurrentVelocity = MovementComponent->Velocity;
|
|
double CurrentSpeed = CurrentVelocity.Length();
|
|
|
|
HeadingAngle = FMath::RadiansToDegrees(FMath::Atan2(CurrentForward.Y, CurrentForward.X));
|
|
|
|
ActuationState.ActuationName = FName("Move");
|
|
ActuationState.Corridor = MakeShared<FNavCorridor>();
|
|
ActuationState.NavigationLocation = CurrentLocation;
|
|
ActuationState.HeadingDirection = FVector3f(CurrentForward);
|
|
|
|
// Find start section
|
|
LastCorridorPathPoint = INDEX_NONE;
|
|
CorridorPathPointIndex = INDEX_NONE;
|
|
UpdateCorridor(0);
|
|
|
|
// Stop existing transitions
|
|
if (StartTransitionTask)
|
|
{
|
|
StartTransitionTask->EndTask();
|
|
StartTransitionTask = nullptr;
|
|
}
|
|
|
|
// Create start transition
|
|
StartTransitionTask = ActuationComponent->TryMakeTransitionTask(FConstStructView::Make(ActuationState));
|
|
|
|
if (IGameplayTaskTransition* Transition = Cast<IGameplayTaskTransition>(StartTransitionTask))
|
|
{
|
|
Transition->GetTransitionCompleted().AddUObject(this, &UGameplayTask_MoveTo::OnTransitionCompleted);
|
|
StartTransitionTask->ReadyForActivation();
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Log, TEXT("MoveTo: Start Transition: %s"), *GetNameSafe(StartTransitionTask));
|
|
}
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::UpdateCorridor(const int32 PathPointIndex)
|
|
{
|
|
if (!ActuationState.Path.IsValid() || !ActuationState.Corridor.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FNavigationPath* PathInstance = ActuationState.Path.Get();
|
|
if (PathInstance != nullptr && PathInstance->GetPathPoints().IsValidIndex(PathPointIndex))
|
|
{
|
|
// Calculate corridor for up to 4 points at a time
|
|
TArray<FNavPathPoint> PathPoints;
|
|
const int32 NumPathPoints = PathInstance->GetPathPoints().Num();
|
|
int32 LastPointIndex = PathPointIndex;
|
|
const int32 MaxPointIndex = FMath::Min(PathPointIndex + 4, NumPathPoints);
|
|
for (int32 PointIndex = PathPointIndex; PointIndex < MaxPointIndex; PointIndex++)
|
|
{
|
|
const FNavPathPoint& PathPt = PathInstance->GetPathPoints()[PointIndex];
|
|
if (PathPt.CustomLinkId)
|
|
{
|
|
break;
|
|
}
|
|
PathPoints.Add(PathPt);
|
|
LastPointIndex = PointIndex;
|
|
}
|
|
|
|
if (LastPointIndex > LastCorridorPathPoint)
|
|
{
|
|
FSharedConstNavQueryFilter NavQueryFilter = PathInstance->GetQueryData().QueryFilter;
|
|
|
|
const bool bOffsetFirst = PathPointIndex > 0;
|
|
const bool bOffsetLast = (PathPointIndex + PathPoints.Num()) < NumPathPoints;
|
|
|
|
ActuationState.Corridor->BuildFromPathPoints(*PathInstance, PathPoints, PathPointIndex, NavQueryFilter, CorridorParams);
|
|
constexpr double LookAheadToOffsetRatio = 1.0 / 3.0; // Empirically found ratio that works well for most paths.
|
|
ActuationState.Corridor->OffsetPathLocationsFromWalls(FollowLookAheadDistance * LookAheadToOffsetRatio, bOffsetFirst, bOffsetLast);
|
|
|
|
LastCorridorPathPoint = LastPointIndex;
|
|
}
|
|
|
|
CorridorPathPointIndex = PathPointIndex;
|
|
|
|
bIsAtLastCorridor = MaxPointIndex == NumPathPoints;
|
|
}
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::TriggerEndOfPathTransition(const double DistanceToEndOfPath)
|
|
{
|
|
FGameplayActuationState_Standing StandingState;
|
|
StandingState.Prediction.Location = ActuationState.Path->GetEndLocation();
|
|
StandingState.Prediction.Direction = FVector3f::ForwardVector; // @todo: provide correct direction
|
|
StandingState.Prediction.Speed = 0.0f;
|
|
StandingState.Prediction.Time = 0.0f; // @todo: provide correct time
|
|
|
|
if (EndTransitionTask == nullptr && !bEndOfPathTransitionTried)
|
|
{
|
|
EndTransitionTask = ActuationComponent->TryMakeTransitionTask(FConstStructView::Make(StandingState));
|
|
bEndOfPathTransitionTried = true;
|
|
}
|
|
|
|
if (EndTransitionTask
|
|
&& EndTransitionTask->GetState() == EGameplayTaskState::AwaitingActivation)
|
|
{
|
|
FMakeGameplayTransitionTaskContext TransitionContext;
|
|
TransitionContext.Actor = GetOwnerActor();
|
|
TransitionContext.MovementComponent = MovementComponent;
|
|
TransitionContext.ActuationComponent = ActuationComponent;
|
|
TransitionContext.TasksComponent = GetGameplayTasksComponent();
|
|
TransitionContext.CurrentActuationState = FConstStructView::Make(ActuationState);
|
|
TransitionContext.NextActuationState = FConstStructView::Make(StandingState);
|
|
|
|
if (IGameplayTaskTransition* Transition = Cast<IGameplayTaskTransition>(EndTransitionTask))
|
|
{
|
|
if (Transition->ShouldActivate(TransitionContext))
|
|
{
|
|
Transition->GetTransitionCompleted().AddUObject(this, &UGameplayTask_MoveTo::OnTransitionCompleted);
|
|
EndTransitionTask->ReadyForActivation();
|
|
|
|
UE_VLOG(GetGameplayTasksComponent(), LogGameplayTasks, Log, TEXT("MoveTo: End Transition: %s"), *GetNameSafe(EndTransitionTask));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::UpdatePathFollow(const float DeltaTime)
|
|
{
|
|
// Move character along the path.
|
|
if (!ActuationState.Path.IsValid() || MovementComponent == nullptr || !ActuationState.Corridor.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FVector CurrentLocation = MovementComponent->GetActorFeetLocation();
|
|
const FVector CurrentVelocity = MovementComponent->Velocity;
|
|
double CurrentSpeed = CurrentVelocity.Length();
|
|
|
|
// Find current section
|
|
const FNavCorridorLocation NearestPathLoc = ActuationState.Corridor->FindNearestLocationOnPath(CurrentLocation);
|
|
const FNavCorridorLocation LookAheadPathLoc = ActuationState.Corridor->AdvancePathLocation(NearestPathLoc, FollowLookAheadDistance);
|
|
|
|
if (!NearestPathLoc.IsValid() || !LookAheadPathLoc.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
NearestPathLocation = NearestPathLoc.Location;
|
|
LookAheadPathLocation = LookAheadPathLoc.Location;
|
|
|
|
const float MaxSpeed = MovementComponent->GetMaxSpeed();
|
|
// @todo: move to movement styles
|
|
const float MaxTurnRate = 180.0f;
|
|
const float EdgeSeparationDist = 100.0f;
|
|
const float EdgeSeparationForce = 350.0f;
|
|
const float TurnForce = 1.0f / 0.15f;
|
|
const float MaxAcceleration = 350.0f;
|
|
const float MaxDeceleration = 850.0f;
|
|
|
|
const FVector SteerPos = (NearestPathLoc.Location + LookAheadPathLoc.Location) * 0.5;
|
|
const FVector Target = ActuationState.Corridor->ConstrainVisibility(NearestPathLoc, CurrentLocation, SteerPos, FVector::Distance(CurrentLocation, LookAheadPathLoc.Location));
|
|
|
|
const FVector TargetDir = (Target - CurrentLocation).GetSafeNormal2D();
|
|
FVector HeadingDir(FMath::Cos(FMath::DegreesToRadians(HeadingAngle)), FMath::Sin(FMath::DegreesToRadians(HeadingAngle)), 0.0);
|
|
|
|
const FVector Left0 = ActuationState.Corridor->Portals[NearestPathLoc.PortalIndex].Left;
|
|
const FVector Left1 = ActuationState.Corridor->Portals[NearestPathLoc.PortalIndex + 1].Left;
|
|
const FVector LeftNorm = -FVector::CrossProduct(Left1 - Left0, FVector::UpVector).GetSafeNormal();
|
|
|
|
const FVector Right0 = ActuationState.Corridor->Portals[NearestPathLoc.PortalIndex].Right;
|
|
const FVector Right1 = ActuationState.Corridor->Portals[NearestPathLoc.PortalIndex + 1].Right;
|
|
const FVector RightNorm = FVector::CrossProduct(Right1 - Right0, FVector::UpVector).GetSafeNormal();
|
|
|
|
|
|
// Speed control based on predicted collision.
|
|
const double HitDist = FMath::Square(MaxSpeed) / (MaxDeceleration * 2.0);
|
|
double HitT = 1.0;
|
|
ActuationState.Corridor->HitTest(CurrentLocation, CurrentLocation + HeadingDir * HitDist, HitT);
|
|
|
|
const double DistToCollision = HitDist * HitT;
|
|
const double DistToStop = FMath::Square(CurrentSpeed) / (MaxDeceleration * 2.0);
|
|
const double MaxSpeedToStop = FMath::Sqrt(DistToCollision * MaxDeceleration * 2.0);
|
|
|
|
// @todo: rearrange the code so that this can be done where other debug draw is done.
|
|
#if ENABLE_VISUAL_LOG
|
|
if (FVisualLogger::IsRecording())
|
|
{
|
|
const FVector Offset(0,0,50);
|
|
const FVector HitTarget = CurrentLocation + HeadingDir * HitDist * HitT;
|
|
UE_VLOG_SEGMENT_THICK(GetGameplayTasksComponent(), LogGameplayTasks, Log, Offset+CurrentLocation, Offset+HitTarget, FColor::Orange, 2, TEXT("CollDist:%.1f/%.1f Speed:%.1f/%.1f CollSpeed:%1f"), DistToCollision, DistToStop, CurrentSpeed,MaxSpeed, MaxSpeedToStop);
|
|
UE_VLOG_SEGMENT_THICK(GetGameplayTasksComponent(), LogGameplayTasks, Log, Offset+HitTarget, HitTarget, FColor::Orange, 1, TEXT(""));
|
|
}
|
|
#endif // ENABLE_VISUAL_LOG
|
|
|
|
DesiredVelocity = TargetDir * FMath::Min(MaxSpeed, MaxSpeedToStop);
|
|
|
|
const FVector RabbitDir = (LookAheadPathLoc.Location - CurrentLocation).GetSafeNormal();
|
|
|
|
const double LeftDist = FVector::DotProduct(LeftNorm, CurrentLocation - Left0);
|
|
const double LeftMag = FMath::Square(1.0 - FMath::Clamp(LeftDist / EdgeSeparationDist, 0.0f, 1.0));
|
|
|
|
const double RightDist = FVector::DotProduct(RightNorm, CurrentLocation - Right0);
|
|
const double RightMag = FMath::Square(1.0 - FMath::Clamp(RightDist / EdgeSeparationDist, 0.0f, 1.0));
|
|
|
|
FVector Force = FVector::ZeroVector;
|
|
Force += LeftNorm * LeftMag * EdgeSeparationForce;
|
|
Force += RightNorm * RightMag * EdgeSeparationForce;
|
|
|
|
FVector NewVelocity = DesiredVelocity + Force * DeltaTime;
|
|
|
|
const double HeadingYaw = FMath::RadiansToDegrees(FMath::Atan2(NewVelocity.Y, NewVelocity.X));
|
|
const double DeltaYaw = FMath::Clamp(FRotator3f::NormalizeAxis(HeadingYaw - HeadingAngle) * TurnForce, -MaxTurnRate, MaxTurnRate);
|
|
|
|
double Speed = FMath::Max(0.0f, FVector::DotProduct(HeadingDir, DesiredVelocity));
|
|
|
|
Speed = FMath::Min(Speed, MaxSpeedToStop);
|
|
|
|
const float DeltaSpeed = FMath::Clamp(Speed - CurrentSpeed, -MaxDeceleration * DeltaTime, MaxAcceleration * DeltaTime);
|
|
CurrentSpeed += DeltaSpeed;
|
|
|
|
HeadingAngle = FRotator3f::NormalizeAxis(HeadingAngle + DeltaYaw * DeltaTime);
|
|
|
|
const double DistanceToEndOfPath = bIsAtLastCorridor ? ActuationState.Corridor->GetDistanceToEndOfPath(NearestPathLoc) : MAX_dbl;
|
|
|
|
// Create end transition, the task is activated later.
|
|
constexpr double TryActivateDistance = 500.0;
|
|
|
|
if (EndOfPathIntent == EGameplayTaskMoveToIntent::Stop
|
|
&& DistanceToEndOfPath < TryActivateDistance)
|
|
{
|
|
TriggerEndOfPathTransition(DistanceToEndOfPath);
|
|
}
|
|
|
|
const int32 CurrentPathPointIndex = ActuationState.Corridor->Portals[NearestPathLoc.PortalIndex].PathPointIndex;
|
|
if (CurrentPathPointIndex != CorridorPathPointIndex)
|
|
{
|
|
UpdateCorridor(CurrentPathPointIndex);
|
|
}
|
|
|
|
ClampedVelocity = HeadingDir * CurrentSpeed;
|
|
|
|
MovementComponent->SetMovementMode(EMovementMode::MOVE_Walking);
|
|
MovementComponent->RequestDirectMove(ClampedVelocity, /*bForceMaxSpeed*/false);
|
|
|
|
if (StartTransitionTask == nullptr && EndTransitionTask == nullptr)
|
|
{
|
|
const float AcceptanceRadius = MoveRequest.GetAcceptanceRadius() > 0.0f ? MoveRequest.GetAcceptanceRadius() : 10.0f;
|
|
if (DistanceToEndOfPath < AcceptanceRadius)
|
|
{
|
|
FinishTask(EGameplayTaskActuationResult::Succeeded);
|
|
}
|
|
}
|
|
|
|
// Update movement state
|
|
ActuationState.NavigationLocation = CurrentLocation;
|
|
ActuationState.HeadingDirection = FVector3f(FMath::Cos(FMath::DegreesToRadians(HeadingAngle)), FMath::Sin(FMath::DegreesToRadians(HeadingAngle)), 0.0);
|
|
|
|
ActuationState.Prediction.Location = LookAheadPathLocation;
|
|
ActuationState.Prediction.Direction = FVector3f(RabbitDir);
|
|
ActuationState.Prediction.Speed = CurrentSpeed;
|
|
ActuationState.Prediction.Time = 0.0f; // @todo:
|
|
}
|
|
|
|
void UGameplayTask_MoveTo::TickTask(float DeltaTime)
|
|
{
|
|
UWorld* World = GetWorld();
|
|
check(World);
|
|
checkf(MovementComponent, TEXT("Expecting valid Movement Component."));
|
|
|
|
if (Result != EGameplayTaskActuationResult::None)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpdatePathFollow(DeltaTime);
|
|
|
|
#if ENABLE_VISUAL_LOG
|
|
if (FVisualLogger::IsRecording())
|
|
{
|
|
const FVector CurrentLocation = MovementComponent->GetActorFeetLocation();
|
|
UE_VLOG_LOCATION(GetGameplayTasksComponent(), LogGameplayTasks, Log, CurrentLocation, 30, FColor::White, TEXT("Move To"));
|
|
|
|
// Debug draw
|
|
if (ActuationState.Path.IsValid())
|
|
{
|
|
UE::GameplayInteraction::Debug::VLogPath(GetGameplayTasksComponent(), *ActuationState.Path.Get());
|
|
}
|
|
|
|
if (ActuationState.Corridor.IsValid())
|
|
{
|
|
UE::GameplayInteraction::Debug::VLogCorridor(GetGameplayTasksComponent(), *ActuationState.Corridor.Get());
|
|
}
|
|
|
|
const FVector Offset(0,0,10);
|
|
const FVector Offset2(0,0,15);
|
|
|
|
UE_VLOG_SEGMENT_THICK(GetGameplayTasksComponent(), LogGameplayTasks, Log, Offset + CurrentLocation, Offset + CurrentLocation + DesiredVelocity, FColor::Silver, 2, TEXT_EMPTY);
|
|
UE_VLOG_SEGMENT_THICK(GetGameplayTasksComponent(), LogGameplayTasks, Log, Offset2 + CurrentLocation, Offset2 + CurrentLocation + ClampedVelocity, FColor::Yellow, 4, TEXT_EMPTY);
|
|
UE_VLOG_SEGMENT_THICK(GetGameplayTasksComponent(), LogGameplayTasks, Log, Offset2 + NearestPathLocation, Offset2 + LookAheadPathLocation, FColor::Blue, 4, TEXT_EMPTY);
|
|
}
|
|
#endif // ENABLE_VISUAL_LOG
|
|
}
|
|
|