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 "NavMesh/RecastNavMesh.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/GameViewportClient.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
|
||||
namespace PS_Editor_NavUtils
|
||||
{
|
||||
@@ -17,6 +20,15 @@ namespace PS_Editor_NavUtils
|
||||
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)
|
||||
{
|
||||
if (!World) return;
|
||||
@@ -46,6 +58,22 @@ namespace PS_Editor_NavUtils
|
||||
{
|
||||
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;
|
||||
UGameplayStatics::GetAllActorsOfClass(World, ANavigationData::StaticClass(), NavMeshActors);
|
||||
for (AActor* A : NavMeshActors)
|
||||
@@ -54,7 +82,116 @@ namespace PS_Editor_NavUtils
|
||||
{
|
||||
FNavDataAccessor* Accessor = static_cast<FNavDataAccessor*>(NavData);
|
||||
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.
|
||||
*/
|
||||
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