目录
1. 依赖模块
*.build.cs
PublicDependencyModuleNames
"NavigationSystem",
"Landscape",
2. 自定义 ANavMeshBoundsVolume
#pragma once
#include "CoreMinimal.h"
//#include "GameFramework/Volume.h"
#include "NavMesh/NavMeshBoundsVolume.h"
#include "CustomNavMeshBoundsVolume.generated.h"
UCLASS(BlueprintType, Blueprintable)
class NAVTEST_API ACustomNavMeshBoundsVolume : public ANavMeshBoundsVolume, public INavRelevantInterface
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ACustomNavMeshBoundsVolume();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RuntimeNavMesh")
float Width;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RuntimeNavMesh")
float Height;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "RuntimeNavMesh")
float Depth;
// Function to update the Brush Shape
UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh")
void UpdateBrushShape();
//~ Begin AActor Interface
virtual void PostRegisterAllComponents() override;
virtual void PostUnregisterAllComponents() override;
//~ End AActor Interface
#if WITH_EDITOR
//~ Begin UObject Interface
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
virtual void PostEditUndo() override;
//~ End UObject Interface
#endif // WITH_EDITOR
virtual void CalcAndCacheBounds() const;
//~ Begin INavRelevantInterface Interface
virtual FBox GetNavigationBounds() const override;
virtual bool IsNavigationRelevant() const override;
virtual void UpdateNavigationBounds() override;
virtual UObject* GetNavigationParent() const override;
//~ End INavRelevantInterface Interface
//~ Begin Actor Interface
virtual FBox GetComponentsBoundingBox(bool bNonColliding = false, bool bIncludeFromChildActors = false) const override;
//~ End Actor Interface
private:
void updateNavMesh();
protected:
/** bounds for navigation octree */
mutable FBox Bounds;
FBox BoundsPre;
mutable uint32 bBoundsInitialized : 1;
};
.cpp
#include "CustomNavMeshBoundsVolume.h"
#include "PhysicsEngine/BoxElem.h"
#include "NavigationSystem.h"
#include "Components/BrushComponent.h"
ACustomNavMeshBoundsVolume::ACustomNavMeshBoundsVolume()
{
Width = 1000.0f;
Height = 1000.0f;
Depth = 1000.0f;
GetBrushComponent()->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
GetBrushComponent()->Mobility = EComponentMobility::Movable;
BrushColor = FColor(200, 200, 200, 255);
SupportedAgents.MarkInitialized();
bColored = true;
//bNavigationRelevant = true;
PrimaryActorTick.bCanEverTick = true;
BoundsPre.Init();
#if WITH_EDITOR
updateNavMesh();
#endif
}
// Called when the game starts or when spawned
void ACustomNavMeshBoundsVolume::BeginPlay()
{
Super::BeginPlay();
}
void ACustomNavMeshBoundsVolume::updateNavMesh()
{
CalcAndCacheBounds();
if (!(Bounds == BoundsPre))
{
BoundsPre = Bounds;
UpdateBrushShape();
}
}
// Called every frame
void ACustomNavMeshBoundsVolume::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
updateNavMesh();
}
// Function to update the Brush Shape
void ACustomNavMeshBoundsVolume::UpdateBrushShape()
{
UpdateNavigationBounds();
class UBrushComponent* CurrBrushComp = GetBrushComponent();
if (CurrBrushComp)
{
// Clear the old Brush Component
//GetBrushComponent()->ClearBrushComponent();
//GetBrushComponent()->MarkRenderStateDirty();
if (CurrBrushComp->BrushBodySetup)
{
CurrBrushComp->BrushBodySetup->AggGeom.BoxElems.Add(FKBoxElem(Depth, Width, Height));
}
else
{
if (GEngine)
{
//GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, FString::Printf(TEXT("CurrBrushComp->BrushBodySetup invalid!")));
UE_LOG(LogTemp, Log, TEXT("CurrBrushComp->BrushBodySetup invalid!"));
}
}
// Update navigation data
FNavigationSystem::UpdateComponentData(*CurrBrushComp);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys)
{
NavSys->OnNavigationBoundsUpdated(this);
NavSys->UpdateActorInNavOctree(*this);
}
}
else
{
if (GEngine)
{
//GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, FString::Printf(TEXT("CurrBrushComp invalid!")));
UE_LOG(LogTemp, Log, TEXT("CurrBrushComp invalid!"));
}
}
}
void ACustomNavMeshBoundsVolume::PostRegisterAllComponents()
{
Super::PostRegisterAllComponents();
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys && GetLocalRole() == ROLE_Authority)
{
NavSys->OnNavigationBoundsAdded(this);
}
}
void ACustomNavMeshBoundsVolume::PostUnregisterAllComponents()
{
Super::PostUnregisterAllComponents();
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys && GetLocalRole() == ROLE_Authority)
{
NavSys->OnNavigationBoundsRemoved(this);
}
}
#if WITH_EDITOR
void ACustomNavMeshBoundsVolume::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (GIsEditor && NavSys)
{
const FName PropName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : FName();
const FName MemberName = (PropertyChangedEvent.MemberProperty != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : FName();
if (PropName == FName("Width")
|| PropName == FName("Height")
|| PropName == FName("Depth")
|| PropName == FName("ComponentToWorld")
|| PropName == GET_MEMBER_NAME_CHECKED(ABrush, BrushBuilder)
|| MemberName == GET_MEMBER_NAME_CHECKED(ANavMeshBoundsVolume, SupportedAgents)
|| MemberName == USceneComponent::GetRelativeLocationPropertyName()
|| MemberName == USceneComponent::GetRelativeRotationPropertyName()
|| MemberName == USceneComponent::GetRelativeScale3DPropertyName())
{
CalcAndCacheBounds();
BoundsPre = Bounds;
NavSys->OnNavigationBoundsUpdated(this);
}
}
}
void ACustomNavMeshBoundsVolume::PostEditUndo()
{
Super::PostEditUndo();
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (GIsEditor && NavSys)
{
NavSys->OnNavigationBoundsUpdated(this);
}
}
#endif // WITH_EDITOR
void ACustomNavMeshBoundsVolume::CalcAndCacheBounds() const
{
FVector vecScale(1.0, 1.0, 1.0);
vecScale = GetActorScale();
Bounds = FBox::BuildAABB(GetActorLocation(), FVector(Depth, Width, Height) / 2 * vecScale);
//旋转========旋转轴有问题,待优化
FQuat Quat = GetActorQuat();
FTransform RotTran;
RotTran.SetRotation(Quat);
Bounds = Bounds.TransformBy(RotTran);
}
//~ Begin INavRelevantInterface Interface
FBox ACustomNavMeshBoundsVolume::GetNavigationBounds() const
{
//if (!bBoundsInitialized || !(Bounds == BoundsPre))
{
CalcAndCacheBounds();
bBoundsInitialized = true;
}
return Bounds;
}
bool ACustomNavMeshBoundsVolume::IsNavigationRelevant() const
{
return true;// bNavigationRelevant;
}
void ACustomNavMeshBoundsVolume::UpdateNavigationBounds()
{
CalcAndCacheBounds();
bBoundsInitialized = true;
}
UObject* ACustomNavMeshBoundsVolume::GetNavigationParent() const
{
return GetOwner();
}
//~ End INavRelevantInterface Interface
FBox ACustomNavMeshBoundsVolume::GetComponentsBoundingBox(bool bNonColliding/* = false*/, bool bIncludeFromChildActors/* = false*/) const
{
FBox Box(ForceInit);
Box = Super::GetComponentsBoundingBox(bNonColliding, bIncludeFromChildActors);
Box += GetNavigationBounds();
return Box;
}
3. 动态绘制
.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "NavMeshDrawMgrActor.generated.h"
class ULineBatchComponent;
UCLASS()
class NAVTEST_API ANavMeshDrawMgrActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ANavMeshDrawMgrActor();
public:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category=DebugDraw)
bool bShowBox=false;
/** Box Line Batchers. */
UPROPERTY(Transient)
class ULineBatchComponent* BoxLineBatcher;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = DebugDraw)
bool bShowNavMesh = false;
/** NavMesh Line Batchers. */
UPROPERTY(Transient)
class ULineBatchComponent* NavMeshLineBatcher;
/** NavMesh Edges Line Batchers. */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = DebugDraw)
bool bShowNavMeshEdges = false;
UPROPERTY(Transient)
class ULineBatchComponent* NavMeshEdgesLineBatcher;
private:
/** LineBatcher组件初始化 */
void initLineBatcher();
/** LineBatcher组件清理 */
void clearLineBatcher();
/** 绘制清理 */
void FlushBoxDebugLines();
void FlushNavMeshDebugLines();
void FlushNavMeshEdgesDebugLines();
/** 绘制Box */
void DrawDebugBox(const UWorld* InWorld, FVector const& Center, FVector const& Extent, FColor const& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0.f);
/** 绘制Mesh */
void DrawDebugMesh(const UWorld* InWorld, TArray<FVector> const& Verts, TArray<int32> const& Indices, FColor const& Color, bool bPersistent = false, float LifeTime = -1.f, uint8 DepthPriority = 0);
/** 绘制Line */
void DrawDebugLine(const UWorld* InWorld, FVector const& LineStart, FVector const& LineEnd, FColor const& Color, bool bPersistentLines = false, float LifeTime = -1.f, uint8 DepthPriority = 0, float Thickness = 0.f);
/** 更新导航盒子 */
void UpdateNavBoxDraw();
/** 更新导航网格 */
void UpdateNavMeshDraw();
/** 更新导航网格边界 */
void UpdateNavMeshEdgesDraw();
/** 绘制导航网格Tiles */
void DrawNavMeshTiles();
/** 绘制导航网格Edges */
void DrawNavMeshEdges();
public:
/** Draw Navigation Mesh in Runtime (Box) */
UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh|Draw")
bool RtNavMeshDrawBox(bool bInShowBox = true);
/** Draw Navigation Mesh in Runtime (Mesh) */
UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh|Draw")
bool RtNavMeshDrawMesh(bool bInShowNavMesh = true);
/** Draw Navigation Mesh in Runtime (Edges) */
UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh|Draw")
bool RtNavMeshDrawEdges(bool bInShowNavMeshEdges = true);
/** Draw Navigation Mesh in Runtime */
UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh|Draw")
bool RtNavMeshDraw(bool bInShowBox = true, bool bInShowNavMesh = true);
/** Draw Navigation Mesh in Runtime */
UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh|Draw")
bool RuntimeNavMeshDraw(bool bInShowBox = true, bool bInShowNavMesh = true, bool bInShowNavMeshEdges = true);
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "NavMeshDrawMgrActor.h"
#include "NavMesh/NavMeshRenderingComponent.h"
#include "Kismet/GameplayStatics.h"
#include "NavMesh/RecastNavMesh.h"
#include "NavMesh/NavMeshBoundsVolume.h"
#include "EngineUtils.h"
#include "Components/LineBatchComponent.h"
#include "NavigationSystem.h"
#include <QERBoxCollision.h>
//#include "DetourNavMesh.h"
// Sets default values
ANavMeshDrawMgrActor::ANavMeshDrawMgrActor()
{
// 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;
}
void ANavMeshDrawMgrActor::initLineBatcher()
{
if (!BoxLineBatcher)
{
BoxLineBatcher = NewObject<ULineBatchComponent>();
BoxLineBatcher->bCalculateAccurateBounds = false;
}
if (BoxLineBatcher && !BoxLineBatcher->IsRegistered())
{
BoxLineBatcher->RegisterComponentWithWorld(GetWorld());
}
if (!NavMeshLineBatcher)
{
NavMeshLineBatcher = NewObject<ULineBatchComponent>();
NavMeshLineBatcher->bCalculateAccurateBounds = false;
}
if (NavMeshLineBatcher && !NavMeshLineBatcher->IsRegistered())
{
NavMeshLineBatcher->RegisterComponentWithWorld(GetWorld());
}
if (!NavMeshEdgesLineBatcher)
{
NavMeshEdgesLineBatcher = NewObject<ULineBatchComponent>();
NavMeshEdgesLineBatcher->bCalculateAccurateBounds = false;
}
if (NavMeshEdgesLineBatcher && !NavMeshEdgesLineBatcher->IsRegistered())
{
NavMeshEdgesLineBatcher->RegisterComponentWithWorld(GetWorld());
}
}
void ANavMeshDrawMgrActor::clearLineBatcher()
{
if (BoxLineBatcher && BoxLineBatcher->IsRegistered())
{
BoxLineBatcher->UnregisterComponent();
}
if (NavMeshLineBatcher && NavMeshLineBatcher->IsRegistered())
{
NavMeshLineBatcher->UnregisterComponent();
}
if (NavMeshEdgesLineBatcher && NavMeshEdgesLineBatcher->IsRegistered())
{
NavMeshEdgesLineBatcher->UnregisterComponent();
}
}
void ANavMeshDrawMgrActor::FlushBoxDebugLines()
{
if (BoxLineBatcher)
{
BoxLineBatcher->Flush();
}
}
void ANavMeshDrawMgrActor::FlushNavMeshDebugLines()
{
if (NavMeshLineBatcher)
{
NavMeshLineBatcher->Flush();
}
}
void ANavMeshDrawMgrActor::FlushNavMeshEdgesDebugLines()
{
if (NavMeshEdgesLineBatcher)
{
NavMeshEdgesLineBatcher->Flush();
}
}
void ANavMeshDrawMgrActor::DrawDebugBox(const UWorld* InWorld, FVector const& Center, FVector const& Box, FColor const& Color, bool bPersistentLines /*= false*/, float LifeTime /*= -1.f*/, uint8 DepthPriority /*= 0*/, float Thickness /*= 0.f*/)
{
// no debug line drawing on dedicated server
if (GEngine->GetNetMode(InWorld) != NM_DedicatedServer)
{
// this means foreground lines can't be persistent
if (ULineBatchComponent* const LineBatcher = BoxLineBatcher)
{
float LineLifeTime = bPersistentLines ? -1.0f : ((LifeTime > 0.f) ? LifeTime : LineBatcher->DefaultLifeTime);
LineBatcher->DrawLine(Center + FVector(Box.X, Box.Y, Box.Z), Center + FVector(Box.X, -Box.Y, Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
LineBatcher->DrawLine(Center + FVector(Box.X, -Box.Y, Box.Z), Center + FVector(-Box.X, -Box.Y, Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
LineBatcher->DrawLine(Center + FVector(-Box.X, -Box.Y, Box.Z), Center + FVector(-Box.X, Box.Y, Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
LineBatcher->DrawLine(Center + FVector(-Box.X, Box.Y, Box.Z), Center + FVector(Box.X, Box.Y, Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
LineBatcher->DrawLine(Center + FVector(Box.X, Box.Y, -Box.Z), Center + FVector(Box.X, -Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
LineBatcher->DrawLine(Center + FVector(Box.X, -Box.Y, -Box.Z), Center + FVector(-Box.X, -Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
LineBatcher->DrawLine(Center + FVector(-Box.X, -Box.Y, -Box.Z), Center + FVector(-Box.X, Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
LineBatcher->DrawLine(Center + FVector(-Box.X, Box.Y, -Box.Z), Center + FVector(Box.X, Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
LineBatcher->DrawLine(Center + FVector(Box.X, Box.Y, Box.Z), Center + FVector(Box.X, Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
LineBatcher->DrawLine(Center + FVector(Box.X, -Box.Y, Box.Z), Center + FVector(Box.X, -Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
LineBatcher->DrawLine(Center + FVector(-Box.X, -Box.Y, Box.Z), Center + FVector(-Box.X, -Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
LineBatcher->DrawLine(Center + FVector(-Box.X, Box.Y, Box.Z), Center + FVector(-Box.X, Box.Y, -Box.Z), Color, DepthPriority, Thickness, LineLifeTime);
}
}
else
{
//UE_DRAW_SERVER_DEBUG_ON_EACH_CLIENT(&DrawDebugBox, Center, Box, Color, bPersistentLines, LifeTime, DepthPriority, Thickness);
}
}
void ANavMeshDrawMgrActor::DrawDebugMesh(const UWorld* InWorld, TArray<FVector> const& Verts, TArray<int32> const& Indices, FColor const& Color, bool bPersistent /*= false*/, float LifeTime /*= -1.f*/, uint8 DepthPriority /*= 0*/)
{
// no debug line drawing on dedicated server
if (GEngine->GetNetMode(InWorld) != NM_DedicatedServer)
{
if (ULineBatchComponent* const LineBatcher = NavMeshLineBatcher)
{
float const ActualLifetime = bPersistent ? -1.0f : ((LifeTime > 0.f) ? LifeTime : LineBatcher->DefaultLifeTime);
LineBatcher->DrawMesh(Verts, Indices, Color, DepthPriority, ActualLifetime);
}
}
else
{
//UE_DRAW_SERVER_DEBUG_ON_EACH_CLIENT(DrawDebugMesh, Verts, Indices, Color, bPersistent, LifeTime, DepthPriority);
}
}
void ANavMeshDrawMgrActor::DrawDebugLine(const UWorld* InWorld, FVector const& LineStart, FVector const& LineEnd, FColor const& Color, bool bPersistentLines, float LifeTime, uint8 DepthPriority, float Thickness)
{
if (GEngine->GetNetMode(InWorld) != NM_DedicatedServer)
{
// this means foreground lines can't be persistent
if (ULineBatchComponent* const LineBatcher = NavMeshEdgesLineBatcher)
{
float const LineLifeTime = bPersistentLines ? -1.0f : ((LifeTime > 0.f) ? LifeTime : LineBatcher->DefaultLifeTime);
LineBatcher->DrawLine(LineStart, LineEnd, Color, DepthPriority, Thickness, LineLifeTime);
}
}
else
{
//UE_DRAW_SERVER_DEBUG_ON_EACH_CLIENT(DrawDebugLine, LineStart, LineEnd, AdjustColorForServer(Color), bPersistentLines, LifeTime, DepthPriority, Thickness);
}
}
void ANavMeshDrawMgrActor::UpdateNavBoxDraw()
{
this->FlushBoxDebugLines();
UWorld* CurrWorld = GetWorld();
if (CurrWorld)
{
if (bShowBox)
{
const EActorIteratorFlags Flags = EActorIteratorFlags::SkipPendingKill;
for (TActorIterator<ANavMeshBoundsVolume> ItNavMeshBV(CurrWorld, ANavMeshBoundsVolume::StaticClass(), Flags); ItNavMeshBV; ++ItNavMeshBV)
{
ANavMeshBoundsVolume* pTempNMBV = *ItNavMeshBV;
if (pTempNMBV)
{
const FVector Center = pTempNMBV->GetComponentsBoundingBox().GetCenter();
const FVector Extent = pTempNMBV->GetComponentsBoundingBox().GetSize() / 2.f;
this->DrawDebugBox(CurrWorld, Center, Extent, FColor(0, 255, 0), true, -1.f, 0, 10.f);
}
}
}
}
}
/** 更新导航网格 */
void ANavMeshDrawMgrActor::UpdateNavMeshDraw()
{
this->FlushNavMeshDebugLines();
if (bShowNavMesh)
{
DrawNavMeshTiles();
}
}
/** 更新导航网格边界 */
void ANavMeshDrawMgrActor::UpdateNavMeshEdgesDraw()
{
this->FlushNavMeshEdgesDebugLines();
if (bShowNavMeshEdges)
{
DrawNavMeshEdges();
}
}
/** Draw Navigation Mesh in Runtime (Box) */
bool ANavMeshDrawMgrActor::RtNavMeshDrawBox(bool bInShowBox/* = true*/)
{
bShowBox = bInShowBox;
if (!bInShowBox)
{
this->FlushBoxDebugLines();
}
return true;
}
/** Draw Navigation Mesh in Runtime (Mesh) */
bool ANavMeshDrawMgrActor::RtNavMeshDrawMesh(bool bInShowNavMesh/* = true*/)
{
bShowNavMesh = bInShowNavMesh;
if (!bInShowNavMesh)
{
this->FlushNavMeshDebugLines();
}
return true;
}
/** Draw Navigation Mesh in Runtime (Edges) */
bool ANavMeshDrawMgrActor::RtNavMeshDrawEdges(bool bInShowNavMeshEdges/* = true*/)
{
bShowNavMeshEdges = bInShowNavMeshEdges;
if (!bInShowNavMeshEdges)
{
this->FlushNavMeshEdgesDebugLines();
}
return true;
}
bool ANavMeshDrawMgrActor::RtNavMeshDraw(bool bInShowBox /*= true*/, bool bInShowNavMesh /*= true*/)
{
UWorld* CurrWorld = GetWorld();
if (CurrWorld)
{
#if 0 //WITH_EDITOR
ARecastNavMesh* DefRecastNavMesh = Cast<ARecastNavMesh>(UGameplayStatics::GetActorOfClass(CurrWorld, ARecastNavMesh::StaticClass()));
if (DefRecastNavMesh)
{
DefRecastNavMesh->bDrawFilledPolys = bInDraw;
DefRecastNavMesh->bDrawNavMeshEdges = bInDraw;
//DefRecastNavMesh->UpdateDrawing();
UNavMeshRenderingComponent* NavMeshRenderComp = Cast<UNavMeshRenderingComponent>(DefRecastNavMesh->RenderingComp);
if (NavMeshRenderComp != nullptr && NavMeshRenderComp->GetVisibleFlag() && (NavMeshRenderComp->IsForcingUpdate() || UNavMeshRenderingComponent::IsNavigationShowFlagSet(CurrWorld)))
{
DefRecastNavMesh->RenderingComp->MarkRenderStateDirty();
}
//return true;
}
#endif
RtNavMeshDrawBox(bInShowBox);
RtNavMeshDrawMesh(bInShowNavMesh);
return true;
}
return false;
}
bool ANavMeshDrawMgrActor::RuntimeNavMeshDraw(bool bInShowBox /*= true*/, bool bInShowNavMesh /*= true*/, bool bInShowNavMeshEdges /*= true*/)
{
RtNavMeshDrawBox(bInShowBox);
RtNavMeshDrawMesh(bInShowNavMesh);
RtNavMeshDrawEdges(bInShowNavMeshEdges);
return true;
}
// Called when the game starts or when spawned
void ANavMeshDrawMgrActor::BeginPlay()
{
Super::BeginPlay();
initLineBatcher();
}
// Called every frame
void ANavMeshDrawMgrActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (bShowBox)
{
UpdateNavBoxDraw();
}
if (bShowNavMesh)
{
UpdateNavMeshDraw();
}
if (bShowNavMeshEdges)
{
UpdateNavMeshEdgesDraw();
}
}
void ANavMeshDrawMgrActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
FlushBoxDebugLines();
FlushNavMeshDebugLines();
FlushNavMeshEdgesDebugLines();
clearLineBatcher();
}
#if WITH_RECAST
int32 ANavMeshDrawMgrActor::GetDetailFlags(const ARecastNavMesh* NavMesh)
{
return (NavMesh == nullptr) ? 0 : 0 |
(NavMesh->bDrawTriangleEdges ? (1 << static_cast<int32>(ENavMeshDetailFlags::TriangleEdges)) : 0) |
(NavMesh->bDrawPolyEdges ? (1 << static_cast<int32>(ENavMeshDetailFlags::PolyEdges)) : 0) |
(NavMesh->bDrawFilledPolys ? (1 << static_cast<int32>(ENavMeshDetailFlags::FilledPolys)) : 0) |
(NavMesh->bDrawNavMeshEdges ? (1 << static_cast<int32>(ENavMeshDetailFlags::BoundaryEdges)) : 0) |
(NavMesh->bDrawTileBounds ? (1 << static_cast<int32>(ENavMeshDetailFlags::TileBounds)) : 0) |
(NavMesh->bDrawPathCollidingGeometry ? (1 << static_cast<int32>(ENavMeshDetailFlags::PathCollidingGeometry)) : 0) |
(NavMesh->bDrawTileLabels ? (1 << static_cast<int32>(ENavMeshDetailFlags::TileLabels)) : 0) |
(NavMesh->bDrawPolygonLabels ? (1 << static_cast<int32>(ENavMeshDetailFlags::PolygonLabels)) : 0) |
(NavMesh->bDrawDefaultPolygonCost ? (1 << static_cast<int32>(ENavMeshDetailFlags::PolygonCost)) : 0) |
(NavMesh->bDrawPolygonFlags ? (1 << static_cast<int32>(ENavMeshDetailFlags::PolygonFlags)) : 0) |
(NavMesh->bDrawLabelsOnPathNodes ? (1 << static_cast<int32>(ENavMeshDetailFlags::PathLabels)) : 0) |
(NavMesh->bDrawNavLinks ? (1 << static_cast<int32>(ENavMeshDetailFlags::NavLinks)) : 0) |
(NavMesh->bDrawFailedNavLinks ? (1 << static_cast<int32>(ENavMeshDetailFlags::FailedNavLinks)) : 0) |
(NavMesh->bDrawClusters ? (1 << static_cast<int32>(ENavMeshDetailFlags::Clusters)) : 0) |
(NavMesh->bDrawOctree ? (1 << static_cast<int32>(ENavMeshDetailFlags::NavOctree)) : 0) |
(NavMesh->bDrawOctreeDetails ? (1 << static_cast<int32>(ENavMeshDetailFlags::NavOctreeDetails)) : 0) |
(NavMesh->bDrawMarkedForbiddenPolys ? (1 << static_cast<int32>(ENavMeshDetailFlags::MarkForbiddenPolys)) : 0);
}
#endif // WITH_RECAST
void ANavMeshDrawMgrActor::DrawNavMeshTiles()
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys)
{
#if WITH_RECAST
for (auto NavigationData : NavSys->NavDataSet)
{
ARecastNavMesh* RecastNavMesh = Cast<ARecastNavMesh>(NavigationData);
if (RecastNavMesh)
{
const FNavDataConfig& NavConfig = RecastNavMesh->GetConfig();
TArray<FColor> NavMeshColors;
NavMeshColors.AddDefaulted(RECAST_MAX_AREAS);
for (uint8 Idx = 0; Idx < RECAST_MAX_AREAS; Idx++)
{
NavMeshColors[Idx] = RecastNavMesh->GetAreaIDColor(Idx);
}
NavMeshColors[RECAST_DEFAULT_AREA] = NavConfig.Color.DWColor() > 0 ? NavConfig.Color : FColor(140, 255, 0, 164);
FVector NavMeshDrawOffset = FVector::ZeroVector;
NavMeshDrawOffset.Z = RecastNavMesh->DrawOffset;
//方式2:代理方式
FNavMeshSceneProxyData OutProxyData;
//const int32 DetailFlags = OutProxyData.GetDetailFlags(RecastNavMesh);// UE4.27
const int32 DetailFlags = GetDetailFlags(RecastNavMesh);
TArray<int32> EmptyTileSet;
OutProxyData.GatherData(RecastNavMesh, DetailFlags, EmptyTileSet);
for (FNavMeshSceneProxyData::FDebugMeshData& ItDMeshData : OutProxyData.MeshBuilders)
{
TArray<FVector> ArrayVecTempVerties;
int nVertNum = ItDMeshData.Vertices.Num();
ArrayVecTempVerties.AddDefaulted(nVertNum);
for (int nItVert = 0; nItVert < nVertNum; ++nItVert)
{
//ArrayVecTempVerties[nItVert] = ItDMeshData.Vertices[nItVert].Position;// UE4.27
ArrayVecTempVerties[nItVert].X = ItDMeshData.Vertices[nItVert].Position.X;
ArrayVecTempVerties[nItVert].Y = ItDMeshData.Vertices[nItVert].Position.Y;
ArrayVecTempVerties[nItVert].Z = ItDMeshData.Vertices[nItVert].Position.Z;
}
TArray<int32> ArrayTempIndex;
int nIdxNum = ItDMeshData.Indices.Num();
ArrayTempIndex.AddDefaulted(nIdxNum);
for (int nItIdx = 0; nItIdx < nIdxNum; ++nItIdx)
{
ArrayTempIndex[nItIdx] = ItDMeshData.Indices[nItIdx];
}
this->DrawDebugMesh(GetWorld(), ArrayVecTempVerties, ArrayTempIndex, ItDMeshData.ClusterColor, true, -1.f, 2);
}
#if 0
//方式1:GetDebugGeometry(FRecastDebugGeometry & OutGeometry, int32 TileIndex = INDEX_NONE)
FRecastDebugGeometry OutGeometry;
OutGeometry.bGatherNavMeshEdges = false;
OutGeometry.bGatherPolyEdges = true;// false;
OutGeometry.bMarkForbiddenPolys = false;
RecastNavMesh->GetDebugGeometry(OutGeometry);
//this->DrawDebugMesh(GetWorld(), OutGeometry.MeshVerts, OutGeometry.BuiltMeshIndices, FColor(0, 200, 0), true, -1.f, 0);
//Offset
TArray<FVector> VecMeshVertsWithOffset;
int nVertNum = OutGeometry.MeshVerts.Num();
VecMeshVertsWithOffset.AddDefaulted(nVertNum);
for (int32 VertIdx = 0; VertIdx < nVertNum; ++VertIdx)
{
VecMeshVertsWithOffset[VertIdx] = OutGeometry.MeshVerts[VertIdx] + NavMeshDrawOffset;
}
for (int32 AreaType = 0; AreaType < RECAST_MAX_AREAS; ++AreaType)
{
if (NavMeshColors[AreaType].B>0 || NavMeshColors[AreaType].R> NavMeshColors[AreaType].G)
{
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, FString::Printf(TEXT("Blue Area %d: Color-%s"), AreaType, *NavMeshColors[AreaType].ToString()));
UE_LOG(LogTemp, Log, TEXT("CurrBrushComp invalid!"));
}
}
else
{
this->DrawDebugMesh(GetWorld(), VecMeshVertsWithOffset, OutGeometry.AreaIndices[AreaType], NavMeshColors[AreaType], true, -1.f, 0);
break;
}
}
}
}
#endif
}
}
void ANavMeshDrawMgrActor::DrawNavMeshEdges()
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys)
{
#if WITH_RECAST
for (auto NavigationData : NavSys->NavDataSet)
{
ARecastNavMesh* RecastNavMesh = Cast<ARecastNavMesh>(NavigationData);
if (RecastNavMesh)
{
const FNavDataConfig& NavConfig = RecastNavMesh->GetConfig();
TArray<FColor> NavMeshColors;
NavMeshColors.AddDefaulted(RECAST_MAX_AREAS);
for (uint8 Idx = 0; Idx < RECAST_MAX_AREAS; Idx++)
{
NavMeshColors[Idx] = RecastNavMesh->GetAreaIDColor(Idx);
}
NavMeshColors[RECAST_DEFAULT_AREA] = NavConfig.Color.DWColor() > 0 ? NavConfig.Color : FColor(140, 255, 0, 164);
FVector NavMeshDrawOffset = FVector::ZeroVector;
NavMeshDrawOffset.Z = RecastNavMesh->DrawOffset;
//GetDebugGeometry(FRecastDebugGeometry & OutGeometry, int32 TileIndex = INDEX_NONE)
FRecastDebugGeometry OutGeometry;
OutGeometry.bGatherNavMeshEdges = true;
OutGeometry.bGatherPolyEdges = false;
OutGeometry.bMarkForbiddenPolys = false;
RecastNavMesh->GetDebugGeometry(OutGeometry);
//NavMeshEdges
const uint32 Col = NavMeshColors[RECAST_DEFAULT_AREA].DWColor();
const FColor EdgesColor = FColor(((Col >> 1) & 0x007f7f7f) | (Col & 0xff000000));
const TArray<FVector>& NavMeshEdgeVerts = OutGeometry.NavMeshEdges;
for (int32 Idx = 0; Idx < NavMeshEdgeVerts.Num(); Idx += 2)
{
this->DrawDebugLine(GetWorld(), NavMeshEdgeVerts[Idx] + NavMeshDrawOffset, NavMeshEdgeVerts[Idx + 1] + NavMeshDrawOffset, /*EdgesColor*/ FColor(0, 0, 255, 255), true, -1.f, 4);
}
}
}
#endif
}
}
4. 模块化
.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FNavTestModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "NavTest.h"
#include "Modules/ModuleManager.h"
#define LOCTEXT_NAMESPACE "FNavTestModule"
void FNavTestModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FNavTestModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FNavTestModule, NavTest)
5.导航测试
.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "NavBlueprintFunctionLibrary.generated.h"
class UNavigationPath;
/**
*
*/
UCLASS()
class NAVTEST_API UNavBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/** 获取指定名称的World; */
/*@Param InStrWorldName World Name
*@Return UWorld* World Pointer(if World Name is Empty return the first vaild world.)
*/
static UWorld* GetWorldByName(FString InStrWorldName=TEXT(""));
// Function to find the Nearest Point of Navigation
//@param InStartPoint Start Point
//@param InEndPoint End Point(Preferably a navigable point)
//@param fNavigationOffset Navigation offset
UFUNCTION(BlueprintCallable, Category = "RuntimeNavMesh|BPL")
static FVector FindNearestNavPoint(const FVector& InStartPoint, const FVector& InEndPoint, float fNavigationOffset=100.0, FString InStrWorldName=TEXT(""));
// Get the landscape Height of the special Location
//@param InVecLoc Location
//@param InStrWorldName World Name
//@return float Height Value
UFUNCTION(BlueprintCallable)
static float GetLandscapeHeight(const FVector& InVecLoc, FString InStrWorldName = TEXT(""));
// Get the landscape Max/Min Height of the special World(or First World)
//@param OutfMinHeight (Out) Minimum Height
//@param OutfMaxHeight (Out) Maximum Height
//@return bool True-Success;false-Fail.
UFUNCTION(BlueprintCallable)
static bool GetLandscapeMinMaxHeight(float& OutfMinHeight, float& OutfMaxHeight, FString InStrWorldName = TEXT(""), float fExtendScale=1.f);
private:
static UWorld* GetFirstWorld();
/** 查找导航路径 */
static UNavigationPath* findNavigationPath(const FVector& InStartPoint, const FVector& InEndPoint, FString InStrWorldName=TEXT(""));
};
.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "NavBlueprintFunctionLibrary.h"
#include "Engine/Engine.h"
#include "NavigationPath.h"
#include "NavigationSystem.h"
#include "NavigationData.h"
#include "NavFilters/NavigationQueryFilter.h"
#include "Landscape.h"
#include "EngineUtils.h"
#include "LandscapeInfo.h"
#include "LandscapeHeightfieldCollisionComponent.h"
#include "GeneratedCodeHelpers.h"
UWorld* UNavBlueprintFunctionLibrary::GetFirstWorld()
{
//Get first valid world
for (const FWorldContext& Context : GEngine->GetWorldContexts())
{
#if WITH_EDITOR
if (GIsEditor)
{
if (Context.WorldType == EWorldType::Editor)
{
return Context.World();
}
}
else
{
if (Context.World() != nullptr)
{
return Context.World();
}
}
#else
if (Context.World() != nullptr)
{
return Context.World();
}
#endif
}
return nullptr;
}
UWorld* UNavBlueprintFunctionLibrary::GetWorldByName(FString InStrWorldName/*=TEXT("")*/)
{
if (InStrWorldName.IsEmpty())
{
return GetFirstWorld();
}
for (const FWorldContext& Context : GEngine->GetWorldContexts())
{
#if WITH_EDITOR
if (GIsEditor)
{
if (Context.WorldType == EWorldType::Editor)
{
if (Context.World()->GetName() == InStrWorldName)
{
return Context.World();
}
}
}
else
{
if (Context.World() != nullptr)
{
if (Context.World()->GetName() == InStrWorldName)
{
return Context.World();
}
}
}
#else
if (Context.World() != nullptr)
{
if (Context.World()->GetName() == InStrWorldName)
{
return Context.World();
}
}
#endif
}
return nullptr;
}
FVector UNavBlueprintFunctionLibrary::FindNearestNavPoint(const FVector& InStartPoint, const FVector& InEndPoint, float fNavigationOffset/*=100.0*/, FString InStrWorldName/*=TEXT("")*/)
{
UE_LOG(LogTemp, Warning, TEXT("Start:%s; End:%s; Length=%f"), *InStartPoint.ToString(), *InEndPoint.ToString(), (InStartPoint - InEndPoint).Size());
if ((InStartPoint - InEndPoint).Size() <= fNavigationOffset)
{
return InEndPoint;
}
UWorld* pTheWorld = GetWorldByName(InStrWorldName);
if (!pTheWorld)
{
return InEndPoint;
}
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(pTheWorld);
if (NavSys)
{
FNavLocation OutNavLocation;
ANavigationData* UseNavData = NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
bool bResult = false;
FVector QueryExtent = FVector::ZeroVector;
bResult = NavSys->ProjectPointToNavigation(InEndPoint, OutNavLocation, QueryExtent, nullptr);
//终点导航不可达
if (!bResult)
{
return InEndPoint;
}
}
}
UNavigationPath* pNavPath = findNavigationPath(InStartPoint, InEndPoint);
if (pNavPath)
{
if (pNavPath->GetPathLength() <= 0.1)
{
FVector HalfStartPoint = (InEndPoint + InStartPoint) / 2;
FVector VecNavigationableP = FindNearestNavPoint(HalfStartPoint, InEndPoint, fNavigationOffset);
if ((VecNavigationableP - HalfStartPoint).Size() <= fNavigationOffset)
{
return VecNavigationableP;
}
else
{
return FindNearestNavPoint(HalfStartPoint, VecNavigationableP, fNavigationOffset);
}
}
return InStartPoint;
}
return InEndPoint;
}
UNavigationPath* UNavBlueprintFunctionLibrary::findNavigationPath(const FVector& InStartPoint, const FVector& InEndPoint, FString InStrWorldName)
{
UNavigationPath* ResultPath = NULL;
UWorld* pTheWorld = GetWorldByName(InStrWorldName);
if (pTheWorld)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(pTheWorld);
if (NavSys)
{
ResultPath = NewObject<UNavigationPath>(NavSys);
const ANavigationData* NavigationData = NULL;
// just use default
NavigationData = NavSys->GetDefaultNavDataInstance();
check(NavigationData);
TSubclassOf<UNavigationQueryFilter> FilterClass;
const FPathFindingQuery Query(nullptr, *NavigationData, InStartPoint, InEndPoint, UNavigationQueryFilter::GetQueryFilter(*NavigationData, nullptr, FilterClass));
const FPathFindingResult Result = NavSys->FindPathSync(Query, EPathFindingMode::Regular);
if (Result.IsSuccessful() && ResultPath)
{
ResultPath->SetPath(Result.Path);
}
}
}
return ResultPath;
}
float UNavBlueprintFunctionLibrary::GetLandscapeHeight(const FVector& InVecLoc, FString InStrWorldName)
{
UWorld* pTheWorld = GetWorldByName(InStrWorldName);
if (pTheWorld)
{
// 获取Landscape的Actor
ALandscape* Landscape = nullptr;
const EActorIteratorFlags Flags = EActorIteratorFlags::SkipPendingKill;
for (TActorIterator<ALandscape> ItLs(pTheWorld, ALandscape::StaticClass(), Flags); ItLs; ++ItLs)
{
Landscape = *ItLs;
if (Landscape)
{
TOptional<float> fOpHeight = Landscape->GetHeightAtLocation(InVecLoc);
if (fOpHeight.IsSet())
{
return fOpHeight.GetValue();
}
}
}
}
return 0.0f;
}
bool UNavBlueprintFunctionLibrary::GetLandscapeMinMaxHeight(float& OutfMinHeight, float& OutfMaxHeight, FString InStrWorldName, float fExtendScale)
{
UWorld* pTheWorld = GetWorldByName(InStrWorldName);
if (pTheWorld)
{
float MinHeight = TNumericLimits<float>::Max();
float MaxHeight = TNumericLimits<float>::Min();
// 获取Landscape的Actor
ALandscape* Landscape = nullptr;
const EActorIteratorFlags Flags = EActorIteratorFlags::SkipPendingKill;
for (TActorIterator<ALandscape> ItLs(pTheWorld, ALandscape::StaticClass(), Flags); ItLs; ++ItLs)
{
Landscape = *ItLs;
if (Landscape)
{
#if WITH_CHAOS
int32 nSizeX = 0;
int32 nSizeY = 0;
TArray<float> ArrayValues;
Landscape->GetHeightValues(nSizeX, nSizeY, ArrayValues);
for (float ItHeight : ArrayValues)
{
if (ItHeight < MinHeight)
{
MinHeight = ItHeight;
}
if (ItHeight > MaxHeight)
{
MaxHeight = ItHeight;
}
}
#else
FVector VecBase = Landscape->GetActorLocation();
FVector VecScale3D = Landscape->GetActorScale3D();
FVector Origin; FVector BoxExtent;
Landscape->GetActorBounds(false, Origin, BoxExtent);
FVector VecResolution = (BoxExtent - Origin) * 2;
float fInterval = (float)Landscape->ComponentSizeQuads;
for (int nItSampleX = 0; nItSampleX < Landscape->ComponentSizeQuads; ++nItSampleX)
{
for (int nItSampleY = 0; nItSampleY < Landscape->ComponentSizeQuads; ++nItSampleY)
{
FVector VecAdder = VecResolution * FVector(nItSampleX / fInterval, nItSampleY/ fInterval, 0.f) * fExtendScale;
TOptional<float> Height = Landscape->GetHeightAtLocation(VecBase + VecAdder);
if (Height.IsSet())
{
if (Height.GetValue() < MinHeight)
{
MinHeight = Height.GetValue();
}
if (Height.GetValue() > MaxHeight)
{
MaxHeight = Height.GetValue();
}
}
}
}
#endif
}
}
OutfMinHeight = MinHeight;
OutfMaxHeight = MaxHeight;
return true;
}
return false;
}