Extend the EditableInterface with two new BlueprintNativeEvents so property combos can show human-friendly labels while storing technical values: - GetCustomOptionsWithValuesForProperty returns TArray<FPS_Editor_PropertyOption> where each pair carries a DisplayName (shown in the combo) and a Value (written to the property via ImportText). Wins over the legacy flat-string variant when non-empty. Ideal for TSubclassOf / TSoftClassPtr properties driven by a DataTable lookup where the stored value is a class path. - GetCurrentDisplayForProperty lets the actor authoritatively resolve the DisplayName from the current stored value, for cases where the automatic reverse lookup (normalize class path, exact-match) isn't reliable — e.g. several Values collapse to the same label, or the format is user-specific. UI refresh now asks the actor first, then falls back to a normalized match (strips the `TypeName'...'` wrapper from ExportText output so it compares apples to apples with a plain /Game/Path.X_C string). Same logic duplicated across both widget implementations (MainWidget and MainWidget_Legacy) since the project can switch UI mode at runtime. The new CLAUDE.md rule makes that requirement explicit. Also documented in CLAUDE.md: - Editor-vs-gameplay execution boundary: which interface methods fire where, including the gotcha that OnEditorPropertyChanged *does* fire in gameplay on the server when the timeline animates a property. - UI modes: keep all MainWidget* implementations in lockstep to avoid the "I edited the file but nothing changed" class of bug. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
103 lines
6.1 KiB
Markdown
103 lines
6.1 KiB
Markdown
# PS_ProserveEditor - Project Guidelines
|
|
|
|
## Project Overview
|
|
Unreal Engine 5.5 project with runtime editor plugin (PS_Editor). The plugin enables runtime scene editing in packaged builds: object placement, property editing, scenario definition, splines, lighting, and JSON scene save/load.
|
|
|
|
## Architecture
|
|
- **Plugin**: PS_Editor (runtime plugin, must work in packaged builds)
|
|
- **UI**: UMG driven by C++ (Slate)
|
|
- **Gizmos**: Custom actor with BasicShapes + unlit material (InteractiveToolsFramework is editor-only)
|
|
- **Serialization**: JSON via Json/JsonUtilities modules (FJsonObjectConverter)
|
|
- **Input**: Enhanced Input system (AZERTY: arrows + A/Q movement, E/R/T modes)
|
|
|
|
## Editor vs. Gameplay execution boundary — CRITICAL
|
|
|
|
The plugin runs in two distinct modes and it's essential never to leak editor-only code
|
|
into the gameplay runtime loaded via `UPS_Editor_SceneLoader::LoadScene` (standalone).
|
|
|
|
### Editor runtime mode
|
|
Activated when the `APS_Editor_PlayerController` + `APS_Editor_HUD` + `UPS_Editor_MainWidget*`
|
|
stack is created (typical during authoring — the user is in the runtime editor).
|
|
- `UPS_Editor_SceneLoader::bIsEditorModeActive == true`
|
|
- The HUD widget drives property editing, catalogue spawn, save/load, simulate, timeline
|
|
- Interface methods called **only** by the widget (never in gameplay):
|
|
- `GetCustomOptionsForProperty` / `GetCustomOptionsWithValuesForProperty` / `GetCurrentDisplayForProperty`
|
|
- `OnEditorModeChanged`, `OnEditorTick`
|
|
- `OnEditorNeedsReinit`, `OnEditorNeedsRespawn`
|
|
|
|
### Gameplay mode
|
|
The project (PROSERVE) loads a scenario via `UPS_Editor_SceneLoader::LoadScene` from a GameMode
|
|
BeginPlay, without ever instantiating the editor PlayerController / HUD / widget.
|
|
- `UPS_Editor_SceneLoader::bIsEditorModeActive == false`
|
|
- Actors spawn, their properties are applied from JSON, `OnPropertiesLoaded` fires once
|
|
- The timeline subsystem auto-plays if the scenario has tracks, driven **server-side only**
|
|
|
|
### Interface methods that fire in BOTH modes
|
|
- `OnPropertiesLoaded` — after every scene load, editor or gameplay (applied once post-spawn)
|
|
- `OnEditorPropertyChanged` — fires both in editor (on UI edit via `ApplyPropertyFromUI`) AND
|
|
in gameplay (on server when the timeline applies a new keyframe value via
|
|
`TimelineSubsystem::ApplyTrackAtTime`). Despite the name, the "Editor" refers to the
|
|
PS_Editor plugin — the event is the canonical hook for "a property changed at runtime".
|
|
In gameplay only the **server** invokes it; client-side reaction must go through
|
|
`ReplicatedUsing=OnRep_X` RepNotify on the replicated property.
|
|
|
|
### Rule when adding new features
|
|
- Anything tied to property panel UI (new interface method, new widget logic, dropdown
|
|
helpers, etc.) lives in the widget only — no extra work to scope it out of gameplay
|
|
- Anything added to the `SceneLoader::LoadScene` path runs in gameplay and must handle
|
|
`NM_Client`, replication, and server-authority correctly (the timeline subsystem is the
|
|
reference pattern)
|
|
- Expensive BP implementations of editor-UI interface methods (DataTable lookups, class
|
|
resolution, etc.) can stay expensive — they never run outside the authoring session
|
|
|
|
### How to guard code that could run in either mode
|
|
```cpp
|
|
if (UPS_Editor_SceneLoader::IsInEditorMode())
|
|
{
|
|
// editor-only behaviour
|
|
}
|
|
```
|
|
or gate on `bIsCurrentlyLoading` to skip BP init during JSON restore.
|
|
|
|
## UI Modes — IMPORTANT for widget changes
|
|
|
|
The runtime editor ships with **multiple widget implementations** that must all stay in sync:
|
|
- `PS_Editor_MainWidget.cpp/.h` — modern UMG-based widget (primary target long-term)
|
|
- `PS_Editor_MainWidget_Legacy.cpp/.h` — Slate-only legacy widget (**currently active** as of April 2026)
|
|
- `PS_Editor_MainWidget_UMG.cpp/.h` — UMG variant
|
|
|
|
The user can switch between them via a `UIMode` setting. At any given time only one is running, but the project keeps all three compiled.
|
|
|
|
**Rule:** when changing any behavior inside a widget (new interface calls, new property row logic, new UI controls, bug fixes in RebuildDynamicProperties / ApplyPropertyFromUI / RefreshDynamicPropertyValues, etc.), **apply the change to ALL widget implementations at the same time**. Otherwise the user's current UIMode gets the fix and the others don't — the "it's like you didn't change anything" bug.
|
|
|
|
Symptom that the wrong widget was edited: logs you added don't appear in the Output Log despite the build being clean and the DLL being correctly loaded. Always grep for the function name across all `PS_Editor_MainWidget*.cpp` files before assuming a single edit is sufficient.
|
|
|
|
## Naming Conventions
|
|
- All plugin files and classes use the `PS_Editor_` prefix (e.g., `PS_Editor_GameMode`, `PS_Editor_Pawn`)
|
|
- Code and comments in English
|
|
- Standard UE5 prefixes: `A` for Actors, `U` for UObjects, `F` for structs, `E` for enums, `I` for interfaces
|
|
|
|
## Code Quality
|
|
- Prefer correct, complete implementations over minimal ones.
|
|
- Use appropriate data structures and algorithms — don't brute-force what has a known better solution.
|
|
- When fixing a bug, fix the root cause, not the symptom.
|
|
- If something requires error handling or validation to work reliably, include it without asking.
|
|
|
|
## Build
|
|
- Engine: Unreal Engine 5.5
|
|
- Plugin location: `Unreal/Plugins/PS_Editor/`
|
|
- Game module: `PS_ProserveEditor` (in `Unreal/Source/PS_ProserveEditor/`)
|
|
- CLI build command (Editor, Development):
|
|
```
|
|
"<UE_INSTALL>/Engine/Build/BatchFiles/Build.bat" PS_ProserveEditorEditor Win64 Development -Project="<REPO>/Unreal/PS_ProserveEditor.uproject" -WaitMutex -FromMsBuild
|
|
```
|
|
UE install path can be found via registry: `HKLM\SOFTWARE\EpicGames\Unreal Engine\5.5` > `InstalledDirectory`
|
|
|
|
## Project Memory
|
|
Shared project memory is stored in `.claude/memory/` (versioned in the repository). When starting a new session on any machine, read these files to restore context:
|
|
|
|
- `MEMORY.md` — Index of all memory files with one-line summaries
|
|
- `project_ps_editor.md` — Project scope, technical decisions, current phase
|
|
- `feedback_naming.md` — Naming conventions and user preferences
|
|
- `reference_build.md` — Full build command and UE install path details
|