Per-scenario dynamic nav opt-in + Settings popup + timeline restore fixes
Dynamic navigation toggle (per scenario):
- New PS_Editor_NavUtils helpers wrap UNavigationSystemV1 / RecastNavMesh
protected APIs (RuntimeGeneration field + bEnableDrawing) via the C++
using-trick so we can switch the level's nav mode and toggle visualization
without engine modifications.
- FPS_Editor_SceneData gets bUseDynamicNavigation persisted in JSON.
- SpawnManager carries the live state plus a session-only bShowNavMesh
preference (never saved).
- SceneSerializer reads/writes the flag and applies it on load (editor mode).
- SceneLoader applies it on the gameplay path before spawning actors so
scenarios that need runtime nav rebuild get it both in editor and at
runtime via the project's GameMode + SceneLoader::LoadScene flow.
UI:
- New "Settings" toolbar button in both Legacy and UMG widgets opens a popup
with two checkboxes:
* Use Dynamic Navigation - saved per scenario, live toggle
* Show NavMesh (P) - session only, mimics editor's P key (green nav
visualization)
- Pawn binds P to IA_ToggleNavMesh -> SpawnManager.bShowNavMesh, in sync
with the popup checkbox.
Timeline restore correctness:
- AddOrUpdateKeyAtCurrentTime, RemoveKey, SetKeyTime, SetKeyInterpolate now
invalidate LastAppliedValues[TrackIndex] so a key edit isn't masked by a
stale per-track cache entry from a previous apply.
- RestoreBaseline now prefers a key at t=0 (within epsilon) over the
captured pre-animation baseline. Updating a t=0 key + Stop now restores
the new value instead of snapping back to the original. Tracks without a
t=0 key still fall back to the pre-animation baseline.
Restart simulation rewind:
- Restart() used to call Stop() + Play() but Play immediately cancelled the
pending RequestStopSimulation timer set by Stop, so transforms were never
restored - characters stayed where they were.
- RequestStopSimulation now takes bImmediate; when true, OnEditorBeforeSimulateStop
still fires (so BP behaviour-reset hooks run) but StopSimulation runs
synchronously, restoring transforms before Play() recaptures them.
- Restart inlines the reset (RestoreBaseline + immediate stop + Play) so
rewinding actually rewinds.
Build:
- Re-add NavigationSystem to PS_Editor.Build.cs (needed for ARecastNavMesh
+ ANavigationData access in NavUtils).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "PS_Editor_SelectionManager.h"
|
||||
#include "PS_Editor_GroundSnap.h"
|
||||
#include "PS_Editor_NavUtils.h"
|
||||
#include "PS_Editor_SpawnManager.h"
|
||||
#include "PS_Editor_PlayerController.h"
|
||||
#include "PS_Editor_Gizmo.h"
|
||||
#include "PS_Editor_UndoManager.h"
|
||||
@@ -254,6 +256,11 @@ void APS_Editor_Pawn::CreateInputActions()
|
||||
IA_Focus = NewObject<UInputAction>(this, TEXT("IA_Focus"));
|
||||
IA_Focus->ValueType = EInputActionValueType::Boolean;
|
||||
EditorMappingContext->MapKey(IA_Focus, EKeys::F);
|
||||
|
||||
// Toggle navmesh visualization (mimics UE editor's P key)
|
||||
IA_ToggleNavMesh = NewObject<UInputAction>(this, TEXT("IA_ToggleNavMesh"));
|
||||
IA_ToggleNavMesh->ValueType = EInputActionValueType::Boolean;
|
||||
EditorMappingContext->MapKey(IA_ToggleNavMesh, EKeys::P);
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||
@@ -301,6 +308,7 @@ void APS_Editor_Pawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComp
|
||||
EIC->BindAction(IA_Confirm, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleConfirm);
|
||||
EIC->BindAction(IA_Cancel, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleCancel);
|
||||
EIC->BindAction(IA_Focus, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleFocus);
|
||||
EIC->BindAction(IA_ToggleNavMesh, ETriggerEvent::Started, this, &APS_Editor_Pawn::HandleToggleNavMesh);
|
||||
}
|
||||
|
||||
// ---- Input Handlers ----
|
||||
@@ -1460,6 +1468,20 @@ void APS_Editor_Pawn::HandleFocus(const FInputActionValue& Value)
|
||||
OrbitDistance = TargetDistance;
|
||||
}
|
||||
|
||||
void APS_Editor_Pawn::HandleToggleNavMesh(const FInputActionValue& Value)
|
||||
{
|
||||
// Mimics UE editor's P key — toggles the runtime navmesh visualization. We route
|
||||
// through SpawnManager's session-only bShowNavMesh flag so the Settings popup
|
||||
// checkbox stays in sync.
|
||||
APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetController());
|
||||
if (!EditorPC) return;
|
||||
UPS_Editor_SpawnManager* SM = EditorPC->GetSpawnManager();
|
||||
if (!SM) return;
|
||||
|
||||
SM->bShowNavMesh = !SM->bShowNavMesh;
|
||||
PS_Editor_NavUtils::SetNavMeshVisible(GetWorld(), SM->bShowNavMesh);
|
||||
}
|
||||
|
||||
// ---- Helpers ----
|
||||
|
||||
void APS_Editor_Pawn::SetCameraMode(EPS_Editor_CameraMode NewMode)
|
||||
|
||||
@@ -212,6 +212,9 @@ private:
|
||||
UPROPERTY()
|
||||
TObjectPtr<UInputAction> IA_Focus;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UInputAction> IA_ToggleNavMesh;
|
||||
|
||||
// ---- Spline point / ChildMovable drag state ----
|
||||
TWeakObjectPtr<AActor> DraggedSplineActor;
|
||||
TArray<FVector> SplinePointDragInitialState;
|
||||
@@ -260,6 +263,7 @@ private:
|
||||
void HandleConfirm(const FInputActionValue& Value);
|
||||
void HandleCancel(const FInputActionValue& Value);
|
||||
void HandleFocus(const FInputActionValue& Value);
|
||||
void HandleToggleNavMesh(const FInputActionValue& Value);
|
||||
|
||||
// ---- Focus animation state (F key, frame selection) ----
|
||||
bool bIsFocusing = false;
|
||||
|
||||
@@ -389,18 +389,23 @@ void APS_Editor_PlayerController::SetSimulationPaused(bool bPaused)
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::RequestStopSimulation()
|
||||
void APS_Editor_PlayerController::RequestStopSimulation(bool bImmediate)
|
||||
{
|
||||
if (!bSimulating) return;
|
||||
|
||||
// If a request is already scheduled, do nothing (debounce double-clicks on Stop).
|
||||
// Skip the debounce when bImmediate (Restart wants to flush right now).
|
||||
if (!bImmediate)
|
||||
{
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
if (PendingStopSimulationHandle.IsValid()) return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fire OnEditorBeforeSimulateStop immediately on every actor so BP handlers can react
|
||||
// while the BT is still live. Then schedule the real cut for ~500ms later.
|
||||
// while the BT is still live. Then schedule the real cut (deferred) or run it now
|
||||
// (immediate, used by timeline Restart).
|
||||
if (SpawnManager)
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SpawnManager->GetSpawnedActors())
|
||||
@@ -413,6 +418,21 @@ void APS_Editor_PlayerController::RequestStopSimulation()
|
||||
}
|
||||
}
|
||||
|
||||
if (bImmediate)
|
||||
{
|
||||
// Cancel any pending deferred stop so it doesn't fire again later.
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
if (PendingStopSimulationHandle.IsValid())
|
||||
{
|
||||
World->GetTimerManager().ClearTimer(PendingStopSimulationHandle);
|
||||
PendingStopSimulationHandle.Invalidate();
|
||||
}
|
||||
}
|
||||
StopSimulation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
TWeakObjectPtr<APS_Editor_PlayerController> WeakThis(this);
|
||||
|
||||
@@ -76,9 +76,14 @@ public:
|
||||
/** Request a deferred stop: fires OnEditorBeforeSimulateStop immediately, then schedules
|
||||
* the real StopSimulation after a grace period (500ms by default) so BT has time to
|
||||
* react to whatever the BP handler does in OnEditorBeforeSimulateStop.
|
||||
* Timeline Stop and the Simulate toolbar button both go through this. */
|
||||
* Timeline Stop and the Simulate toolbar button both go through this.
|
||||
*
|
||||
* @param bImmediate If true, fire OnEditorBeforeSimulateStop and run StopSimulation
|
||||
* synchronously in the same call (no grace period). Used by
|
||||
* timeline Restart, where we want transforms restored before the
|
||||
* subsequent Play(). Defaults to false (deferred). */
|
||||
UFUNCTION(BlueprintCallable, Category = "PS_Editor")
|
||||
void RequestStopSimulation();
|
||||
void RequestStopSimulation(bool bImmediate = false);
|
||||
|
||||
/** True while simulation is running. */
|
||||
UFUNCTION(BlueprintCallable, Category = "PS_Editor")
|
||||
|
||||
@@ -33,7 +33,8 @@ public class PS_Editor : ModuleRules
|
||||
"JsonUtilities",
|
||||
"ProceduralMeshComponent",
|
||||
"DesktopPlatform",
|
||||
"AIModule"
|
||||
"AIModule",
|
||||
"NavigationSystem"
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[]
|
||||
|
||||
@@ -61,4 +61,11 @@ struct FPS_Editor_SceneData
|
||||
/** Global timeline data (animation tracks + duration). Empty on legacy scenes. */
|
||||
UPROPERTY()
|
||||
FPS_Editor_TimelineData Timeline;
|
||||
|
||||
/** If true, the level's nav mesh is switched to Dynamic runtime generation when this
|
||||
* scenario loads — both in the runtime editor (so spawn / move actors update nav live)
|
||||
* and in gameplay (for scenarios that need runtime-built navigation). False keeps the
|
||||
* cooked Static nav, which has zero runtime cost. Default: false. */
|
||||
UPROPERTY()
|
||||
bool bUseDynamicNavigation = false;
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "PS_Editor_TimelineSubsystem.h"
|
||||
#include "PS_Editor_SpawnManager.h"
|
||||
#include "PS_Editor_PlayerController.h"
|
||||
#include "PS_Editor_NavUtils.h"
|
||||
#include "AIController.h"
|
||||
#include "BrainComponent.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
@@ -130,6 +131,12 @@ bool UPS_Editor_SceneLoader::LoadSceneFromData(UWorld* World, const FPS_Editor_S
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply the scenario's dynamic-navigation flag BEFORE spawning actors. If the scenario
|
||||
// needs runtime nav generation, switching the level's RecastNavMesh to Dynamic now
|
||||
// means the actors we're about to spawn will dirty + rebuild tiles around them. Cooked
|
||||
// Static nav is preserved on disk for scenarios that don't need this.
|
||||
PS_Editor_NavUtils::SetDynamicNavigationEnabled(World, SceneData.bUseDynamicNavigation);
|
||||
|
||||
UPS_Editor_TimelineSubsystem* TimelineSS = World->GetSubsystem<UPS_Editor_TimelineSubsystem>();
|
||||
|
||||
int32 SpawnedCount = 0;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "PS_Editor_TimelineSubsystem.h"
|
||||
#include "PS_Editor_NavUtils.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
@@ -166,6 +167,12 @@ bool UPS_Editor_SceneSerializer::SaveScene(const FString& SceneName, UPS_Editor_
|
||||
SceneData.Timeline = TimelineSS->GetData();
|
||||
}
|
||||
|
||||
// Per-scenario settings — read from the SpawnManager's session state.
|
||||
if (SpawnManager)
|
||||
{
|
||||
SceneData.bUseDynamicNavigation = SpawnManager->bUseDynamicNavigation;
|
||||
}
|
||||
|
||||
// Convert to JSON
|
||||
FString JsonString;
|
||||
if (!FJsonObjectConverter::UStructToJsonObjectString(SceneData, JsonString, 0, 0, 0, nullptr, true))
|
||||
@@ -438,6 +445,17 @@ bool UPS_Editor_SceneSerializer::LoadScene(const FString& SceneName, UPS_Editor_
|
||||
}
|
||||
}
|
||||
|
||||
// Per-scenario settings — push into the SpawnManager's session state and apply
|
||||
// the dynamic-navigation switch to the level's RecastNavMesh actors right away.
|
||||
if (SpawnManager)
|
||||
{
|
||||
SpawnManager->bUseDynamicNavigation = SceneData.bUseDynamicNavigation;
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetOuter()))
|
||||
{
|
||||
PS_Editor_NavUtils::SetDynamicNavigationEnabled(EditorPC->GetWorld(), SpawnManager->bUseDynamicNavigation);
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Scene loaded: %s (%d actors)"), *SceneName, SpawnedCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "PS_Editor_NavUtils.h"
|
||||
#include "NavigationSystem.h"
|
||||
#include "NavigationData.h"
|
||||
#include "NavMesh/RecastNavMesh.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
namespace PS_Editor_NavUtils
|
||||
{
|
||||
// ANavigationData::RuntimeGeneration and bEnableDrawing are both protected. Local
|
||||
// using-trick subclass to re-publish them — the C++ access check uses the static
|
||||
// type, so a static_cast through this exposing class lets us read/write them
|
||||
// without modifying the engine.
|
||||
struct FNavDataAccessor : public ANavigationData
|
||||
{
|
||||
using ANavigationData::RuntimeGeneration;
|
||||
using ANavigationData::bEnableDrawing;
|
||||
};
|
||||
|
||||
void SetRuntimeGenerationMode(UWorld* World, ERuntimeGenerationType Mode)
|
||||
{
|
||||
if (!World) return;
|
||||
|
||||
TArray<AActor*> NavMeshActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(World, ANavigationData::StaticClass(), NavMeshActors);
|
||||
for (AActor* A : NavMeshActors)
|
||||
{
|
||||
if (ANavigationData* NavData = Cast<ANavigationData>(A))
|
||||
{
|
||||
FNavDataAccessor* Accessor = static_cast<FNavDataAccessor*>(NavData);
|
||||
if (Accessor->RuntimeGeneration != Mode)
|
||||
{
|
||||
Accessor->RuntimeGeneration = Mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetDynamicNavigationEnabled(UWorld* World, bool bDynamic)
|
||||
{
|
||||
SetRuntimeGenerationMode(World,
|
||||
bDynamic ? ERuntimeGenerationType::Dynamic : ERuntimeGenerationType::Static);
|
||||
}
|
||||
|
||||
void SetNavMeshVisible(UWorld* World, bool bVisible)
|
||||
{
|
||||
if (!World) return;
|
||||
|
||||
TArray<AActor*> NavMeshActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(World, ANavigationData::StaticClass(), NavMeshActors);
|
||||
for (AActor* A : NavMeshActors)
|
||||
{
|
||||
if (ANavigationData* NavData = Cast<ANavigationData>(A))
|
||||
{
|
||||
FNavDataAccessor* Accessor = static_cast<FNavDataAccessor*>(NavData);
|
||||
Accessor->bEnableDrawing = bVisible;
|
||||
NavData->MarkComponentsRenderStateDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
class UWorld;
|
||||
enum class ERuntimeGenerationType : uint8;
|
||||
|
||||
/**
|
||||
* Centralised helpers for switching the level's RecastNavMesh runtime-generation mode and
|
||||
* toggling the runtime navmesh visualization. Used by:
|
||||
* - The runtime editor "Settings" popup (live toggle of dynamic nav + show navmesh)
|
||||
* - SceneLoader (gameplay path: applies the scenario's bUseDynamicNavigation flag)
|
||||
* - Scenario save/load (persists bUseDynamicNavigation in JSON)
|
||||
*/
|
||||
namespace PS_Editor_NavUtils
|
||||
{
|
||||
/**
|
||||
* Switch every RecastNavMesh in the world to the requested runtime-generation mode.
|
||||
* Static = baked nav only, no runtime updates (fastest gameplay).
|
||||
* DynamicModifiersOnly = only NavModifierVolumes affect nav at runtime.
|
||||
* Dynamic = all collision-affecting actors update the nav mesh on spawn / move.
|
||||
*
|
||||
* Idempotent — calling with the same mode the navmesh already has is a no-op.
|
||||
*/
|
||||
PS_EDITOR_API void SetRuntimeGenerationMode(UWorld* World, ERuntimeGenerationType Mode);
|
||||
|
||||
/** Convenience: switch to Dynamic if bDynamic, Static otherwise. */
|
||||
PS_EDITOR_API void SetDynamicNavigationEnabled(UWorld* World, bool bDynamic);
|
||||
|
||||
/**
|
||||
* Toggle the runtime navmesh visualization (mimics the editor's P key — green nav lines
|
||||
* + tile bounds rendered on top of the world). Affects all RecastNavMesh actors in the
|
||||
* world. Free in editor PIE/Standalone; in shipping packaged builds, debug rendering may
|
||||
* be stripped depending on build flags.
|
||||
*/
|
||||
PS_EDITOR_API void SetNavMeshVisible(UWorld* World, bool bVisible);
|
||||
}
|
||||
@@ -75,6 +75,19 @@ public:
|
||||
/** Notify all spawned actors that implement IPS_Editor_EditableInterface of editor mode change. */
|
||||
void NotifyEditorModeChanged(bool bIsEditing);
|
||||
|
||||
// ---- Per-scenario settings (persisted in JSON) ----
|
||||
|
||||
/** If true, switch the level's RecastNavMesh to Dynamic runtime generation when this
|
||||
* scenario is loaded — both in editor and gameplay. False = keep cooked Static nav.
|
||||
* Persisted in the scenario JSON; default false. */
|
||||
bool bUseDynamicNavigation = false;
|
||||
|
||||
// ---- Session-only UI preferences (NOT persisted) ----
|
||||
|
||||
/** If true, the navmesh is rendered at runtime (mimics the editor's P key). Personal
|
||||
* visualization preference, never saved with the scenario. */
|
||||
bool bShowNavMesh = false;
|
||||
|
||||
/** Tick all spawned actors that implement IPS_Editor_EditableInterface. */
|
||||
void TickEditorMode(float DeltaTime);
|
||||
|
||||
|
||||
@@ -194,20 +194,16 @@ void UPS_Editor_TimelineSubsystem::CaptureBaselinesForAllMissingTracks()
|
||||
|
||||
void UPS_Editor_TimelineSubsystem::RestoreBaseline()
|
||||
{
|
||||
if (PrePlayBaseline.Num() == 0) return;
|
||||
|
||||
// Server-only write path (same guard as ApplyValuesAtTime).
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
if (World->GetNetMode() == NM_Client) return;
|
||||
}
|
||||
|
||||
const float ZeroEpsilon = 0.0001f;
|
||||
|
||||
for (const FPS_Editor_TimelineTrack& Track : Data.Tracks)
|
||||
{
|
||||
const TPair<FGuid, FName> Key(Track.ActorId, Track.PropertyPath);
|
||||
const FString* BaselineValue = PrePlayBaseline.Find(Key);
|
||||
if (!BaselineValue) continue;
|
||||
|
||||
AActor* Actor = FindActorById(Track.ActorId);
|
||||
if (!Actor) continue;
|
||||
UObject* Target = nullptr;
|
||||
@@ -215,7 +211,39 @@ void UPS_Editor_TimelineSubsystem::RestoreBaseline()
|
||||
ResolveTarget(Actor, Track.PropertyPath, Target, Prop);
|
||||
if (!Target || !Prop) continue;
|
||||
|
||||
Prop->ImportText_InContainer(**BaselineValue, Target, Target, PPF_None);
|
||||
// Pick the value to restore:
|
||||
// - If the user placed a key at (or before) t=0 → use that key's value. This
|
||||
// matches the natural mental model where a key at t=0 sets the timeline's
|
||||
// starting state — and lets the user adjust it just by re-keying at t=0
|
||||
// (otherwise Stop would keep snapping back to the captured pre-animation
|
||||
// baseline forever, ignoring their edits).
|
||||
// - Otherwise fall back to the captured pre-animation baseline so Stop still
|
||||
// undoes whatever the timeline pushed (relevant for tracks whose first key
|
||||
// is past t=0, e.g. a property that only changes mid-timeline).
|
||||
const FString* RestoreValue = nullptr;
|
||||
FString KeyZeroValue;
|
||||
for (const FPS_Editor_TimelineKey& K : Track.Keys)
|
||||
{
|
||||
if (K.Time <= ZeroEpsilon)
|
||||
{
|
||||
KeyZeroValue = K.Value;
|
||||
RestoreValue = &KeyZeroValue;
|
||||
// Continue scanning in case there are several keys at t≈0 — the LAST one wins
|
||||
// (matches sort order: later same-time entries override earlier ones).
|
||||
}
|
||||
else
|
||||
{
|
||||
break; // keys are sorted by time; no need to look past t>0
|
||||
}
|
||||
}
|
||||
if (!RestoreValue)
|
||||
{
|
||||
const TPair<FGuid, FName> BaselineKey(Track.ActorId, Track.PropertyPath);
|
||||
RestoreValue = PrePlayBaseline.Find(BaselineKey);
|
||||
}
|
||||
if (!RestoreValue) continue;
|
||||
|
||||
Prop->ImportText_InContainer(**RestoreValue, Target, Target, PPF_None);
|
||||
if (UActorComponent* AsComp = Cast<UActorComponent>(Target))
|
||||
{
|
||||
AsComp->MarkRenderStateDirty();
|
||||
@@ -234,15 +262,40 @@ void UPS_Editor_TimelineSubsystem::RestoreBaseline()
|
||||
}
|
||||
}
|
||||
|
||||
// Don't clear the map — a subsequent Stop (or Restart) must still restore the same baseline.
|
||||
// Don't clear PrePlayBaseline — subsequent Stops without a t=0 key still need it.
|
||||
// SetData/Clear are the only resetters.
|
||||
}
|
||||
|
||||
void UPS_Editor_TimelineSubsystem::Restart()
|
||||
{
|
||||
// Rewind + replay from the start. Done in two steps so simulation transforms are restored
|
||||
// by Stop() before Play() re-enables AI, giving a clean "from the beginning" rerun.
|
||||
Stop();
|
||||
// Rewind + replay from the start.
|
||||
//
|
||||
// We can't just call Stop() then Play() because Stop() goes through
|
||||
// PlayerController::RequestStopSimulation which DEFERS the actual StopSimulation by
|
||||
// 500ms (BT grace period). The Play() call right after cancels that pending timer
|
||||
// AND re-enters StartSimulation early (bSimulating still true), so transforms are
|
||||
// never restored — characters stay at their current position instead of rewinding.
|
||||
//
|
||||
// Fix: do the full reset synchronously here. Fire OnEditorBeforeSimulateStop (via
|
||||
// RequestStopSimulation(bImmediate=true) so BP behaviour-reset hooks still run),
|
||||
// then immediately StopSimulation (which restores transforms + fires
|
||||
// OnEditorSimulateStop), then Play() to capture the rewound transforms and restart
|
||||
// AI from the clean state.
|
||||
bIsPlaying = false;
|
||||
CurrentTime = 0.f;
|
||||
LastAppliedValues.Empty();
|
||||
LastAppliedValues.SetNum(Data.Tracks.Num());
|
||||
|
||||
RestoreBaseline();
|
||||
|
||||
if (APS_Editor_PlayerController* EditorPC = FindEditorPC(GetWorld()))
|
||||
{
|
||||
if (EditorPC->IsSimulating())
|
||||
{
|
||||
EditorPC->RequestStopSimulation(/*bImmediate=*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
Play();
|
||||
}
|
||||
|
||||
@@ -364,6 +417,14 @@ bool UPS_Editor_TimelineSubsystem::AddOrUpdateKeyAtCurrentTime(AActor* Actor, co
|
||||
if (FMath::IsNearlyEqual(Track.Keys[InsertAt].Time, CurrentTime, Epsilon))
|
||||
{
|
||||
Track.Keys[InsertAt].Value = CurrentValue;
|
||||
// Invalidate the per-track applied-value cache so the next ApplyTrackAtTime
|
||||
// re-writes the (possibly different) value instead of skipping based on the
|
||||
// stale cache entry. Otherwise, updating a key with a value the cache happened
|
||||
// to hold from a previous apply would silently revert the property to it.
|
||||
if (LastAppliedValues.IsValidIndex(TrackIndex))
|
||||
{
|
||||
LastAppliedValues[TrackIndex].Empty();
|
||||
}
|
||||
OnTimelineChanged.Broadcast();
|
||||
return true;
|
||||
}
|
||||
@@ -404,6 +465,12 @@ void UPS_Editor_TimelineSubsystem::RemoveKey(int32 TrackIndex, int32 KeyIndex)
|
||||
Data.Tracks.RemoveAt(TrackIndex);
|
||||
LastAppliedValues.SetNum(Data.Tracks.Num());
|
||||
}
|
||||
else if (LastAppliedValues.IsValidIndex(TrackIndex))
|
||||
{
|
||||
// Track still has keys but its surrounding keys for any time may have changed —
|
||||
// invalidate the cache so the next apply re-writes the correct value.
|
||||
LastAppliedValues[TrackIndex].Empty();
|
||||
}
|
||||
OnTimelineChanged.Broadcast();
|
||||
}
|
||||
|
||||
@@ -413,6 +480,10 @@ void UPS_Editor_TimelineSubsystem::SetKeyInterpolate(int32 TrackIndex, int32 Key
|
||||
FPS_Editor_TimelineTrack& Track = Data.Tracks[TrackIndex];
|
||||
if (!Track.Keys.IsValidIndex(KeyIndex)) return;
|
||||
Track.Keys[KeyIndex].bInterpolate = bInterpolate;
|
||||
if (LastAppliedValues.IsValidIndex(TrackIndex))
|
||||
{
|
||||
LastAppliedValues[TrackIndex].Empty();
|
||||
}
|
||||
OnTimelineChanged.Broadcast();
|
||||
}
|
||||
|
||||
@@ -428,6 +499,10 @@ void UPS_Editor_TimelineSubsystem::SetKeyTime(int32 TrackIndex, int32 KeyIndex,
|
||||
{
|
||||
return A.Time < B.Time;
|
||||
});
|
||||
if (LastAppliedValues.IsValidIndex(TrackIndex))
|
||||
{
|
||||
LastAppliedValues[TrackIndex].Empty();
|
||||
}
|
||||
OnTimelineChanged.Broadcast();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "PS_Editor_MainWidget.h"
|
||||
#include "PS_Editor_SelectionManager.h"
|
||||
#include "PS_Editor_SpawnManager.h"
|
||||
#include "PS_Editor_NavUtils.h"
|
||||
#include "PS_Editor_SceneSerializer.h"
|
||||
#include "PS_Editor_SceneLoader.h"
|
||||
#include "PS_Editor_UndoManager.h"
|
||||
@@ -500,6 +501,110 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::RebuildWidget()
|
||||
]
|
||||
]
|
||||
]
|
||||
// Settings popup (per-scenario nav option + session navmesh visu)
|
||||
+ SOverlay::Slot()
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SAssignNew(SettingsPopup, SBorder)
|
||||
.Visibility(EVisibility::Collapsed)
|
||||
.BorderImage(BrushGlassStrong.Get())
|
||||
.Padding(FMargin(28.0f, 20.0f))
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center).Padding(0.0f, 0.0f, 0.0f, 16.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Settings")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 14))
|
||||
.ColorAndOpacity(FLinearColor::White)
|
||||
]
|
||||
+ SVerticalBox::Slot().AutoHeight().Padding(0.0f, 4.0f)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(0.0f, 0.0f, 8.0f, 0.0f)
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked_Lambda([this]() -> ECheckBoxState
|
||||
{
|
||||
if (UPS_Editor_SpawnManager* SM = SpawnManager.Get())
|
||||
{
|
||||
return SM->bUseDynamicNavigation ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
||||
}
|
||||
return ECheckBoxState::Unchecked;
|
||||
})
|
||||
.OnCheckStateChanged_Lambda([this](ECheckBoxState State) { OnUseDynamicNavigationToggled(State); })
|
||||
]
|
||||
+ SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot().AutoHeight()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Use Dynamic Navigation")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
|
||||
.ColorAndOpacity(FLinearColor::White)
|
||||
]
|
||||
+ SVerticalBox::Slot().AutoHeight()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Rebuild navmesh at runtime when actors are spawned/moved (saved with scenario).")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 9))
|
||||
.ColorAndOpacity(FLinearColor(0.7f, 0.7f, 0.7f))
|
||||
.AutoWrapText(true)
|
||||
]
|
||||
]
|
||||
]
|
||||
+ SVerticalBox::Slot().AutoHeight().Padding(0.0f, 8.0f, 0.0f, 4.0f)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(0.0f, 0.0f, 8.0f, 0.0f)
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked_Lambda([this]() -> ECheckBoxState
|
||||
{
|
||||
if (UPS_Editor_SpawnManager* SM = SpawnManager.Get())
|
||||
{
|
||||
return SM->bShowNavMesh ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
||||
}
|
||||
return ECheckBoxState::Unchecked;
|
||||
})
|
||||
.OnCheckStateChanged_Lambda([this](ECheckBoxState State) { OnShowNavMeshToggled(State); })
|
||||
]
|
||||
+ SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot().AutoHeight()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Show NavMesh (P)")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
|
||||
.ColorAndOpacity(FLinearColor::White)
|
||||
]
|
||||
+ SVerticalBox::Slot().AutoHeight()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Render the navmesh in green (mimics editor's P key). Personal preference, never saved.")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 9))
|
||||
.ColorAndOpacity(FLinearColor(0.7f, 0.7f, 0.7f))
|
||||
.AutoWrapText(true)
|
||||
]
|
||||
]
|
||||
]
|
||||
+ SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center).Padding(0.0f, 16.0f, 0.0f, 0.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.ButtonStyle(StyleFlatButton.Get())
|
||||
.OnClicked_Lambda([this]() -> FReply { CloseSettingsPopup(); return FReply::Handled(); })
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Close")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
|
||||
.ColorAndOpacity(FLinearColor::White)
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
// ============= COMMAND PALETTE (modal, Ctrl+K) =============
|
||||
+ SOverlay::Slot()
|
||||
@@ -987,6 +1092,26 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::BuildSceneButtons()
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
|
||||
]
|
||||
]
|
||||
// Settings button
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(4.0f, 0.0f, 0.0f, 0.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.ButtonStyle(StyleFlatButton.Get())
|
||||
.ToolTipText(FText::FromString(TEXT("Open scenario / session settings (dynamic nav, navmesh visu).")))
|
||||
.OnClicked_Lambda([this]() -> FReply
|
||||
{
|
||||
OpenSettingsPopup();
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Settings")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
|
||||
.ColorAndOpacity(FLinearColor(0.8f, 0.8f, 0.8f))
|
||||
]
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(4.0f, 0.0f, 0.0f, 0.0f)
|
||||
@@ -3207,6 +3332,43 @@ void UPS_Editor_MainWidget::CancelBaseLevelPopup()
|
||||
PendingBaseLevelSelection.Empty();
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget::OpenSettingsPopup()
|
||||
{
|
||||
if (SettingsPopup.IsValid())
|
||||
{
|
||||
SettingsPopup->SetVisibility(EVisibility::Visible);
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget::CloseSettingsPopup()
|
||||
{
|
||||
if (SettingsPopup.IsValid())
|
||||
{
|
||||
SettingsPopup->SetVisibility(EVisibility::Collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget::OnUseDynamicNavigationToggled(ECheckBoxState State)
|
||||
{
|
||||
UPS_Editor_SpawnManager* SM = SpawnManager.Get();
|
||||
if (!SM) return;
|
||||
|
||||
const bool bEnabled = (State == ECheckBoxState::Checked);
|
||||
SM->bUseDynamicNavigation = bEnabled;
|
||||
bSceneDirty = true;
|
||||
PS_Editor_NavUtils::SetDynamicNavigationEnabled(GetWorld(), bEnabled);
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget::OnShowNavMeshToggled(ECheckBoxState State)
|
||||
{
|
||||
UPS_Editor_SpawnManager* SM = SpawnManager.Get();
|
||||
if (!SM) return;
|
||||
|
||||
const bool bVisible = (State == ECheckBoxState::Checked);
|
||||
SM->bShowNavMesh = bVisible;
|
||||
PS_Editor_NavUtils::SetNavMeshVisible(GetWorld(), bVisible);
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget::OnCloseClicked()
|
||||
{
|
||||
if (CloseConfirmOverlay.IsValid())
|
||||
|
||||
@@ -134,6 +134,13 @@ private:
|
||||
void ConfirmBaseLevelSelection();
|
||||
void CancelBaseLevelPopup();
|
||||
|
||||
// Global Settings popup (per-scenario nav option + session-only navmesh visu).
|
||||
TSharedPtr<SWidget> SettingsPopup;
|
||||
void OpenSettingsPopup();
|
||||
void CloseSettingsPopup();
|
||||
void OnUseDynamicNavigationToggled(ECheckBoxState State);
|
||||
void OnShowNavMeshToggled(ECheckBoxState State);
|
||||
|
||||
// Transform fields
|
||||
TSharedPtr<SEditableTextBox> LocX, LocY, LocZ;
|
||||
TSharedPtr<SEditableTextBox> RotX, RotY, RotZ;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "PS_Editor_MainWidget_Legacy.h"
|
||||
#include "PS_Editor_SelectionManager.h"
|
||||
#include "PS_Editor_SpawnManager.h"
|
||||
#include "PS_Editor_NavUtils.h"
|
||||
#include "PS_Editor_SceneSerializer.h"
|
||||
#include "PS_Editor_SceneLoader.h"
|
||||
#include "PS_Editor_UndoManager.h"
|
||||
@@ -448,6 +449,124 @@ TSharedRef<SWidget> UPS_Editor_MainWidget_Legacy::RebuildWidget()
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
// Settings popup (per-scenario nav option + session navmesh visu)
|
||||
+ SOverlay::Slot()
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SAssignNew(SettingsPopup, SBorder)
|
||||
.Visibility(EVisibility::Collapsed)
|
||||
.BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush"))
|
||||
.BorderBackgroundColor(FLinearColor(0.08f, 0.08f, 0.08f, 0.97f))
|
||||
.Padding(FMargin(28.0f, 20.0f))
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
// Header
|
||||
+ SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center).Padding(0.0f, 0.0f, 0.0f, 16.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Settings")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 14))
|
||||
.ColorAndOpacity(FLinearColor::White)
|
||||
]
|
||||
// Use Dynamic Navigation (saved per-scenario)
|
||||
+ SVerticalBox::Slot().AutoHeight().Padding(0.0f, 4.0f)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(0.0f, 0.0f, 8.0f, 0.0f)
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked_Lambda([this]() -> ECheckBoxState
|
||||
{
|
||||
if (UPS_Editor_SpawnManager* SM = SpawnManager.Get())
|
||||
{
|
||||
return SM->bUseDynamicNavigation ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
||||
}
|
||||
return ECheckBoxState::Unchecked;
|
||||
})
|
||||
.OnCheckStateChanged_Lambda([this](ECheckBoxState State)
|
||||
{
|
||||
OnUseDynamicNavigationToggled(State);
|
||||
})
|
||||
]
|
||||
+ SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot().AutoHeight()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Use Dynamic Navigation")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
|
||||
.ColorAndOpacity(FLinearColor::White)
|
||||
]
|
||||
+ SVerticalBox::Slot().AutoHeight()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Rebuild navmesh at runtime when actors are spawned/moved (saved with scenario).")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 9))
|
||||
.ColorAndOpacity(FLinearColor(0.7f, 0.7f, 0.7f))
|
||||
.AutoWrapText(true)
|
||||
]
|
||||
]
|
||||
]
|
||||
// Show NavMesh (session-only)
|
||||
+ SVerticalBox::Slot().AutoHeight().Padding(0.0f, 8.0f, 0.0f, 4.0f)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(0.0f, 0.0f, 8.0f, 0.0f)
|
||||
[
|
||||
SNew(SCheckBox)
|
||||
.IsChecked_Lambda([this]() -> ECheckBoxState
|
||||
{
|
||||
if (UPS_Editor_SpawnManager* SM = SpawnManager.Get())
|
||||
{
|
||||
return SM->bShowNavMesh ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
||||
}
|
||||
return ECheckBoxState::Unchecked;
|
||||
})
|
||||
.OnCheckStateChanged_Lambda([this](ECheckBoxState State)
|
||||
{
|
||||
OnShowNavMeshToggled(State);
|
||||
})
|
||||
]
|
||||
+ SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot().AutoHeight()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Show NavMesh (P)")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
|
||||
.ColorAndOpacity(FLinearColor::White)
|
||||
]
|
||||
+ SVerticalBox::Slot().AutoHeight()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Render the navmesh in green (mimics editor's P key). Personal preference, never saved.")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 9))
|
||||
.ColorAndOpacity(FLinearColor(0.7f, 0.7f, 0.7f))
|
||||
.AutoWrapText(true)
|
||||
]
|
||||
]
|
||||
]
|
||||
// Close button
|
||||
+ SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center).Padding(0.0f, 16.0f, 0.0f, 0.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.OnClicked_Lambda([this]() -> FReply
|
||||
{
|
||||
CloseSettingsPopup();
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Close")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
|
||||
.ColorAndOpacity(FLinearColor::White)
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -816,6 +935,25 @@ TSharedRef<SWidget> UPS_Editor_MainWidget_Legacy::BuildSceneButtons()
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
|
||||
]
|
||||
]
|
||||
// Settings button — opens the global Settings popup
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(4.0f, 0.0f, 0.0f, 0.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.ToolTipText(FText::FromString(TEXT("Open scenario / session settings (dynamic nav, navmesh visu).")))
|
||||
.OnClicked_Lambda([this]() -> FReply
|
||||
{
|
||||
OpenSettingsPopup();
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Settings")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
|
||||
.ColorAndOpacity(FLinearColor(0.8f, 0.8f, 0.8f))
|
||||
]
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(4.0f, 0.0f, 0.0f, 0.0f)
|
||||
@@ -2868,6 +3006,55 @@ void UPS_Editor_MainWidget_Legacy::CancelBaseLevelPopup()
|
||||
PendingBaseLevelSelection.Empty();
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_Legacy::OpenSettingsPopup()
|
||||
{
|
||||
if (SettingsPopup.IsValid())
|
||||
{
|
||||
SettingsPopup->SetVisibility(EVisibility::Visible);
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_Legacy::CloseSettingsPopup()
|
||||
{
|
||||
if (SettingsPopup.IsValid())
|
||||
{
|
||||
SettingsPopup->SetVisibility(EVisibility::Collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_Legacy::OnUseDynamicNavigationToggled(ECheckBoxState State)
|
||||
{
|
||||
UPS_Editor_SpawnManager* SM = SpawnManager.Get();
|
||||
if (!SM) return;
|
||||
|
||||
const bool bEnabled = (State == ECheckBoxState::Checked);
|
||||
SM->bUseDynamicNavigation = bEnabled;
|
||||
bSceneDirty = true; // user changed a per-scenario setting
|
||||
|
||||
// Apply live to the level's RecastNavMesh actors.
|
||||
PS_Editor_NavUtils::SetDynamicNavigationEnabled(GetWorld(), bEnabled);
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_Legacy::OnShowNavMeshToggled(ECheckBoxState State)
|
||||
{
|
||||
UPS_Editor_SpawnManager* SM = SpawnManager.Get();
|
||||
if (!SM) return;
|
||||
|
||||
const bool bVisible = (State == ECheckBoxState::Checked);
|
||||
SM->bShowNavMesh = bVisible;
|
||||
|
||||
PS_Editor_NavUtils::SetNavMeshVisible(GetWorld(), bVisible);
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_Legacy::ToggleShowNavMesh()
|
||||
{
|
||||
UPS_Editor_SpawnManager* SM = SpawnManager.Get();
|
||||
if (!SM) return;
|
||||
|
||||
SM->bShowNavMesh = !SM->bShowNavMesh;
|
||||
PS_Editor_NavUtils::SetNavMeshVisible(GetWorld(), SM->bShowNavMesh);
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget_Legacy::OnCloseClicked()
|
||||
{
|
||||
if (CloseConfirmOverlay.IsValid())
|
||||
|
||||
@@ -107,6 +107,15 @@ private:
|
||||
void ConfirmBaseLevelSelection();
|
||||
void CancelBaseLevelPopup();
|
||||
|
||||
// Global Settings popup (per-scenario nav option + session-only navmesh visu).
|
||||
TSharedPtr<SWidget> SettingsPopup;
|
||||
void OpenSettingsPopup();
|
||||
void CloseSettingsPopup();
|
||||
void OnUseDynamicNavigationToggled(ECheckBoxState State);
|
||||
void OnShowNavMeshToggled(ECheckBoxState State);
|
||||
/** Toggle the navmesh visualization (P key handler). */
|
||||
void ToggleShowNavMesh();
|
||||
|
||||
// Transform fields
|
||||
TSharedPtr<SEditableTextBox> LocX, LocY, LocZ;
|
||||
TSharedPtr<SEditableTextBox> RotX, RotY, RotZ;
|
||||
|
||||
Reference in New Issue
Block a user