Runtime nav settings + custom debug viz (P key) + editable restore on stop
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,10 @@
|
|||||||
#include "NavigationData.h"
|
#include "NavigationData.h"
|
||||||
#include "NavMesh/RecastNavMesh.h"
|
#include "NavMesh/RecastNavMesh.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
|
#include "Engine/GameViewportClient.h"
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
#include "Kismet/GameplayStatics.h"
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
#include "DrawDebugHelpers.h"
|
||||||
|
|
||||||
namespace PS_Editor_NavUtils
|
namespace PS_Editor_NavUtils
|
||||||
{
|
{
|
||||||
@@ -17,6 +20,15 @@ namespace PS_Editor_NavUtils
|
|||||||
using ANavigationData::bEnableDrawing;
|
using ANavigationData::bEnableDrawing;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Recast-specific draw flags exposer.
|
||||||
|
struct FRecastAccessor : public ARecastNavMesh
|
||||||
|
{
|
||||||
|
using ARecastNavMesh::bDrawTriangleEdges;
|
||||||
|
using ARecastNavMesh::bDrawPolyEdges;
|
||||||
|
using ARecastNavMesh::bDrawNavMeshEdges;
|
||||||
|
using ARecastNavMesh::bDrawTileBounds;
|
||||||
|
};
|
||||||
|
|
||||||
void SetRuntimeGenerationMode(UWorld* World, ERuntimeGenerationType Mode)
|
void SetRuntimeGenerationMode(UWorld* World, ERuntimeGenerationType Mode)
|
||||||
{
|
{
|
||||||
if (!World) return;
|
if (!World) return;
|
||||||
@@ -46,6 +58,22 @@ namespace PS_Editor_NavUtils
|
|||||||
{
|
{
|
||||||
if (!World) return;
|
if (!World) return;
|
||||||
|
|
||||||
|
// PIE/runtime nav visualization in UE has multiple gates that all need to align:
|
||||||
|
//
|
||||||
|
// 1. ANavigationData::bEnableDrawing — controls whether the rendering
|
||||||
|
// component creates a scene proxy at all.
|
||||||
|
// 2. ARecastNavMesh::bDrawNavMeshEdges (+ bDrawPolyEdges, bDrawTileBounds, etc.)
|
||||||
|
// — control what the proxy actually paints. With bEnableDrawing=true but every
|
||||||
|
// bDraw* flag false, the proxy is created but draws nothing.
|
||||||
|
// 3. GameViewportClient::EngineShowFlags.Navigation — the show-flag the
|
||||||
|
// `show navigation` console command toggles. The proxy is only rendered
|
||||||
|
// when this flag is on.
|
||||||
|
// 4. ReregisterAllComponents — if bEnableDrawing was false at construction
|
||||||
|
// time, the rendering component never created its proxy. Toggling
|
||||||
|
// bEnableDrawing alone won't recreate it; we have to re-register the
|
||||||
|
// components so the proxy is rebuilt with the new flag value.
|
||||||
|
//
|
||||||
|
// All four are required for runtime visualization to actually appear.
|
||||||
TArray<AActor*> NavMeshActors;
|
TArray<AActor*> NavMeshActors;
|
||||||
UGameplayStatics::GetAllActorsOfClass(World, ANavigationData::StaticClass(), NavMeshActors);
|
UGameplayStatics::GetAllActorsOfClass(World, ANavigationData::StaticClass(), NavMeshActors);
|
||||||
for (AActor* A : NavMeshActors)
|
for (AActor* A : NavMeshActors)
|
||||||
@@ -54,7 +82,116 @@ namespace PS_Editor_NavUtils
|
|||||||
{
|
{
|
||||||
FNavDataAccessor* Accessor = static_cast<FNavDataAccessor*>(NavData);
|
FNavDataAccessor* Accessor = static_cast<FNavDataAccessor*>(NavData);
|
||||||
Accessor->bEnableDrawing = bVisible;
|
Accessor->bEnableDrawing = bVisible;
|
||||||
NavData->MarkComponentsRenderStateDirty();
|
|
||||||
|
// Recast-specific: enable enough draw flags that the proxy actually paints
|
||||||
|
// something visible. Edges only — tile bounds are noisy.
|
||||||
|
if (ARecastNavMesh* Recast = Cast<ARecastNavMesh>(NavData))
|
||||||
|
{
|
||||||
|
FRecastAccessor* RA = static_cast<FRecastAccessor*>(Recast);
|
||||||
|
RA->bDrawNavMeshEdges = bVisible;
|
||||||
|
RA->bDrawPolyEdges = bVisible;
|
||||||
|
RA->bDrawTriangleEdges = bVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force proxy recreation. MarkComponentsRenderStateDirty alone is not
|
||||||
|
// enough when the proxy was never created (bEnableDrawing=false at init).
|
||||||
|
NavData->ReregisterAllComponents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UGameViewportClient* GVC = World->GetGameViewport())
|
||||||
|
{
|
||||||
|
GVC->EngineShowFlags.SetNavigation(bVisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Custom navmesh visualization (works without engine modifications) ----
|
||||||
|
//
|
||||||
|
// Cache the geometry between frames to avoid the expensive GetDebugGeometry call every
|
||||||
|
// tick. We refresh it on a small interval (default 0.5s) — enough for visual feedback
|
||||||
|
// during dynamic nav rebuilds without burning CPU on static maps.
|
||||||
|
struct FCachedNavMeshGeo
|
||||||
|
{
|
||||||
|
TArray<FVector> Vertices;
|
||||||
|
TArray<int32> TriIndices; // groups of 3 — A B C
|
||||||
|
};
|
||||||
|
static TArray<FCachedNavMeshGeo> GCachedNavMeshes;
|
||||||
|
static float GTimeSinceRefresh = 9999.f; // force initial refresh
|
||||||
|
|
||||||
|
void ClearNavMeshDebugCache()
|
||||||
|
{
|
||||||
|
GCachedNavMeshes.Empty();
|
||||||
|
GTimeSinceRefresh = 9999.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawNavMeshDebug(UWorld* World, float DeltaTime, float RefreshIntervalSeconds)
|
||||||
|
{
|
||||||
|
if (!World) return;
|
||||||
|
|
||||||
|
GTimeSinceRefresh += DeltaTime;
|
||||||
|
if (GTimeSinceRefresh >= RefreshIntervalSeconds || GCachedNavMeshes.Num() == 0)
|
||||||
|
{
|
||||||
|
GTimeSinceRefresh = 0.f;
|
||||||
|
GCachedNavMeshes.Empty();
|
||||||
|
|
||||||
|
TArray<AActor*> NavMeshActors;
|
||||||
|
UGameplayStatics::GetAllActorsOfClass(World, ARecastNavMesh::StaticClass(), NavMeshActors);
|
||||||
|
for (AActor* A : NavMeshActors)
|
||||||
|
{
|
||||||
|
if (ARecastNavMesh* Recast = Cast<ARecastNavMesh>(A))
|
||||||
|
{
|
||||||
|
Recast->BeginBatchQuery();
|
||||||
|
|
||||||
|
FCachedNavMeshGeo Cached;
|
||||||
|
|
||||||
|
// Iterate every tile and pull its debug geometry. UE 5.5 dropped the
|
||||||
|
// single-call GetDebugGeometry; only the per-tile variant remains.
|
||||||
|
const int32 NumTiles = Recast->GetNavMeshTilesCount();
|
||||||
|
for (int32 TileIdx = 0; TileIdx < NumTiles; ++TileIdx)
|
||||||
|
{
|
||||||
|
FRecastDebugGeometry Geo;
|
||||||
|
Recast->GetDebugGeometryForTile(Geo, TileIdx);
|
||||||
|
|
||||||
|
const int32 BaseIdx = Cached.Vertices.Num();
|
||||||
|
Cached.Vertices.Append(Geo.MeshVerts);
|
||||||
|
for (int32 AreaIdx = 0; AreaIdx < RECAST_MAX_AREAS; ++AreaIdx)
|
||||||
|
{
|
||||||
|
const TArray<int32>& AreaIndices = Geo.AreaIndices[AreaIdx];
|
||||||
|
Cached.TriIndices.Reserve(Cached.TriIndices.Num() + AreaIndices.Num());
|
||||||
|
for (int32 Idx : AreaIndices)
|
||||||
|
{
|
||||||
|
Cached.TriIndices.Add(BaseIdx + Idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Recast->FinishBatchQuery();
|
||||||
|
GCachedNavMeshes.Add(MoveTemp(Cached));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-frame draw of cached geometry. Lifetime=0 + bPersistent=false means each call
|
||||||
|
// only paints for one frame, so we get clean redraw every tick.
|
||||||
|
const FColor EdgeColor(40, 220, 90); // green
|
||||||
|
for (const FCachedNavMeshGeo& Cached : GCachedNavMeshes)
|
||||||
|
{
|
||||||
|
const int32 NumTris = Cached.TriIndices.Num() / 3;
|
||||||
|
for (int32 t = 0; t < NumTris; ++t)
|
||||||
|
{
|
||||||
|
const int32 I0 = Cached.TriIndices[t * 3 + 0];
|
||||||
|
const int32 I1 = Cached.TriIndices[t * 3 + 1];
|
||||||
|
const int32 I2 = Cached.TriIndices[t * 3 + 2];
|
||||||
|
if (!Cached.Vertices.IsValidIndex(I0) || !Cached.Vertices.IsValidIndex(I1) || !Cached.Vertices.IsValidIndex(I2))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const FVector& V0 = Cached.Vertices[I0];
|
||||||
|
const FVector& V1 = Cached.Vertices[I1];
|
||||||
|
const FVector& V2 = Cached.Vertices[I2];
|
||||||
|
DrawDebugLine(World, V0, V1, EdgeColor, /*bPersistent=*/ false, /*LifeTime=*/ 0.f, /*DepthPriority=*/ 0, /*Thickness=*/ 1.0f);
|
||||||
|
DrawDebugLine(World, V1, V2, EdgeColor, false, 0.f, 0, 1.0f);
|
||||||
|
DrawDebugLine(World, V2, V0, EdgeColor, false, 0.f, 0, 1.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,4 +34,22 @@ namespace PS_Editor_NavUtils
|
|||||||
* be stripped depending on build flags.
|
* be stripped depending on build flags.
|
||||||
*/
|
*/
|
||||||
PS_EDITOR_API void SetNavMeshVisible(UWorld* World, bool bVisible);
|
PS_EDITOR_API void SetNavMeshVisible(UWorld* World, bool bVisible);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the navmesh as green debug lines for the current frame. Bypasses UE's own nav
|
||||||
|
* debug renderer (which is gated behind UE_ALLOW_NAVMESH_DEBUG_DRAWING_IN_GAME and
|
||||||
|
* doesn't work on binary engine installs at runtime). Pulls the geometry from each
|
||||||
|
* ARecastNavMesh and emits DrawDebugLine calls — works in PIE / Standalone / packaged
|
||||||
|
* shipping alike.
|
||||||
|
*
|
||||||
|
* Internally caches the FRecastDebugGeometry between calls and only refreshes it when
|
||||||
|
* RefreshIntervalSeconds elapses, since GetDebugGeometry is expensive on big navmeshes.
|
||||||
|
*
|
||||||
|
* Call from a per-tick code path while the user wants the visualization shown; stop
|
||||||
|
* calling when they hide it (DrawDebugLine entries auto-expire after 1 frame).
|
||||||
|
*/
|
||||||
|
PS_EDITOR_API void DrawNavMeshDebug(UWorld* World, float DeltaTime, float RefreshIntervalSeconds = 0.5f);
|
||||||
|
|
||||||
|
/** Drop the cached debug geometry (call when leaving editor mode / unloading scene). */
|
||||||
|
PS_EDITOR_API void ClearNavMeshDebugCache();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user