Files
UnrealEngine/Engine/Source/Runtime/AIModule/Private/Navigation/NavLinkProxy.cpp
2025-05-18 13:04:45 +08:00

386 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Navigation/NavLinkProxy.h"
#include "UObject/ConstructorHelpers.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/Controller.h"
#include "NavigationSystem.h"
#include "NavigationSystemTypes.h"
#include "Components/BillboardComponent.h"
#include "Engine/Texture2D.h"
#include "Navigation/PathFollowingComponent.h"
#include "NavLinkCustomComponent.h"
#include "NavLinkRenderingComponent.h"
#include "NavAreas/NavArea_Default.h"
#include "AI/NavigationSystemHelpers.h"
#include "VisualLogger/VisualLogger.h"
#include "NavigationOctree.h"
#include "ObjectEditorUtils.h"
#if WITH_EDITOR
#include "ScopedTransaction.h"
#endif
#include UE_INLINE_GENERATED_CPP_BY_NAME(NavLinkProxy)
#define LOCTEXT_NAMESPACE "NavLink"
#if WITH_EDITOR
namespace UE::Navigation::LinkProxy::Private
{
void OnNavAreaRegistrationChanged(ANavLinkProxy& LinkProxy, const UWorld& World, const UClass* NavAreaClass)
{
if (&World != LinkProxy.GetWorld())
{
return;
}
bool bUpdateActor = false;
for (const FNavigationLink& NavLink : LinkProxy.PointLinks)
{
if (NavLink.GetAreaClass() == NavAreaClass)
{
bUpdateActor = true;
break;
}
}
if (bUpdateActor)
{
FNavigationSystem::UpdateActorData(LinkProxy);
}
}
} // UE::Navigation::LinkProxy::Private
#endif // WITH_EDITOR
ANavLinkProxy::ANavLinkProxy(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
USceneComponent* SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("PositionComponent"));
RootComponent = SceneComponent;
SetHidden(true);
#if WITH_EDITORONLY_DATA
EdRenderComp = CreateDefaultSubobject<UNavLinkRenderingComponent>(TEXT("EdRenderComp"));
EdRenderComp->SetupAttachment(RootComponent);
#endif // WITH_EDITORONLY_DATA
#if WITH_EDITOR
SpriteComponent = CreateEditorOnlyDefaultSubobject<UBillboardComponent>(TEXT("Sprite"));
if (!IsRunningCommandlet() && (SpriteComponent != NULL))
{
struct FConstructorStatics
{
ConstructorHelpers::FObjectFinderOptional<UTexture2D> SpriteTexture;
FName ID_Decals;
FText NAME_Decals;
FConstructorStatics()
: SpriteTexture(TEXT("/Engine/EditorResources/AI/S_NavLink"))
, ID_Decals(TEXT("Navigation"))
, NAME_Decals(NSLOCTEXT("SpriteCategory", "Navigation", "Navigation"))
{
}
};
static FConstructorStatics ConstructorStatics;
SpriteComponent->Sprite = ConstructorStatics.SpriteTexture.Get();
SpriteComponent->SetRelativeScale3D(FVector(0.5f, 0.5f, 0.5f));
SpriteComponent->bHiddenInGame = true;
SpriteComponent->SetVisibleFlag(true);
SpriteComponent->SpriteInfo.Category = ConstructorStatics.ID_Decals;
SpriteComponent->SpriteInfo.DisplayName = ConstructorStatics.NAME_Decals;
SpriteComponent->SetupAttachment(RootComponent);
SpriteComponent->SetAbsolute(false, false, true);
SpriteComponent->bIsScreenSizeScaled = true;
}
#endif
SmartLinkComp = CreateDefaultSubobject<UNavLinkCustomComponent>(TEXT("SmartLinkComp"));
SmartLinkComp->SetNavigationRelevancy(false);
SmartLinkComp->SetMoveReachedLink(this, &ANavLinkProxy::NotifySmartLinkReached);
bSmartLinkIsRelevant = false;
FNavigationLink DefLink;
DefLink.SetAreaClass(UNavArea_Default::StaticClass());
PointLinks.Add(DefLink);
SetActorEnableCollision(false);
SetCanBeDamaged(false);
}
void ANavLinkProxy::PostInitProperties()
{
Super::PostInitProperties();
#if WITH_EDITOR
if (GIsEditor && !HasAnyFlags(RF_ClassDefaultObject))
{
OnNavAreaRegisteredDelegateHandle = UNavigationSystemBase::OnNavAreaRegisteredDelegate().AddUObject(this, &ANavLinkProxy::OnNavAreaRegistered);
OnNavAreaUnregisteredDelegateHandle = UNavigationSystemBase::OnNavAreaUnregisteredDelegate().AddUObject(this, &ANavLinkProxy::OnNavAreaUnregistered);
}
#endif // WITH_EDITOR
}
void ANavLinkProxy::BeginDestroy()
{
#if WITH_EDITOR
if (GIsEditor && !HasAnyFlags(RF_ClassDefaultObject))
{
UNavigationSystemBase::OnNavAreaRegisteredDelegate().Remove(OnNavAreaRegisteredDelegateHandle);
UNavigationSystemBase::OnNavAreaUnregisteredDelegate().Remove(OnNavAreaUnregisteredDelegateHandle);
}
#endif // WITH_EDITOR
Super::BeginDestroy();
}
#if WITH_EDITOR
// This function is only called if GIsEditor == true
void ANavLinkProxy::OnNavAreaRegistered(const UWorld& World, const UClass* NavAreaClass)
{
UE::Navigation::LinkProxy::Private::OnNavAreaRegistrationChanged(*this, World, NavAreaClass);
}
// This function is only called if GIsEditor == true
void ANavLinkProxy::OnNavAreaUnregistered(const UWorld& World, const UClass* NavAreaClass)
{
UE::Navigation::LinkProxy::Private::OnNavAreaRegistrationChanged(*this, World, NavAreaClass);
}
void ANavLinkProxy::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
static const FName NAME_SmartLinkIsRelevant = GET_MEMBER_NAME_CHECKED(ANavLinkProxy, bSmartLinkIsRelevant);
static const FName NAME_PointLinks = GET_MEMBER_NAME_CHECKED(ANavLinkProxy, PointLinks);
static const FName NAME_AreaClass = TEXT("AreaClass");
const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;
const FName MemberPropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None;
bool bUpdateInNavOctree = false;
if (PropertyName == NAME_SmartLinkIsRelevant)
{
SmartLinkComp->SetNavigationRelevancy(bSmartLinkIsRelevant);
bUpdateInNavOctree = true;
}
const FName CategoryName = FObjectEditorUtils::GetCategoryFName(PropertyChangedEvent.Property);
const FName MemberCategoryName = FObjectEditorUtils::GetCategoryFName(PropertyChangedEvent.MemberProperty);
if (CategoryName == TEXT("SimpleLink") || MemberCategoryName == TEXT("SimpleLink"))
{
bUpdateInNavOctree = true;
if (PropertyName == NAME_AreaClass && MemberPropertyName == NAME_PointLinks)
{
for (FNavigationLink& Link : PointLinks)
{
Link.InitializeAreaClass(/*bForceRefresh=*/true);
}
}
}
if (bUpdateInNavOctree)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys)
{
FNavigationSystem::UpdateActorData(*this);
}
}
Super::PostEditChangeProperty(PropertyChangedEvent);
}
void ANavLinkProxy::PostEditUndo()
{
Super::PostEditUndo();
for (FNavigationLink& Link : PointLinks)
{
Link.InitializeAreaClass(/*bForceRefresh=*/true);
}
}
void ANavLinkProxy::PostEditImport()
{
Super::PostEditImport();
for (FNavigationLink& Link : PointLinks)
{
Link.InitializeAreaClass(/*bForceRefresh=*/true);
}
}
#endif // WITH_EDITOR
void ANavLinkProxy::PostRegisterAllComponents()
{
// Check if the smart link is enabled and valid for determining navigation relevancy
// A smart link with no simple links will result in warnings for invalid bounds if Super is called prior to setting the navigation relevancy of the smart link.
if (SmartLinkComp)
{
SmartLinkComp->SetNavigationRelevancy(bSmartLinkIsRelevant);
}
Super::PostRegisterAllComponents();
}
void ANavLinkProxy::PostLoad()
{
Super::PostLoad();
for (FNavigationLink& Link : PointLinks)
{
Link.InitializeAreaClass();
}
}
#if ENABLE_VISUAL_LOG
void ANavLinkProxy::BeginPlay()
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys)
{
REDIRECT_OBJECT_TO_VLOG(this, NavSys);
}
Super::BeginPlay();
}
#endif // ENABLE_VISUAL_LOG
void ANavLinkProxy::GetNavigationData(FNavigationRelevantData& Data) const
{
NavigationHelper::ProcessNavLinkAndAppend(&Data.Modifiers, this, PointLinks);
NavigationHelper::ProcessNavLinkSegmentAndAppend(&Data.Modifiers, this, SegmentLinks);
}
FBox ANavLinkProxy::GetNavigationBounds() const
{
return GetComponentsBoundingBox();
}
bool ANavLinkProxy::IsNavigationRelevant() const
{
return (PointLinks.Num() > 0) || (SegmentLinks.Num() > 0) || bSmartLinkIsRelevant;
}
bool ANavLinkProxy::GetNavigationLinksClasses(TArray<TSubclassOf<UNavLinkDefinition> >& OutClasses) const
{
return false;
}
bool ANavLinkProxy::GetNavigationLinksArray(TArray<FNavigationLink>& OutLink, TArray<FNavigationSegmentLink>& OutSegments) const
{
OutLink.Append(PointLinks);
const bool bIsSmartLinkActive = (SmartLinkComp && SmartLinkComp->IsNavigationRelevant());
if (bIsSmartLinkActive)
{
OutLink.Add(SmartLinkComp->GetLinkModifier());
}
OutSegments.Append(SegmentLinks);
return (PointLinks.Num() > 0) || (SegmentLinks.Num() > 0) || bIsSmartLinkActive;
}
FBox ANavLinkProxy::GetComponentsBoundingBox(bool bNonColliding, bool bIncludeFromChildActors) const
{
FBox LinksBB(FVector(0.f, 0.f, -10.f), FVector(0.f,0.f,10.f));
for (int32 i = 0; i < PointLinks.Num(); ++i)
{
const FNavigationLink& Link = PointLinks[i];
LinksBB += Link.Left;
LinksBB += Link.Right;
}
for (int32 i = 0; i < SegmentLinks.Num(); ++i)
{
const FNavigationSegmentLink& SegmentLink = SegmentLinks[i];
LinksBB += SegmentLink.LeftStart;
LinksBB += SegmentLink.LeftEnd;
LinksBB += SegmentLink.RightStart;
LinksBB += SegmentLink.RightEnd;
}
LinksBB = LinksBB.TransformBy(RootComponent->GetComponentTransform());
if (SmartLinkComp && SmartLinkComp->IsNavigationRelevant())
{
LinksBB += SmartLinkComp->GetStartPoint();
LinksBB += SmartLinkComp->GetEndPoint();
}
return LinksBB;
}
void ANavLinkProxy::NotifySmartLinkReached(UNavLinkCustomComponent* LinkComp, UObject* PathingAgent, const FVector& DestPoint)
{
UPathFollowingComponent* PathComp = Cast<UPathFollowingComponent>(PathingAgent);
if (PathComp)
{
AActor* PathOwner = PathComp->GetOwner();
AController* ControllerOwner = Cast<AController>(PathOwner);
if (ControllerOwner)
{
PathOwner = ControllerOwner->GetPawn();
}
ReceiveSmartLinkReached(PathOwner, DestPoint);
OnSmartLinkReached.Broadcast(PathOwner, DestPoint);
}
}
void ANavLinkProxy::ResumePathFollowing(AActor* Agent)
{
if (Agent)
{
UPathFollowingComponent* PathComp = Agent->FindComponentByClass<UPathFollowingComponent>();
if (PathComp == NULL)
{
APawn* PawnOwner = Cast<APawn>(Agent);
if (PawnOwner && PawnOwner->GetController())
{
PathComp = PawnOwner->GetController()->FindComponentByClass<UPathFollowingComponent>();
}
}
if (PathComp)
{
PathComp->FinishUsingCustomLink(SmartLinkComp);
}
}
}
bool ANavLinkProxy::IsSmartLinkEnabled() const
{
return SmartLinkComp->IsEnabled();
}
void ANavLinkProxy::SetSmartLinkEnabled(bool bEnabled)
{
SmartLinkComp->SetEnabled(bEnabled);
}
bool ANavLinkProxy::HasMovingAgents() const
{
return SmartLinkComp->HasMovingAgents();
}
#if WITH_EDITOR
void ANavLinkProxy::CopyEndPointsFromSimpleLinkToSmartLink()
{
if (PointLinks.Num() && SmartLinkComp)
{
{
const FScopedTransaction Transaction(LOCTEXT("SetLinkData", "Set Link Data"));
SmartLinkComp->Modify();
SmartLinkComp->SetLinkData(PointLinks[0].Left, PointLinks[0].Right, PointLinks[0].Direction);
}
if (EdRenderComp)
{
EdRenderComp->MarkRenderStateDirty();
}
}
}
#endif // WITH_EDITOR
#undef LOCTEXT_NAMESPACE