Compare commits
32 Commits
3ca87a7893
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57ea21073a | ||
|
|
15d6e88780 | ||
|
|
dc9c1a6b98 | ||
|
|
14441c000c | ||
|
|
7c2e8df6b1 | ||
|
|
5c08c929b5 | ||
|
|
321287253b | ||
|
|
9fd679fd5b | ||
|
|
d16c661022 | ||
|
|
0a2d08b2ad | ||
|
|
6b6c702dd7 | ||
|
|
318d0d4317 | ||
|
|
040db37720 | ||
|
|
c515920eea | ||
|
|
5b7d652505 | ||
|
|
bcbfcdf167 | ||
|
|
0852386168 | ||
|
|
44aca98885 | ||
|
|
7e876e4f0c | ||
|
|
ccd1872e59 | ||
|
|
8bb162eda2 | ||
|
|
cd0ebf2233 | ||
|
|
6132571f8d | ||
|
|
4ae2137179 | ||
|
|
f986343325 | ||
|
|
a145ae9373 | ||
|
|
d982a6367d | ||
|
|
108173b87b | ||
|
|
d100a097f5 | ||
|
|
3da9fd7493 | ||
|
|
9ee0a65630 | ||
|
|
0b1128d209 |
16
.gitattributes
vendored
Normal file
16
.gitattributes
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Unreal binary assets
|
||||||
|
*.uasset filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.umap filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
||||||
|
# Common DCC / source asset formats you may want in LFS too
|
||||||
|
*.fbx filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.blend filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tga filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.exr filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.mov filter=lfs diff=lfs merge=lfs -text
|
||||||
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Unreal generated folders
|
||||||
|
Binaries/
|
||||||
|
DerivedDataCache/
|
||||||
|
Intermediate/
|
||||||
|
Saved/
|
||||||
|
|
||||||
|
# Visual Studio / Rider / IDE
|
||||||
|
.vs/
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.VC.db
|
||||||
|
*.VC.opendb
|
||||||
|
*.sln
|
||||||
|
*.suo
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
|
||||||
|
# OS junk
|
||||||
|
Thumbs.db
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Local AI / tool folders
|
||||||
|
.kilo/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Keep core Unreal project files
|
||||||
|
!*.uproject
|
||||||
|
!*.uplugin
|
||||||
@@ -2,5 +2,18 @@
|
|||||||
"$schema": "https://app.kilo.ai/config.json",
|
"$schema": "https://app.kilo.ai/config.json",
|
||||||
"indexing": {
|
"indexing": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"context7": {
|
||||||
|
"type": "local",
|
||||||
|
"command": [
|
||||||
|
"npx",
|
||||||
|
"-y",
|
||||||
|
"@upstash/context7-mcp"
|
||||||
|
],
|
||||||
|
"environment": {
|
||||||
|
"DEFAULT_MINIMUM_TOKENS": "{{DEFAULT_MINIMUM_TOKENS}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
33
CONTEXT.md
33
CONTEXT.md
@@ -68,6 +68,22 @@ Content/
|
|||||||
S_ActionPermissionResult.uasset # Permission result struct
|
S_ActionPermissionResult.uasset # Permission result struct
|
||||||
DA_StateGatingTable.uasset # All gating rules (37 action rules)
|
DA_StateGatingTable.uasset # All gating rules (37 action rules)
|
||||||
BPC_StateManager.uasset # Central state authority component
|
BPC_StateManager.uasset # Central state authority component
|
||||||
|
Capture/ # Planar Capture assets (NEW — Phase 17)
|
||||||
|
BP_Mirror.uasset # Standard mirror surface
|
||||||
|
BP_Portal.uasset # Portal surface with linked target
|
||||||
|
BP_Monitor.uasset # Security camera monitor
|
||||||
|
BP_HorrorMirror.uasset # Horror mirror with wrong reflection
|
||||||
|
BP_FakeWindow.uasset # Architectural fake window
|
||||||
|
M_CaptureSurface_Master.uasset # Master unlit material (9-layer)
|
||||||
|
MPC_CaptureSurface.uasset # Global MPC (10 scalar params)
|
||||||
|
DA_PlanarCaptureProfile.uasset # Per-surface capture config
|
||||||
|
MI_Mirror_Clean.uasset # Material instances
|
||||||
|
MI_Mirror_Dirty.uasset
|
||||||
|
MI_Mirror_Steam.uasset
|
||||||
|
MI_Mirror_Horror.uasset
|
||||||
|
MI_Portal_Standard.uasset
|
||||||
|
MI_Monitor_Security.uasset
|
||||||
|
MI_FakeWindow_Interior.uasset
|
||||||
|
|
||||||
docs/
|
docs/
|
||||||
blueprints/
|
blueprints/
|
||||||
@@ -90,6 +106,7 @@ docs/
|
|||||||
14-data-assets/ # Data Asset definitions (16 files, 115-127, 129, 134-135)
|
14-data-assets/ # Data Asset definitions (16 files, 115-127, 129, 134-135)
|
||||||
15-input/ # Enhanced Input System (1 file, 128)
|
15-input/ # Enhanced Input System (1 file, 128)
|
||||||
16-state/ # State Management (2 files, 130-131)
|
16-state/ # State Management (2 files, 130-131)
|
||||||
|
17-capture/ # Planar Capture System (12 files, 136-147)
|
||||||
developer/ # Developer Reference Docs (NEW)
|
developer/ # Developer Reference Docs (NEW)
|
||||||
INDEX.md # Master developer docs index (12 docs)
|
INDEX.md # Master developer docs index (12 docs)
|
||||||
architecture-overview.md # Framework-wide architecture walkthrough
|
architecture-overview.md # Framework-wide architecture walkthrough
|
||||||
@@ -106,7 +123,7 @@ docs/
|
|||||||
09-ai-systems.md # AI, perception & encounters explained
|
09-ai-systems.md # AI, perception & encounters explained
|
||||||
10-adaptive-systems.md # Adaptive environment & atmosphere explained
|
10-adaptive-systems.md # Adaptive environment & atmosphere explained
|
||||||
11-16-systems.md # Meta, Settings, Polish, Data Assets, Input, State explained
|
11-16-systems.md # Meta, Settings, Polish, Data Assets, Input, State explained
|
||||||
architecture/ # Architecture & Design Documents (8 files)
|
architecture/ # Architecture & Design Documents (9 files)
|
||||||
bpc-statemanager.md # BPC_StateManager full spec + Chooser Table Audit
|
bpc-statemanager.md # BPC_StateManager full spec + Chooser Table Audit
|
||||||
metasounds-audio-system.md # MetaSounds audio architecture (mix buses, room zones, settings)
|
metasounds-audio-system.md # MetaSounds audio architecture (mix buses, room zones, settings)
|
||||||
animation-catalog.md # Animation requirements for all 135 systems
|
animation-catalog.md # Animation requirements for all 135 systems
|
||||||
@@ -115,6 +132,7 @@ docs/
|
|||||||
hud-overview.md # HUD system architecture — 14 widgets, wiring, integration points
|
hud-overview.md # HUD system architecture — 14 widgets, wiring, integration points
|
||||||
multiplayer-networking.md # Multiplayer networking architecture — authority model, RPCs, prediction
|
multiplayer-networking.md # Multiplayer networking architecture — authority model, RPCs, prediction
|
||||||
blueprint-limitations-workarounds.md # UE5 C++-only functions + 100% Blueprint workarounds (NEW)
|
blueprint-limitations-workarounds.md # UE5 C++-only functions + 100% Blueprint workarounds (NEW)
|
||||||
|
planar-capture-system.md # Planar Capture System — mirrors, portals, monitors, horror surfaces (Phase 17)
|
||||||
CLEAN_SLATE_PLAN.md # Clean slate refactoring plan
|
CLEAN_SLATE_PLAN.md # Clean slate refactoring plan
|
||||||
reports/ # Condensed audit reports
|
reports/ # Condensed audit reports
|
||||||
blueprint_condensed_summary.md # ASK agent audit of all 129 files
|
blueprint_condensed_summary.md # ASK agent audit of all 129 files
|
||||||
@@ -123,7 +141,7 @@ docs/
|
|||||||
enhanced-input-system.md
|
enhanced-input-system.md
|
||||||
bpc-statemanager.md # NEW — State Manager implementation checklist
|
bpc-statemanager.md # NEW — State Manager implementation checklist
|
||||||
```
|
```
|
||||||
**Total: 135 numbered Blueprint files + 5 enums + 5 Data Assets + 11 Data Table CSVs + TEMPLATE.md + AUDIT_REPORT.md + INDEX.md + 12 developer docs + 8 architecture docs + 1 audit report = 178 files in 19 directory groups**
|
**Total: 149 numbered Blueprint files + 2 supplementary + 5 enums + 5 Data Assets + 11 Data Table CSVs + TEMPLATE.md + AUDIT_REPORT.md + INDEX.md + 13 developer docs + 9 architecture docs + 1 audit report + 1 haptics game example = 196 files in 20 directory groups**
|
||||||
|
|
||||||
## Naming Conventions
|
## Naming Conventions
|
||||||
| Prefix | Type |
|
| Prefix | Type |
|
||||||
@@ -163,7 +181,7 @@ docs/
|
|||||||
15. **Server-Authoritative Replication** — All state mutations gated by `HasAuthority()`. Clients request via `Server_` RPCs; servers validate and replicate via `RepNotify`. See [`multiplayer-networking.md`](docs/architecture/multiplayer-networking.md).
|
15. **Server-Authoritative Replication** — All state mutations gated by `HasAuthority()`. Clients request via `Server_` RPCs; servers validate and replicate via `RepNotify`. See [`multiplayer-networking.md`](docs/architecture/multiplayer-networking.md).
|
||||||
|
|
||||||
## Build Order (Dependency-Safe)
|
## Build Order (Dependency-Safe)
|
||||||
- **Phase 0:** Foundation + Input (01-core, 15-input — 8 systems)
|
- **Phase 0:** Foundation + Input (01-core + 15-input — 9 systems — includes BPC_PlatformServiceAbstraction 150)
|
||||||
- **Phase 1:** Player Core (02-player, 8 systems)
|
- **Phase 1:** Player Core (02-player, 8 systems)
|
||||||
- **Phase 2:** Interaction (03-interaction, 8 systems)
|
- **Phase 2:** Interaction (03-interaction, 8 systems)
|
||||||
- **Phase 3:** Inventory (04-inventory, 11 systems)
|
- **Phase 3:** Inventory (04-inventory, 11 systems)
|
||||||
@@ -175,11 +193,14 @@ docs/
|
|||||||
- **Phase 9:** Adaptive & Atmosphere (10-adaptive, 15 systems)
|
- **Phase 9:** Adaptive & Atmosphere (10-adaptive, 15 systems)
|
||||||
- **Phase 10:** Data Assets (14-data-assets, 16 systems)
|
- **Phase 10:** Data Assets (14-data-assets, 16 systems)
|
||||||
- **Phase 11:** Meta/Progression (11-meta, 2 systems)
|
- **Phase 11:** Meta/Progression (11-meta, 2 systems)
|
||||||
- **Phase 12:** Settings/Platform (12-settings, 2 systems)
|
- **Phase 12:** Settings/Platform (12-settings, 5 systems — includes BPC_HapticsController 148, BPC_RenderPipelineManager 149)
|
||||||
- **Phase 13:** Polish (13-polish, 9 systems)
|
- **Phase 13:** Polish (13-polish, 9 systems)
|
||||||
- **Phase 14:** State Management (BPC_StateManager + 4 enums + 3 structs + DA_StateGatingTable — 130, 131)
|
- **Phase 14:** State Management (BPC_StateManager + 4 enums + 3 structs + DA_StateGatingTable — 130, 131)
|
||||||
- **Phase 15:** MetaSounds Audio (SS_AudioManager + BP_RoomAudioZone + DA_AudioSettings + DA_RoomAcousticPreset — 132-135)
|
- **Phase 15:** MetaSounds Audio (SS_AudioManager + BP_RoomAudioZone + DA_AudioSettings + DA_RoomAcousticPreset — 132-135)
|
||||||
- **Phase 16:** Multiplayer Networking — All systems updated with `HasAuthority()` gates, `Server_` RPCs, `RepNotify` handlers, and client prediction patterns. See [`docs/architecture/multiplayer-networking.md`](docs/architecture/multiplayer-networking.md).
|
- **Phase 16:** Multiplayer Networking — All systems updated with `HasAuthority()` gates, `Server_` RPCs, `RepNotify` handlers, and client prediction patterns. See [`docs/architecture/multiplayer-networking.md`](docs/architecture/multiplayer-networking.md).
|
||||||
|
- **Phase 17:** Planar Capture System — Mirrors, portals, monitors, horror surfaces. C++ core (136-138) + Blueprint actors (139-143) + Material/MPC (144-145) + Data Assets (146-147). See [`docs/architecture/planar-capture-system.md`](docs/architecture/planar-capture-system.md).
|
||||||
|
- **Phase 18:** Haptics Controller — BPC_HapticsController (148) for GameplayTag-driven force feedback, DualSense adaptive triggers, heartbeat pulse. See [`docs/blueprints/12-settings/148_BPC_HapticsController.md`](docs/blueprints/12-settings/148_BPC_HapticsController.md).
|
||||||
|
- **Phase 19:** Render Pipeline Manager — BPC_RenderPipelineManager (149) + DA_RenderPipelineProfile for per-platform quality presets, Lumen/Baked switching, upscaling (DLSS/FSR/PSSR), Nanite/LOD. Planar capture system integration. See [`docs/blueprints/12-settings/149_BPC_RenderPipelineManager.md`](docs/blueprints/12-settings/149_BPC_RenderPipelineManager.md) and [`docs/developer/platform-render-profiles.md`](docs/developer/platform-render-profiles.md).
|
||||||
|
|
||||||
## Key Communication Rules
|
## Key Communication Rules
|
||||||
1. Gameplay Tags + Subsystem lookup (global, decoupled)
|
1. Gameplay Tags + Subsystem lookup (global, decoupled)
|
||||||
@@ -235,4 +256,6 @@ No PR is accepted without these files being current. This ensures the animator,
|
|||||||
- **Phase 8-13:** COMPLETE — AUDIT_REPORT updated, CONTEXT.md updated, verification, final commit
|
- **Phase 8-13:** COMPLETE — AUDIT_REPORT updated, CONTEXT.md updated, verification, final commit
|
||||||
- **Phase 14-15:** COMPLETE — State Management (130-131) + MetaSounds Audio (132-135) blueprint specs created
|
- **Phase 14-15:** COMPLETE — State Management (130-131) + MetaSounds Audio (132-135) blueprint specs created
|
||||||
- **Phase 16:** IN PROGRESS — Multiplayer Networking: all 135 blueprint specs being updated with replication stubs, RPC definitions, and server-authoritative patterns. Developer docs updated. Architecture doc created at [`docs/architecture/multiplayer-networking.md`](docs/architecture/multiplayer-networking.md)
|
- **Phase 16:** IN PROGRESS — Multiplayer Networking: all 135 blueprint specs being updated with replication stubs, RPC definitions, and server-authoritative patterns. Developer docs updated. Architecture doc created at [`docs/architecture/multiplayer-networking.md`](docs/architecture/multiplayer-networking.md)
|
||||||
- **Remaining:** Cross-reference pass across all 135 files; deprecated BPC_AudioAtmosphereController (95) references to update
|
- **Phase 18:** COMPLETE — Haptics Controller (148) blueprint spec created with full Manual Implementation Guide, DA_HapticProfile (121) enhanced, game example (`docs/game/haptics-example.md`) created. All developer docs and indexes updated.
|
||||||
|
- **Phase 19:** COMPLETE — Render Pipeline Manager (149) + DA_RenderPipelineProfile created. Quality preset system with Lumen/Baked switching, per-platform profiles (PS5/PS4/Xbox/Switch/PC/SteamDeck), upscaling integration (DLSS/FSR/PSSR/TSR). Platform render profiles developer guide created. PlanarCapture system updated for pipeline compatibility. All docs updated.
|
||||||
|
- **Remaining:** Cross-reference pass across all 149 files; deprecated BPC_AudioAtmosphereController (95) references to update; Planar Capture System (Phase 17) C++ written, blueprint specs created, pending UE5 editor compile and BP child creation
|
||||||
153
README.md
Normal file
153
README.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# UE5 Modular Game Framework
|
||||||
|
|
||||||
|
**A complete Blueprint architecture for first-person narrative horror games — and beyond.**
|
||||||
|
|
||||||
|
This is a production-ready, modular game framework for Unreal Engine 5.5–5.7. It provides 147 self-contained Blueprint systems across 17 categories, plus a fully documented example game prototype ("Project Void"). Built Blueprint-first with 27 supporting C++ classes for performance-critical paths.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What This Framework Covers
|
||||||
|
|
||||||
|
### Foundation
|
||||||
|
Game Instance kernel, Core GameMode, GameState, PlayerState, Function Library, Macro Library, GameplayTag registry (334 tags across 11 categories), and a unified C++/Blueprint interface library.
|
||||||
|
|
||||||
|
### Player Simulation
|
||||||
|
Health & damage resistance, Stamina with exhaustion states, Psychological Stress with 5 tiers (calm → catatonic), GASP-integrated Movement & Posture, Hiding & peek system, First-person Embodiment with arm IK, Camera state layers (FOV/offset per action), and Player Metrics tracking (accuracy, playstyle).
|
||||||
|
|
||||||
|
### Interaction & World
|
||||||
|
Raycast-based Interaction Detector, Physical Doors (open/close/lock/barricade), Puzzle Devices with state machines, Contextual Traversal (vault/mantle/slide via Motion Warping), Physics Drag (grab & throw objects), Usable World Objects (levers, valves, buttons), Diegetic In-World Displays (wristwatch, monitors).
|
||||||
|
|
||||||
|
### Inventory & Items
|
||||||
|
Grid-based Inventory with weight management, World Pickup actors with soft-reference mesh loading, Equipment Slots (weapon, tool, armor), Active Item quick-slots, Consumable use system, Item Combination crafting, Container inventories (chests, desks), Key Item tracking with loss protection, Document/Journal archive, and Collectible tracking with set bonuses.
|
||||||
|
|
||||||
|
### Weapons & Combat
|
||||||
|
Weapon Base with fire/holster/FX, Frame-driven Hitscan Firearms, Pellet-spread Shotguns, Melee combos with hit detection, Ammo pools with magazine management, Tactical & empty Reload, Recoil with recovery pattern, Damage Reception pipeline (resistance → shield → health), Hit Reactions (flinch/stagger/knockdown), Combat Feedback (hit markers, kill confirm), Death Cause tracking.
|
||||||
|
|
||||||
|
### AI & Enemies
|
||||||
|
Enemy Base character, AI Controller with Behavior Tree runner, State Machine (Patrol/Search/Combat/Flee), Alert System (suspicious → alerted → combat), Perception (sight, hearing, damage sense), Memory (last known locations, investigation), Behavior Variant selection (weighted random profiles), Patrol Path splines, Procedural Encounter spawners.
|
||||||
|
|
||||||
|
### Narrative & Dialogue
|
||||||
|
Narrative State Machine with chapter flags, Objective tracking (activate/complete/fail/branch), Dialogue Playback with VO & subtitles, Dialogue Choices with consequence branching, Cutscene Bridge with skip support, Lore Unlock system, Narrative Trigger Volumes, and a multi-factor Ending Accumulator.
|
||||||
|
|
||||||
|
### Menus & HUD (14 widgets)
|
||||||
|
Main Menu, Pause Menu, Settings Menu, Inventory Grid UI, Journal/Document Viewer, Diegetic HUD Frame (health, stress, stamina, compass), Interaction Prompt with hold progress, Notification Toasts, Objective Display, Screen Effect Controller (damage vignette, stress blur), Accessibility UI (subtitles, colorblind), Credits Screen, Splash Screen, Debug Menu. All coordinated by a menu-stack Subsystem.
|
||||||
|
|
||||||
|
### Save & Persistence
|
||||||
|
Save Manager Subsystem (auto-save, manual save, save slots), Checkpoint actors, Death Handling (normal death → respawn OR void-space death), Alt Death Space system, Persistent Corpses, World State Recorder (doors, items, enemies), Player Respawn with state restoration, Run History tracker, and `I_Persistable` interface for any actor.
|
||||||
|
|
||||||
|
### Adaptive Environment & Atmosphere
|
||||||
|
Dynamic Difficulty (metric-based scaling), Atmosphere State Controller (room tone, mood), Procedural Encounter generation, Adaptive Environment Director (room mutations, reality shifts), Light Events (flicker, dim, strobe, blackout), Memory Drift (visual/audio hallucinations tied to stress), Pacing Director (intensity bands: exploration → tension → combat → recovery), Playstyle Classifier (aggressive vs stealthy), Rare Events, Scare Events (anticipation → payoff → recovery).
|
||||||
|
|
||||||
|
### MetaSounds Audio
|
||||||
|
Single-entry-point Audio Manager Subsystem with 4 mix buses (SFX, Ambience, Music, Dialogue), Room Audio Zones that auto-switch reverb/occlusion on overlap, 8 gameplay-driven float parameters (heart rate, stress, fear, tension), and 7 acoustic presets (Outdoor, Small Room, Large Hall, Cave, Cathedral, Void, Padded Cell).
|
||||||
|
|
||||||
|
### Enhanced Input
|
||||||
|
Centralized Input Manager Subsystem with stack-based context switching, 5 priority-layered contexts (Default < Hiding < WristwatchUI < Inspection < UI), 30+ Input Actions, platform profiles (PC, Xbox, PS5), key rebinding with persistence, and input mode coordination with the UI layer.
|
||||||
|
|
||||||
|
### Multiplayer Networking
|
||||||
|
Server-authoritative replication model. All state mutations gated by `HasAuthority()`. Client→Server RPCs for requests, Server→Client `RepNotify` for state sync. Client-side prediction for cosmetic effects. Anti-cheat validation on all server-side state changes.
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
Central State Manager (42 exclusive action states + 18 overlay states). All systems query `IsActionPermitted(Tag)` instead of checking other systems directly. Gating rules configured in a designer-editable Data Asset (no Blueprint changes needed). Force Stack pattern for death/cutscenes/void space. GASP liaison for movement→action state mapping. Vital signal tracking (Normal → Critical).
|
||||||
|
|
||||||
|
### Planar Capture System (Phase 17)
|
||||||
|
Unified rendering pipeline for mirrors, portals, monitors, and horror surfaces. C++ core with Blueprint designer-interface. World Subsystem quality budget manager with 5-tier automatic quality scaling (Off → Low/256px/4fps → Medium/512px/15fps → High/1024px/30fps → Hero/2048px/60fps). Horror features: wrong-reflection actor swap, delayed frame ring buffer, 10-parameter MPC steam/dirt/darkness/text reveal material system. Render target pool with automatic reuse. Blueprint children: BP_Mirror, BP_Portal, BP_Monitor, BP_HorrorMirror, BP_FakeWindow. Integrates with scare events, audio manager, and state manager.
|
||||||
|
|
||||||
|
### Polish & Tooling
|
||||||
|
Tutorial system with contextual triggers, Loading screen with tips & progress, Credits screen, Analytics tracker (opt-in), Developer Cheat Manager (god mode, noclip, teleport, give item), Error Handler with crash recovery, FPS counter, Debug Menu (state viewer, AI debug, performance, audio).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example Game Prototype: "Project Void"
|
||||||
|
|
||||||
|
A complete psychological horror FPS prototype demonstrating every system. Set in an abandoned asylum. All game content lives in `Content/Game/` — never modifying `Content/Framework/`.
|
||||||
|
|
||||||
|
**Features built with this framework:**
|
||||||
|
- 11-level scene flow (Splash → Menu → Asylum Entry/WardA/WardB/Basement/WardensOffice → Void Space → 3 Endings → Credits)
|
||||||
|
- Full player character with 27 components wired together
|
||||||
|
- 18 pickup items (weapons, tools, consumables, keys, documents, collectibles)
|
||||||
|
- 4 held weapons (pistol, shotgun, flashlight, crowbar)
|
||||||
|
- 3 enemy types with full AI (Patient, Orderly, Void Shade)
|
||||||
|
- 4 main objectives + 3 side objectives with branching narrative
|
||||||
|
- 3 endings determined by player choices, collectibles, sanity, and playstyle
|
||||||
|
- 6 atmosphere profiles with room audio zones
|
||||||
|
- 6 scare events with anticipation/payoff/recovery cycles
|
||||||
|
- Void space alternate-reality death system
|
||||||
|
- 8 planar capture surfaces (mirrors, horror mirrors, security monitors, void portals, fake windows) with automatic quality scaling
|
||||||
|
- 18 pickup items (weapons, tools, consumables, keys, documents, collectibles)
|
||||||
|
- 4 held weapons (pistol, shotgun, flashlight, crowbar)
|
||||||
|
- 3 enemy types with full AI (Patient, Orderly, Void Shade)
|
||||||
|
- 4 main objectives + 3 side objectives with branching narrative
|
||||||
|
- 3 endings determined by player choices, collectibles, sanity, and playstyle
|
||||||
|
- 6 atmosphere profiles with room audio zones
|
||||||
|
- 6 scare events with anticipation/payoff/recovery cycles
|
||||||
|
- Void space alternate-reality death system
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Where to Find Things
|
||||||
|
|
||||||
|
| You want to... | Go here |
|
||||||
|
|---------------|---------|
|
||||||
|
| **Understand the whole framework** | [`UE5_Modular_Game_Framework.md`](UE5_Modular_Game_Framework.md) — the full design document |
|
||||||
|
| **See every Blueprint spec** | [`docs/blueprints/INDEX.md`](docs/blueprints/INDEX.md) — 135 system specs with wiring guides |
|
||||||
|
| **Learn the architecture** | [`docs/developer/architecture-overview.md`](docs/developer/architecture-overview.md) — layers, communication patterns |
|
||||||
|
| **Set up a project from scratch** | [`docs/developer/project-setup-migration.md`](docs/developer/project-setup-migration.md) |
|
||||||
|
| **Build the example game** | [`docs/game/GAMEINDEX.md`](docs/game/GAMEINDEX.md) — 170+ assets, 21-phase build order |
|
||||||
|
| **Read item build walkthroughs** | [`docs/game/item-flashlight.md`](docs/game/item-flashlight.md) — step-by-step Blueprint wiring |
|
||||||
|
| **Check animation requirements** | [`docs/architecture/animation-catalog.md`](docs/architecture/animation-catalog.md) |
|
||||||
|
| **Check sound/audio triggers** | [`docs/architecture/sound-catalog.md`](docs/architecture/sound-catalog.md) |
|
||||||
|
| **Understand input system** | [`docs/architecture/enhanced-input-system.md`](docs/architecture/enhanced-input-system.md) |
|
||||||
|
| **Understand state management** | [`docs/architecture/bpc-statemanager.md`](docs/architecture/bpc-statemanager.md) |
|
||||||
|
| **Understand multiplayer** | [`docs/architecture/multiplayer-networking.md`](docs/architecture/multiplayer-networking.md) |
|
||||||
|
| **Find Blueprint-only workarounds** | [`docs/architecture/blueprint-limitations-workarounds.md`](docs/architecture/blueprint-limitations-workarounds.md) |
|
||||||
|
| **See the HUD architecture** | [`docs/architecture/hud-overview.md`](docs/architecture/hud-overview.md) |
|
||||||
|
| **See the audio architecture** | [`docs/architecture/metasounds-audio-system.md`](docs/architecture/metasounds-audio-system.md) |
|
||||||
|
| **See the planar capture system** | [`docs/architecture/planar-capture-system.md`](docs/architecture/planar-capture-system.md) |
|
||||||
|
| **See C++/BP implementation status** | [`docs/checklists/cpp-blueprint-status.md`](docs/checklists/cpp-blueprint-status.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Engine:** Unreal Engine 5.5–5.7
|
||||||
|
- **Scripting:** Blueprint-Only (with 27 C++ support classes)
|
||||||
|
- **Locomotion:** GASP (Ground Animation Strafing Platform) — read-only, extended via notifies
|
||||||
|
- **Input:** Enhanced Input System — 30+ Input Actions, stack-based context switching
|
||||||
|
- **Assets:** `UPrimaryDataAsset` for all content definitions
|
||||||
|
- **State:** Central `BPC_StateManager` — 42 exclusive states, 18 overlay states, vital signals
|
||||||
|
- **Animation:** GASP Motion Matching + 14 animation notifies
|
||||||
|
- **Audio:** MetaSounds — 4 mix buses, room acoustic zones, gameplay-driven float parameters
|
||||||
|
- **Networking:** Server-authoritative with client prediction
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## By the Numbers
|
||||||
|
|
||||||
|
| What | How Many |
|
||||||
|
|------|:--------:|
|
||||||
|
| Blueprint system specs | 147 |
|
||||||
|
| C++ classes | 27 |
|
||||||
|
| Widget Blueprints | 14 |
|
||||||
|
| Blueprint Components | 80 |
|
||||||
|
| Blueprint Actors | 16 |
|
||||||
|
| Data Assets | 19 |
|
||||||
|
| Game Instance Subsystems | 7 |
|
||||||
|
| World Subsystems | 1 |
|
||||||
|
| Materials | 1 |
|
||||||
|
| Material Instances | 7 |
|
||||||
|
| Material Parameter Collections | 1 |
|
||||||
|
| Interfaces | 3 |
|
||||||
|
| Gameplay Tags | 334 |
|
||||||
|
| Input Actions | 30+ |
|
||||||
|
| Animation Notifies | 14 |
|
||||||
|
| Architecture docs | 9 |
|
||||||
|
| Developer docs | 13 |
|
||||||
|
| Game prototype docs | 22 |
|
||||||
|
| Example game assets | 170+ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*UE5 Modular Game Framework — Build faster. Stay modular. Never hardcode.*
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
||||||
// UE5 Modular Game Framework — Build Configuration
|
|
||||||
// Version 1.0 | 2026-05-20
|
|
||||||
|
|
||||||
using UnrealBuildTool;
|
|
||||||
|
|
||||||
public class Framework : ModuleRules
|
|
||||||
{
|
|
||||||
public Framework(ReadOnlyTargetRules Target) : base(Target)
|
|
||||||
{
|
|
||||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
|
||||||
|
|
||||||
PublicDependencyModuleNames.AddRange(new string[]
|
|
||||||
{
|
|
||||||
"Core",
|
|
||||||
"CoreUObject",
|
|
||||||
"Engine",
|
|
||||||
"GameplayTags",
|
|
||||||
"EnhancedInput",
|
|
||||||
"InputCore",
|
|
||||||
"UMG",
|
|
||||||
"Slate",
|
|
||||||
"SlateCore",
|
|
||||||
"AIModule",
|
|
||||||
"NavigationSystem",
|
|
||||||
"MotionWarping",
|
|
||||||
"PhysicsCore",
|
|
||||||
"DeveloperSettings",
|
|
||||||
"MetasoundEngine",
|
|
||||||
});
|
|
||||||
|
|
||||||
PrivateDependencyModuleNames.AddRange(new string[]
|
|
||||||
{
|
|
||||||
"GameplayTasks",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Uncomment if you need these optional modules:
|
|
||||||
// DynamicallyLoadedModuleNames.Add("OnlineSubsystem");
|
|
||||||
// DynamicallyLoadedModuleNames.Add("OnlineSubsystemSteam");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
15
Source/PG_Framework.Target.cs
Normal file
15
Source/PG_Framework.Target.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// // Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class PG_FrameworkTarget : TargetRules
|
||||||
|
{
|
||||||
|
public PG_FrameworkTarget(TargetInfo Target) : base(Target)
|
||||||
|
{
|
||||||
|
Type = TargetType.Game;
|
||||||
|
DefaultBuildSettings = BuildSettingsVersion.V6;
|
||||||
|
|
||||||
|
ExtraModuleNames.AddRange( new string[] { "PG_Framework" } );
|
||||||
|
}
|
||||||
|
}
|
||||||
46
Source/PG_Framework/PG_Framework.Build.cs
Normal file
46
Source/PG_Framework/PG_Framework.Build.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// // Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class PG_Framework : ModuleRules
|
||||||
|
{
|
||||||
|
public PG_Framework(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(new string[]
|
||||||
|
{
|
||||||
|
"Core",
|
||||||
|
"CoreUObject",
|
||||||
|
"Engine",
|
||||||
|
"Renderer",
|
||||||
|
"RenderCore",
|
||||||
|
"GameplayTags",
|
||||||
|
"EnhancedInput",
|
||||||
|
"InputCore",
|
||||||
|
"UMG",
|
||||||
|
"Slate",
|
||||||
|
"SlateCore",
|
||||||
|
"AIModule",
|
||||||
|
"NavigationSystem",
|
||||||
|
"MotionWarping",
|
||||||
|
"PhysicsCore",
|
||||||
|
"DeveloperSettings",
|
||||||
|
"MetasoundEngine",
|
||||||
|
});
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(new string[]
|
||||||
|
{
|
||||||
|
"GameplayTasks",
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Uncomment if you are using Slate UI
|
||||||
|
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
|
||||||
|
|
||||||
|
// Uncomment if you are using online features
|
||||||
|
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
|
||||||
|
|
||||||
|
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Source/PG_Framework/PG_Framework.cpp
Normal file
6
Source/PG_Framework/PG_Framework.cpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// // Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "PG_Framework.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, PG_Framework, "PG_Framework" );
|
||||||
7
Source/PG_Framework/PG_Framework.h
Normal file
7
Source/PG_Framework/PG_Framework.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// // Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Capture/PlanarCaptureCommon.h"
|
||||||
|
|
||||||
599
Source/PG_Framework/Private/Capture/BPC_PlanarCapture.cpp
Normal file
599
Source/PG_Framework/Private/Capture/BPC_PlanarCapture.cpp
Normal file
@@ -0,0 +1,599 @@
|
|||||||
|
// Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
// UE5 Modular Game Framework — BPC_PlanarCapture implementation
|
||||||
|
|
||||||
|
#include "Capture/BPC_PlanarCapture.h"
|
||||||
|
#include "Capture/BP_PlanarCaptureActor.h"
|
||||||
|
#include "Capture/SS_PlanarCaptureManager.h"
|
||||||
|
#include "Capture/PlanarCaptureCameraUtils.h"
|
||||||
|
#include "Components/SceneCaptureComponent2D.h"
|
||||||
|
#include "Engine/TextureRenderTarget2D.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
|
#include "Materials/MaterialParameterCollection.h"
|
||||||
|
#include "Materials/MaterialParameterCollectionInstance.h"
|
||||||
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
|
||||||
|
UBPC_PlanarCapture::UBPC_PlanarCapture()
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bCanEverTick = true;
|
||||||
|
PrimaryComponentTick.bStartWithTickEnabled = true;
|
||||||
|
PrimaryComponentTick.TickInterval = 0.0f; // We throttle internally via TimeSinceLastCapture
|
||||||
|
|
||||||
|
// Initialize default quality profiles
|
||||||
|
QualityProfiles.SetNum(4);
|
||||||
|
|
||||||
|
// Low — 256px, 4fps
|
||||||
|
QualityProfiles[0].RenderTargetSize = 256;
|
||||||
|
QualityProfiles[0].CaptureInterval = 0.25f;
|
||||||
|
QualityProfiles[0].bEnableShadows = false;
|
||||||
|
QualityProfiles[0].bEnablePostProcess = false;
|
||||||
|
QualityProfiles[0].bEnableFog = false;
|
||||||
|
QualityProfiles[0].bEnableBloom = false;
|
||||||
|
QualityProfiles[0].bEnableAO = false;
|
||||||
|
QualityProfiles[0].bEnableLumen = false;
|
||||||
|
QualityProfiles[0].bEnableMotionBlur = false;
|
||||||
|
QualityProfiles[0].bEnableClipPlane = false;
|
||||||
|
|
||||||
|
// Medium — 512px, 15fps
|
||||||
|
QualityProfiles[1].RenderTargetSize = 512;
|
||||||
|
QualityProfiles[1].CaptureInterval = 0.0667f;
|
||||||
|
QualityProfiles[1].bEnableShadows = true;
|
||||||
|
QualityProfiles[1].bEnablePostProcess = false;
|
||||||
|
QualityProfiles[1].bEnableFog = false;
|
||||||
|
QualityProfiles[1].bEnableBloom = false;
|
||||||
|
QualityProfiles[1].bEnableAO = false;
|
||||||
|
QualityProfiles[1].bEnableLumen = false;
|
||||||
|
QualityProfiles[1].bEnableMotionBlur = false;
|
||||||
|
QualityProfiles[1].bEnableClipPlane = true;
|
||||||
|
|
||||||
|
// High — 1024px, 30fps
|
||||||
|
QualityProfiles[2].RenderTargetSize = 1024;
|
||||||
|
QualityProfiles[2].CaptureInterval = 0.0333f;
|
||||||
|
QualityProfiles[2].bEnableShadows = true;
|
||||||
|
QualityProfiles[2].bEnablePostProcess = true;
|
||||||
|
QualityProfiles[2].bEnableFog = true;
|
||||||
|
QualityProfiles[2].bEnableBloom = false;
|
||||||
|
QualityProfiles[2].bEnableAO = true;
|
||||||
|
QualityProfiles[2].bEnableLumen = true;
|
||||||
|
QualityProfiles[2].bEnableMotionBlur = false;
|
||||||
|
QualityProfiles[2].bEnableClipPlane = true;
|
||||||
|
|
||||||
|
// Hero — 2048px, 60fps
|
||||||
|
QualityProfiles[3].RenderTargetSize = 2048;
|
||||||
|
QualityProfiles[3].CaptureInterval = 0.0167f;
|
||||||
|
QualityProfiles[3].bEnableShadows = true;
|
||||||
|
QualityProfiles[3].bEnablePostProcess = true;
|
||||||
|
QualityProfiles[3].bEnableFog = true;
|
||||||
|
QualityProfiles[3].bEnableBloom = true;
|
||||||
|
QualityProfiles[3].bEnableAO = true;
|
||||||
|
QualityProfiles[3].bEnableLumen = true;
|
||||||
|
QualityProfiles[3].bEnableMotionBlur = true;
|
||||||
|
QualityProfiles[3].bEnableClipPlane = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
CachedOwningActor = Cast<ABP_PlanarCaptureActor>(GetOwner());
|
||||||
|
if (!CachedOwningActor)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("BPC_PlanarCapture: Owner is not a BP_PlanarCaptureActor! Capture disabled."));
|
||||||
|
SetComponentTickEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache manager reference
|
||||||
|
if (UWorld* World = GetWorld())
|
||||||
|
{
|
||||||
|
CachedManager = World->GetSubsystem<USS_PlanarCaptureManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResolveSoftReferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
|
{
|
||||||
|
ShutdownCapture();
|
||||||
|
Super::EndPlay(EndPlayReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::TickComponent(float DeltaTime, ELevelTick TickType,
|
||||||
|
FActorComponentTickFunction* ThisTickFunction)
|
||||||
|
{
|
||||||
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||||
|
|
||||||
|
if (!bIsCapturing || !SceneCapture || CurrentQualityTier == EPlanarCaptureQualityTier::Off)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSinceLastCapture += DeltaTime;
|
||||||
|
|
||||||
|
if (TimeSinceLastCapture >= ActiveProfile.CaptureInterval)
|
||||||
|
{
|
||||||
|
TimeSinceLastCapture = 0.0f;
|
||||||
|
|
||||||
|
// Get viewer camera for transform computation
|
||||||
|
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
||||||
|
if (!PC || !PC->PlayerCameraManager)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FTransform ViewerTransform = FTransform(
|
||||||
|
PC->PlayerCameraManager->GetCameraRotation(),
|
||||||
|
PC->PlayerCameraManager->GetCameraLocation()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compute capture camera position
|
||||||
|
const FTransform CaptureTransform = ComputeCaptureCameraTransform(ViewerTransform);
|
||||||
|
SceneCapture->SetWorldTransform(CaptureTransform);
|
||||||
|
|
||||||
|
// Capture the scene
|
||||||
|
SceneCapture->CaptureScene();
|
||||||
|
|
||||||
|
// Push MPC parameters
|
||||||
|
if (CachedOwningActor && CachedOwningActor->SurfaceMPC)
|
||||||
|
{
|
||||||
|
PushMPCParameters(CachedOwningActor->SurfaceMPC);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnCaptureRendered.Broadcast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Public API
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
EPlanarCaptureInitResult UBPC_PlanarCapture::InitializeCapture()
|
||||||
|
{
|
||||||
|
if (!CachedOwningActor)
|
||||||
|
{
|
||||||
|
return EPlanarCaptureInitResult::InvalidSurfaceMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentQualityTier == EPlanarCaptureQualityTier::Off)
|
||||||
|
{
|
||||||
|
return EPlanarCaptureInitResult::Success; // Nothing to initialize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request render target from pool
|
||||||
|
const int32 ProfileIndex = static_cast<int32>(CurrentQualityTier) - 1; // Skip "Off" (0)
|
||||||
|
if (ProfileIndex < 0 || ProfileIndex >= QualityProfiles.Num())
|
||||||
|
{
|
||||||
|
return EPlanarCaptureInitResult::NoRenderTargetPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveProfile = QualityProfiles[ProfileIndex];
|
||||||
|
|
||||||
|
if (CachedManager)
|
||||||
|
{
|
||||||
|
CaptureRenderTarget = CachedManager->RequestRenderTarget(ActiveProfile.RenderTargetSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CaptureRenderTarget)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Error, TEXT("BPC_PlanarCapture: Failed to allocate render target (%dx%d)"),
|
||||||
|
ActiveProfile.RenderTargetSize, ActiveProfile.RenderTargetSize);
|
||||||
|
return EPlanarCaptureInitResult::NoRenderTargetPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateSceneCaptureComponent();
|
||||||
|
|
||||||
|
bIsCapturing = true;
|
||||||
|
OnCaptureInitialized.Broadcast(EPlanarCaptureInitResult::Success);
|
||||||
|
return EPlanarCaptureInitResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::ShutdownCapture()
|
||||||
|
{
|
||||||
|
bIsCapturing = false;
|
||||||
|
|
||||||
|
if (SceneCapture)
|
||||||
|
{
|
||||||
|
SceneCapture->DestroyComponent();
|
||||||
|
SceneCapture = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CaptureRenderTarget && CachedManager)
|
||||||
|
{
|
||||||
|
CachedManager->ReleaseRenderTarget(CaptureRenderTarget);
|
||||||
|
CaptureRenderTarget = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up frame ring buffer
|
||||||
|
for (UTextureRenderTarget2D* RT : FrameRingBuffer)
|
||||||
|
{
|
||||||
|
if (RT && CachedManager)
|
||||||
|
{
|
||||||
|
CachedManager->ReleaseRenderTarget(RT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FrameRingBuffer.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::ApplyQualityTier(EPlanarCaptureQualityTier Tier)
|
||||||
|
{
|
||||||
|
const EPlanarCaptureQualityTier OldTier = CurrentQualityTier;
|
||||||
|
CurrentQualityTier = Tier;
|
||||||
|
|
||||||
|
if (Tier == EPlanarCaptureQualityTier::Off)
|
||||||
|
{
|
||||||
|
ShutdownCapture();
|
||||||
|
OnCaptureQualityChanged.Broadcast(OldTier, Tier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If transitioning from Off to active, initialize
|
||||||
|
if (OldTier == EPlanarCaptureQualityTier::Off)
|
||||||
|
{
|
||||||
|
InitializeCapture();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Update active profile and resize render target if needed
|
||||||
|
const int32 ProfileIndex = static_cast<int32>(Tier) - 1;
|
||||||
|
if (ProfileIndex >= 0 && ProfileIndex < QualityProfiles.Num())
|
||||||
|
{
|
||||||
|
ActiveProfile = QualityProfiles[ProfileIndex];
|
||||||
|
ApplyShowFlags();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnCaptureQualityChanged.Broadcast(OldTier, Tier);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::CaptureNow()
|
||||||
|
{
|
||||||
|
if (!SceneCapture)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
||||||
|
if (!PC || !PC->PlayerCameraManager)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FTransform ViewerTransform = FTransform(
|
||||||
|
PC->PlayerCameraManager->GetCameraRotation(),
|
||||||
|
PC->PlayerCameraManager->GetCameraLocation()
|
||||||
|
);
|
||||||
|
|
||||||
|
const FTransform CaptureTransform = ComputeCaptureCameraTransform(ViewerTransform);
|
||||||
|
SceneCapture->SetWorldTransform(CaptureTransform);
|
||||||
|
SceneCapture->CaptureScene();
|
||||||
|
|
||||||
|
TimeSinceLastCapture = 0.0f;
|
||||||
|
OnCaptureRendered.Broadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::ActivateHorrorReflection()
|
||||||
|
{
|
||||||
|
// Save current ShowOnly list
|
||||||
|
SavedShowOnlyActors.Empty();
|
||||||
|
for (const FPlanarCaptureActorListEntry& Entry : ShowOnlyActors)
|
||||||
|
{
|
||||||
|
SavedShowOnlyActors.Add(Entry.Actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear and set wrong reflection actor
|
||||||
|
ShowOnlyActors.Empty();
|
||||||
|
if (WrongReflectionActor.IsValid())
|
||||||
|
{
|
||||||
|
FPlanarCaptureActorListEntry NewEntry;
|
||||||
|
NewEntry.Actor = WrongReflectionActor;
|
||||||
|
NewEntry.bActive = true;
|
||||||
|
ShowOnlyActors.Add(NewEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateActorLists();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::DeactivateHorrorReflection()
|
||||||
|
{
|
||||||
|
ShowOnlyActors.Empty();
|
||||||
|
for (const TSoftObjectPtr<AActor>& SavedActor : SavedShowOnlyActors)
|
||||||
|
{
|
||||||
|
FPlanarCaptureActorListEntry NewEntry;
|
||||||
|
NewEntry.Actor = SavedActor;
|
||||||
|
NewEntry.bActive = true;
|
||||||
|
ShowOnlyActors.Add(NewEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
SavedShowOnlyActors.Empty();
|
||||||
|
UpdateActorLists();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::PushDelayedFrame()
|
||||||
|
{
|
||||||
|
if (ActiveProfile.DelayedFrameCount <= 0 || !CaptureRenderTarget)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure ring buffer is properly sized
|
||||||
|
while (FrameRingBuffer.Num() < ActiveProfile.DelayedFrameCount)
|
||||||
|
{
|
||||||
|
UTextureRenderTarget2D* NewRT = nullptr;
|
||||||
|
if (CachedManager)
|
||||||
|
{
|
||||||
|
NewRT = CachedManager->RequestRenderTarget(ActiveProfile.RenderTargetSize);
|
||||||
|
}
|
||||||
|
FrameRingBuffer.Add(NewRT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy current render target to ring buffer slot
|
||||||
|
RingBufferWriteIndex = (RingBufferWriteIndex + 1) % FrameRingBuffer.Num();
|
||||||
|
|
||||||
|
// Push the oldest frame to the material as the delayed reflection
|
||||||
|
if (FrameRingBuffer.IsValidIndex(RingBufferWriteIndex) && FrameRingBuffer[RingBufferWriteIndex])
|
||||||
|
{
|
||||||
|
// The material samples the delayed frame via the MPC texture parameter
|
||||||
|
// This is set in PushMPCParameters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::SetScriptedPriority(float Priority)
|
||||||
|
{
|
||||||
|
ScriptedPriorityOverride = FMath::Clamp(Priority, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::PushMPCParameters(UMaterialParameterCollection* MPC)
|
||||||
|
{
|
||||||
|
if (!MPC)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UMaterialParameterCollectionInstance* MPCInstance = GetWorld()->GetParameterCollectionInstance(MPC);
|
||||||
|
if (!MPCInstance)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push standard parameters
|
||||||
|
// These are used by M_CaptureSurface_Master to drive steam, dirt, horror effects
|
||||||
|
MPCInstance->SetScalarParameterValue(FName("SteamIntensity"), 0.0f);
|
||||||
|
MPCInstance->SetScalarParameterValue(FName("DirtOpacity"), 0.0f);
|
||||||
|
MPCInstance->SetScalarParameterValue(FName("CondensationFlow"), 0.0f);
|
||||||
|
MPCInstance->SetScalarParameterValue(FName("DistortionAmplitude"), 0.0f);
|
||||||
|
MPCInstance->SetScalarParameterValue(FName("MirrorDarkness"), 0.0f);
|
||||||
|
MPCInstance->SetScalarParameterValue(FName("WrongReflectionBlend"), 0.0f);
|
||||||
|
MPCInstance->SetScalarParameterValue(FName("TextRevealProgress"), 0.0f);
|
||||||
|
MPCInstance->SetScalarParameterValue(FName("SteamEmissiveIntensity"), 0.0f);
|
||||||
|
MPCInstance->SetScalarParameterValue(FName("DelayedReflectionBlend"), 0.0f);
|
||||||
|
MPCInstance->SetScalarParameterValue(FName("SurfaceAge"), 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Compute
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
FTransform UBPC_PlanarCapture::ComputeCaptureCameraTransform(const FTransform& ViewerCameraTransform) const
|
||||||
|
{
|
||||||
|
switch (CaptureMode)
|
||||||
|
{
|
||||||
|
case EPlanarCaptureMode::Mirror:
|
||||||
|
case EPlanarCaptureMode::HorrorMirror:
|
||||||
|
{
|
||||||
|
const FTransform SurfaceTransform = CachedOwningActor
|
||||||
|
? CachedOwningActor->GetActorTransform()
|
||||||
|
: FTransform::Identity;
|
||||||
|
return UPlanarCaptureCameraUtils::ComputeMirroredTransform(ViewerCameraTransform, SurfaceTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
case EPlanarCaptureMode::Portal:
|
||||||
|
case EPlanarCaptureMode::HorrorPortal:
|
||||||
|
{
|
||||||
|
if (LinkedTargetSurface.IsValid())
|
||||||
|
{
|
||||||
|
const FTransform SourceTransform = CachedOwningActor
|
||||||
|
? CachedOwningActor->GetActorTransform()
|
||||||
|
: FTransform::Identity;
|
||||||
|
const FTransform TargetTransform = LinkedTargetSurface->GetActorTransform();
|
||||||
|
return UPlanarCaptureCameraUtils::ComputePortalTransform(
|
||||||
|
ViewerCameraTransform, SourceTransform, TargetTransform);
|
||||||
|
}
|
||||||
|
return ViewerCameraTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EPlanarCaptureMode::Monitor:
|
||||||
|
{
|
||||||
|
if (FixedCameraActor.IsValid())
|
||||||
|
{
|
||||||
|
return FixedCameraActor->GetActorTransform();
|
||||||
|
}
|
||||||
|
return ViewerCameraTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EPlanarCaptureMode::FakeWindow:
|
||||||
|
{
|
||||||
|
// Fake windows use a fixed offset from the surface
|
||||||
|
const FTransform SurfaceTransform = CachedOwningActor
|
||||||
|
? CachedOwningActor->GetActorTransform()
|
||||||
|
: FTransform::Identity;
|
||||||
|
const FVector SurfaceNormal = SurfaceTransform.GetUnitAxis(EAxis::Z);
|
||||||
|
return FTransform(SurfaceTransform.GetRotation(),
|
||||||
|
SurfaceTransform.GetLocation() + SurfaceNormal * 200.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ViewerCameraTransform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FPlanarCaptureScore UBPC_PlanarCapture::GetCurrentScore() const
|
||||||
|
{
|
||||||
|
FPlanarCaptureScore Score;
|
||||||
|
|
||||||
|
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
||||||
|
if (!PC || !PC->PlayerCameraManager || !CachedOwningActor)
|
||||||
|
{
|
||||||
|
return Score;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FVector ViewerPos = PC->PlayerCameraManager->GetCameraLocation();
|
||||||
|
const FTransform ViewerTransform = FTransform(
|
||||||
|
PC->PlayerCameraManager->GetCameraRotation(),
|
||||||
|
ViewerPos
|
||||||
|
);
|
||||||
|
|
||||||
|
const FVector SurfacePos = CachedOwningActor->GetActorLocation();
|
||||||
|
const FVector SurfaceNormal = CachedOwningActor->GetActorUpVector();
|
||||||
|
|
||||||
|
Score.DistanceToViewer = FVector::Dist(ViewerPos, SurfacePos);
|
||||||
|
Score.FacingAngle = FVector::DotProduct(
|
||||||
|
PC->PlayerCameraManager->GetCameraRotation().Vector(),
|
||||||
|
SurfaceNormal
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compute screen coverage using bounding box
|
||||||
|
const FBox SurfaceBounds = CachedOwningActor->GetComponentsBoundingBox();
|
||||||
|
Score.ScreenCoverage = UPlanarCaptureCameraUtils::ComputeScreenCoverage(
|
||||||
|
SurfaceBounds, ViewerTransform, CaptureFOV, 1920, 1080);
|
||||||
|
|
||||||
|
Score.bInFrustum = UPlanarCaptureCameraUtils::IsSurfaceVisibleToViewer(
|
||||||
|
SurfaceBounds, ViewerTransform, CaptureFOV, 1.777f, 10.0f, MaxViewDistance);
|
||||||
|
|
||||||
|
Score.ScriptedPriority = ScriptedPriorityOverride;
|
||||||
|
|
||||||
|
Score.CompositeScore = UPlanarCaptureCameraUtils::ComputeCompositeScore(
|
||||||
|
Score.ScreenCoverage, Score.FacingAngle, Score.DistanceToViewer,
|
||||||
|
CachedManager ? CachedManager->MaxCaptureDistance : 10000.0f,
|
||||||
|
Score.ScriptedPriority);
|
||||||
|
|
||||||
|
return Score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Internal Methods
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::CreateSceneCaptureComponent()
|
||||||
|
{
|
||||||
|
if (!GetOwner())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneCapture = NewObject<USceneCaptureComponent2D>(GetOwner(), USceneCaptureComponent2D::StaticClass());
|
||||||
|
if (!SceneCapture)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneCapture->RegisterComponent();
|
||||||
|
SceneCapture->AttachToComponent(GetOwner()->GetRootComponent(),
|
||||||
|
FAttachmentTransformRules::SnapToTargetNotIncludingScale);
|
||||||
|
|
||||||
|
SceneCapture->TextureTarget = CaptureRenderTarget;
|
||||||
|
SceneCapture->FOVAngle = CaptureFOV;
|
||||||
|
SceneCapture->bCaptureEveryFrame = false; // We control capture timing
|
||||||
|
SceneCapture->bCaptureOnMovement = false;
|
||||||
|
|
||||||
|
// Configure for planar surface capture
|
||||||
|
SceneCapture->ProjectionType = ECameraProjectionMode::Perspective;
|
||||||
|
SceneCapture->PrimitiveRenderMode = ESceneCapturePrimitiveRenderMode::PRM_UseShowOnlyList;
|
||||||
|
|
||||||
|
ApplyShowFlags();
|
||||||
|
UpdateActorLists();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::ApplyShowFlags()
|
||||||
|
{
|
||||||
|
if (!SceneCapture)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply show flags based on active quality profile
|
||||||
|
// UE5.7: FEngineShowFlags members are set directly, not via setter methods
|
||||||
|
if (!ActiveProfile.bEnableShadows)
|
||||||
|
{
|
||||||
|
SceneCapture->ShowFlags.DynamicShadows = false;
|
||||||
|
SceneCapture->ShowFlags.ContactShadows = false;
|
||||||
|
}
|
||||||
|
if (!ActiveProfile.bEnableFog)
|
||||||
|
{
|
||||||
|
SceneCapture->ShowFlags.Fog = 0;
|
||||||
|
}
|
||||||
|
if (!ActiveProfile.bEnableBloom)
|
||||||
|
{
|
||||||
|
SceneCapture->ShowFlags.Bloom = 0;
|
||||||
|
}
|
||||||
|
if (!ActiveProfile.bEnableAO)
|
||||||
|
{
|
||||||
|
SceneCapture->ShowFlags.AmbientOcclusion = 0;
|
||||||
|
SceneCapture->ShowFlags.ScreenSpaceAO = 0;
|
||||||
|
}
|
||||||
|
if (!ActiveProfile.bEnableMotionBlur)
|
||||||
|
{
|
||||||
|
SceneCapture->ShowFlags.MotionBlur = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lumen is controlled via post-process settings on the capture component
|
||||||
|
SceneCapture->PostProcessSettings.bOverride_DynamicGlobalIlluminationMethod = true;
|
||||||
|
SceneCapture->PostProcessSettings.DynamicGlobalIlluminationMethod = ActiveProfile.bEnableLumen
|
||||||
|
? EDynamicGlobalIlluminationMethod::Lumen
|
||||||
|
: EDynamicGlobalIlluminationMethod::None;
|
||||||
|
|
||||||
|
// Post-process toggle
|
||||||
|
SceneCapture->PostProcessSettings.bOverride_BloomIntensity = !ActiveProfile.bEnablePostProcess;
|
||||||
|
SceneCapture->PostProcessBlendWeight = ActiveProfile.bEnablePostProcess ? 1.0f : 0.0f;
|
||||||
|
|
||||||
|
// Disable Lumen reflections on the capture itself to prevent double-rendering
|
||||||
|
SceneCapture->PostProcessSettings.bOverride_ReflectionMethod = true;
|
||||||
|
SceneCapture->PostProcessSettings.ReflectionMethod = EReflectionMethod::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::UpdateActorLists()
|
||||||
|
{
|
||||||
|
if (!SceneCapture)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneCapture->ShowOnlyActors.Empty();
|
||||||
|
SceneCapture->HiddenActors.Empty();
|
||||||
|
|
||||||
|
for (const FPlanarCaptureActorListEntry& Entry : ShowOnlyActors)
|
||||||
|
{
|
||||||
|
if (Entry.bActive && Entry.Actor.IsValid())
|
||||||
|
{
|
||||||
|
SceneCapture->ShowOnlyActors.Add(Entry.Actor.Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const FPlanarCaptureActorListEntry& Entry : HiddenActors)
|
||||||
|
{
|
||||||
|
if (Entry.bActive && Entry.Actor.IsValid())
|
||||||
|
{
|
||||||
|
SceneCapture->HiddenActors.Add(Entry.Actor.Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_PlanarCapture::ResolveSoftReferences()
|
||||||
|
{
|
||||||
|
if (SurfaceMeshComponent.IsValid())
|
||||||
|
{
|
||||||
|
// Surface mesh is resolved — ready for capture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FPlane UBPC_PlanarCapture::GetSurfacePlane() const
|
||||||
|
{
|
||||||
|
if (!CachedOwningActor)
|
||||||
|
{
|
||||||
|
return FPlane(FVector::ZeroVector, FVector::UpVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FVector SurfaceLocation = CachedOwningActor->GetActorLocation();
|
||||||
|
const FVector SurfaceNormal = CachedOwningActor->GetActorUpVector();
|
||||||
|
|
||||||
|
return FPlane(SurfaceLocation, SurfaceNormal);
|
||||||
|
}
|
||||||
241
Source/PG_Framework/Private/Capture/BP_PlanarCaptureActor.cpp
Normal file
241
Source/PG_Framework/Private/Capture/BP_PlanarCaptureActor.cpp
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
// Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
// UE5 Modular Game Framework — BP_PlanarCaptureActor implementation
|
||||||
|
|
||||||
|
#include "Capture/BP_PlanarCaptureActor.h"
|
||||||
|
#include "Capture/BPC_PlanarCapture.h"
|
||||||
|
#include "Capture/SS_PlanarCaptureManager.h"
|
||||||
|
#include "Components/StaticMeshComponent.h"
|
||||||
|
#include "Components/BoxComponent.h"
|
||||||
|
#include "Materials/MaterialInstanceDynamic.h"
|
||||||
|
#include "Materials/MaterialParameterCollection.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "Net/UnrealNetwork.h"
|
||||||
|
|
||||||
|
ABP_PlanarCaptureActor::ABP_PlanarCaptureActor()
|
||||||
|
{
|
||||||
|
PrimaryActorTick.bCanEverTick = false;
|
||||||
|
|
||||||
|
// Create root component
|
||||||
|
USceneComponent* Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
|
||||||
|
SetRootComponent(Root);
|
||||||
|
|
||||||
|
// Surface mesh
|
||||||
|
SurfaceMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("SurfaceMesh"));
|
||||||
|
SurfaceMesh->SetupAttachment(Root);
|
||||||
|
SurfaceMesh->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||||
|
SurfaceMesh->SetCollisionResponseToAllChannels(ECR_Ignore);
|
||||||
|
SurfaceMesh->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
|
||||||
|
|
||||||
|
// Proximity trigger for quality scoring
|
||||||
|
ProximityTrigger = CreateDefaultSubobject<UBoxComponent>(TEXT("ProximityTrigger"));
|
||||||
|
ProximityTrigger->SetupAttachment(Root);
|
||||||
|
ProximityTrigger->SetBoxExtent(FVector(500.0f, 500.0f, 500.0f));
|
||||||
|
ProximityTrigger->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||||
|
ProximityTrigger->SetCollisionResponseToAllChannels(ECR_Ignore);
|
||||||
|
ProximityTrigger->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
|
||||||
|
|
||||||
|
// Capture component
|
||||||
|
CaptureComponent = CreateDefaultSubobject<UBPC_PlanarCapture>(TEXT("CaptureComponent"));
|
||||||
|
|
||||||
|
// Replication
|
||||||
|
bReplicates = true;
|
||||||
|
bAlwaysRelevant = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
// Bind overlap events
|
||||||
|
if (ProximityTrigger)
|
||||||
|
{
|
||||||
|
ProximityTrigger->OnComponentBeginOverlap.AddDynamic(this, &ABP_PlanarCaptureActor::OnProximityBeginOverlap);
|
||||||
|
ProximityTrigger->OnComponentEndOverlap.AddDynamic(this, &ABP_PlanarCaptureActor::OnProximityEndOverlap);
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateMaterialInstance();
|
||||||
|
RegisterWithManager();
|
||||||
|
|
||||||
|
if (bStartEnabled)
|
||||||
|
{
|
||||||
|
EnableSurface();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
|
{
|
||||||
|
// Unregister from manager
|
||||||
|
if (UWorld* World = GetWorld())
|
||||||
|
{
|
||||||
|
if (USS_PlanarCaptureManager* Manager = World->GetSubsystem<USS_PlanarCaptureManager>())
|
||||||
|
{
|
||||||
|
Manager->UnregisterSurface(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Super::EndPlay(EndPlayReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Public API
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::EnableSurface()
|
||||||
|
{
|
||||||
|
if (bIsActive)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bIsActive = true;
|
||||||
|
bRepIsActive = true;
|
||||||
|
|
||||||
|
if (CaptureComponent)
|
||||||
|
{
|
||||||
|
CaptureComponent->InitializeCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::DisableSurface()
|
||||||
|
{
|
||||||
|
if (!bIsActive)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bIsActive = false;
|
||||||
|
bRepIsActive = false;
|
||||||
|
|
||||||
|
if (CaptureComponent)
|
||||||
|
{
|
||||||
|
CaptureComponent->ShutdownCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::SetCaptureMode(EPlanarCaptureMode NewMode)
|
||||||
|
{
|
||||||
|
if (CaptureComponent)
|
||||||
|
{
|
||||||
|
const bool WasCapturing = CaptureComponent->bIsCapturing;
|
||||||
|
if (WasCapturing)
|
||||||
|
{
|
||||||
|
CaptureComponent->ShutdownCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptureComponent->CaptureMode = NewMode;
|
||||||
|
|
||||||
|
if (WasCapturing)
|
||||||
|
{
|
||||||
|
CaptureComponent->InitializeCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
OnModeChanged.Broadcast(NewMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::SetSurfaceMaterial(UMaterialInterface* NewMaterial)
|
||||||
|
{
|
||||||
|
if (SurfaceMesh && NewMaterial)
|
||||||
|
{
|
||||||
|
SurfaceMaterialInstance = SurfaceMesh->CreateDynamicMaterialInstance(0, NewMaterial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::SetSurfaceMPCParameter(FName ParameterName, float Value)
|
||||||
|
{
|
||||||
|
UMaterialInstanceDynamic* MID = SurfaceMaterialInstance;
|
||||||
|
if (!MID)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MID->SetScalarParameterValue(ParameterName, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::DestroySurface()
|
||||||
|
{
|
||||||
|
if (!bDestructible)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisableSurface();
|
||||||
|
|
||||||
|
OnSurfaceDestroyed.Broadcast(this);
|
||||||
|
|
||||||
|
// Blueprint child handles visual destruction (particles, sound via SS_AudioManager, etc.)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Overlap Events
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::OnProximityBeginOverlap(UPrimitiveComponent* OverlappedComponent,
|
||||||
|
AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
|
||||||
|
bool bFromSweep, const FHitResult& SweepResult)
|
||||||
|
{
|
||||||
|
// Player entered proximity — notify capture component for quality scoring
|
||||||
|
if (CaptureComponent && OtherActor && OtherActor->ActorHasTag(FName("Player")))
|
||||||
|
{
|
||||||
|
CaptureComponent->SetScriptedPriority(0.3f); // Slight priority boost when player is near
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::OnProximityEndOverlap(UPrimitiveComponent* OverlappedComponent,
|
||||||
|
AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
|
||||||
|
{
|
||||||
|
if (CaptureComponent && OtherActor && OtherActor->ActorHasTag(FName("Player")))
|
||||||
|
{
|
||||||
|
CaptureComponent->SetScriptedPriority(0.0f); // Reset priority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Replication
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||||
|
{
|
||||||
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||||
|
|
||||||
|
DOREPLIFETIME(ABP_PlanarCaptureActor, bRepIsActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::OnRep_IsActive()
|
||||||
|
{
|
||||||
|
if (bRepIsActive && !bIsActive)
|
||||||
|
{
|
||||||
|
EnableSurface();
|
||||||
|
}
|
||||||
|
else if (!bRepIsActive && bIsActive)
|
||||||
|
{
|
||||||
|
DisableSurface();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Internal
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::RegisterWithManager()
|
||||||
|
{
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
if (!World)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
USS_PlanarCaptureManager* Manager = World->GetSubsystem<USS_PlanarCaptureManager>();
|
||||||
|
if (Manager)
|
||||||
|
{
|
||||||
|
Manager->RegisterSurface(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ABP_PlanarCaptureActor::CreateMaterialInstance()
|
||||||
|
{
|
||||||
|
if (SurfaceMesh && SurfaceMesh->GetMaterial(0))
|
||||||
|
{
|
||||||
|
SurfaceMaterialInstance = SurfaceMesh->CreateDynamicMaterialInstance(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
242
Source/PG_Framework/Private/Capture/PlanarCaptureCameraUtils.cpp
Normal file
242
Source/PG_Framework/Private/Capture/PlanarCaptureCameraUtils.cpp
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
// Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
// UE5 Modular Game Framework — PlanarCaptureCameraUtils implementation
|
||||||
|
|
||||||
|
#include "Capture/PlanarCaptureCameraUtils.h"
|
||||||
|
#include "Kismet/KismetMathLibrary.h"
|
||||||
|
|
||||||
|
FTransform UPlanarCaptureCameraUtils::ComputeMirroredTransform(
|
||||||
|
const FTransform& ViewerCameraTransform,
|
||||||
|
const FTransform& MirrorPlaneTransform)
|
||||||
|
{
|
||||||
|
// Mirror plane normal (local Z axis of the mirror surface)
|
||||||
|
const FVector MirrorNormal = MirrorPlaneTransform.GetUnitAxis(EAxis::Z);
|
||||||
|
const FVector MirrorLocation = MirrorPlaneTransform.GetLocation();
|
||||||
|
|
||||||
|
// Reflect viewer position across the mirror plane
|
||||||
|
const FVector ViewerPos = ViewerCameraTransform.GetLocation();
|
||||||
|
const FVector ToViewer = ViewerPos - MirrorLocation;
|
||||||
|
const float DistanceToPlane = FVector::DotProduct(ToViewer, MirrorNormal);
|
||||||
|
const FVector ReflectedPosition = ViewerPos - 2.0f * DistanceToPlane * MirrorNormal;
|
||||||
|
|
||||||
|
// Reflect viewer rotation across the mirror plane
|
||||||
|
const FVector ViewerForward = ViewerCameraTransform.GetUnitAxis(EAxis::X);
|
||||||
|
const FVector ReflectedForward = ViewerForward - 2.0f * FVector::DotProduct(ViewerForward, MirrorNormal) * MirrorNormal;
|
||||||
|
|
||||||
|
const FVector ViewerUp = ViewerCameraTransform.GetUnitAxis(EAxis::Z);
|
||||||
|
const FVector ReflectedUp = ViewerUp - 2.0f * FVector::DotProduct(ViewerUp, MirrorNormal) * MirrorNormal;
|
||||||
|
|
||||||
|
const FRotator ReflectedRotation = FRotationMatrix::MakeFromXZ(ReflectedForward, ReflectedUp).Rotator();
|
||||||
|
|
||||||
|
return FTransform(ReflectedRotation, ReflectedPosition, ViewerCameraTransform.GetScale3D());
|
||||||
|
}
|
||||||
|
|
||||||
|
FTransform UPlanarCaptureCameraUtils::ComputePortalTransform(
|
||||||
|
const FTransform& ViewerCameraTransform,
|
||||||
|
const FTransform& SourceSurfaceTransform,
|
||||||
|
const FTransform& TargetSurfaceTransform)
|
||||||
|
{
|
||||||
|
// Compute viewer position relative to source surface
|
||||||
|
const FVector ViewerPos = ViewerCameraTransform.GetLocation();
|
||||||
|
const FVector RelativePos = SourceSurfaceTransform.InverseTransformPosition(ViewerPos);
|
||||||
|
|
||||||
|
// Compute viewer rotation relative to source surface
|
||||||
|
const FQuat ViewerRot = ViewerCameraTransform.GetRotation();
|
||||||
|
const FQuat SourceRotInv = SourceSurfaceTransform.GetRotation().Inverse();
|
||||||
|
const FQuat RelativeRot = SourceRotInv * ViewerRot;
|
||||||
|
|
||||||
|
// Apply relative transform to target surface
|
||||||
|
const FVector TargetPos = TargetSurfaceTransform.TransformPosition(RelativePos);
|
||||||
|
const FQuat TargetRot = TargetSurfaceTransform.GetRotation() * RelativeRot;
|
||||||
|
|
||||||
|
return FTransform(TargetRot, TargetPos, ViewerCameraTransform.GetScale3D());
|
||||||
|
}
|
||||||
|
|
||||||
|
FMatrix UPlanarCaptureCameraUtils::ComputeObliqueProjectionMatrix(
|
||||||
|
float FOV,
|
||||||
|
float AspectRatio,
|
||||||
|
float NearPlane,
|
||||||
|
float FarPlane,
|
||||||
|
const FPlane& ClipPlane,
|
||||||
|
const FTransform& SurfaceTransform)
|
||||||
|
{
|
||||||
|
// Build standard perspective projection matrix
|
||||||
|
FMatrix ProjectionMatrix;
|
||||||
|
const float HalfFOVRad = FMath::DegreesToRadians(FOV * 0.5f);
|
||||||
|
const float YScale = 1.0f / FMath::Tan(HalfFOVRad);
|
||||||
|
const float XScale = YScale / AspectRatio;
|
||||||
|
|
||||||
|
ProjectionMatrix.SetIdentity();
|
||||||
|
ProjectionMatrix.M[0][0] = XScale;
|
||||||
|
ProjectionMatrix.M[1][1] = YScale;
|
||||||
|
ProjectionMatrix.M[2][2] = (NearPlane == FarPlane) ? 0.0f : FarPlane / (FarPlane - NearPlane);
|
||||||
|
ProjectionMatrix.M[2][3] = 1.0f;
|
||||||
|
ProjectionMatrix.M[3][2] = (NearPlane == FarPlane) ? NearPlane : -NearPlane * FarPlane / (FarPlane - NearPlane);
|
||||||
|
ProjectionMatrix.M[3][3] = 0.0f;
|
||||||
|
|
||||||
|
// Transform clip plane into view space (inverse of surface transform)
|
||||||
|
const FMatrix ViewMatrix = SurfaceTransform.ToMatrixNoScale().Inverse();
|
||||||
|
const FPlane ViewSpaceClipPlane = ClipPlane.TransformBy(ViewMatrix);
|
||||||
|
|
||||||
|
// Compute oblique near-plane using the standard technique:
|
||||||
|
// Calculate the clip-space corner that maximizes the dot product with the plane normal.
|
||||||
|
// Then modify the third row of the projection matrix to make the near plane pass through the clip plane.
|
||||||
|
|
||||||
|
FVector4 ClipPlaneVec(ViewSpaceClipPlane.X, ViewSpaceClipPlane.Y, ViewSpaceClipPlane.Z, ViewSpaceClipPlane.W);
|
||||||
|
|
||||||
|
// Find the vertex of the view frustum in clip space that is closest to the plane
|
||||||
|
FVector4 Q;
|
||||||
|
Q.X = (FMath::Sign(ClipPlaneVec.X) + ProjectionMatrix.M[0][2]) / ProjectionMatrix.M[0][0];
|
||||||
|
Q.Y = (FMath::Sign(ClipPlaneVec.Y) + ProjectionMatrix.M[1][2]) / ProjectionMatrix.M[1][1];
|
||||||
|
Q.Z = 1.0f;
|
||||||
|
Q.W = (1.0f + ProjectionMatrix.M[2][2]) / ProjectionMatrix.M[3][2];
|
||||||
|
|
||||||
|
// Scale the clip plane so its distance from the origin matches the Q vertex
|
||||||
|
const float DotVal = ClipPlaneVec.X * Q.X + ClipPlaneVec.Y * Q.Y + ClipPlaneVec.Z * Q.Z + ClipPlaneVec.W * Q.W;
|
||||||
|
const float Scale = 2.0f / DotVal;
|
||||||
|
ClipPlaneVec *= Scale;
|
||||||
|
|
||||||
|
// Replace the third row of the projection matrix with the clip plane
|
||||||
|
ProjectionMatrix.M[2][0] = ClipPlaneVec.X;
|
||||||
|
ProjectionMatrix.M[2][1] = ClipPlaneVec.Y;
|
||||||
|
ProjectionMatrix.M[2][2] = ClipPlaneVec.Z;
|
||||||
|
ProjectionMatrix.M[2][3] = ClipPlaneVec.W;
|
||||||
|
|
||||||
|
return ProjectionMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
float UPlanarCaptureCameraUtils::ComputeScreenCoverage(
|
||||||
|
const FBox& SurfaceBounds,
|
||||||
|
const FTransform& ViewerTransform,
|
||||||
|
float ViewerFOV,
|
||||||
|
int32 ScreenWidth,
|
||||||
|
int32 ScreenHeight)
|
||||||
|
{
|
||||||
|
if (!SurfaceBounds.IsValid || ScreenWidth <= 0 || ScreenHeight <= 0)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float HalfFOVRad = FMath::DegreesToRadians(ViewerFOV * 0.5f);
|
||||||
|
const FVector ViewerPos = ViewerTransform.GetLocation();
|
||||||
|
const FVector ViewerForward = ViewerTransform.GetUnitAxis(EAxis::X);
|
||||||
|
|
||||||
|
// Project all 8 corners of the bounding box to screen space
|
||||||
|
const FVector Corners[8] = {
|
||||||
|
FVector(SurfaceBounds.Min.X, SurfaceBounds.Min.Y, SurfaceBounds.Min.Z),
|
||||||
|
FVector(SurfaceBounds.Min.X, SurfaceBounds.Min.Y, SurfaceBounds.Max.Z),
|
||||||
|
FVector(SurfaceBounds.Min.X, SurfaceBounds.Max.Y, SurfaceBounds.Min.Z),
|
||||||
|
FVector(SurfaceBounds.Min.X, SurfaceBounds.Max.Y, SurfaceBounds.Max.Z),
|
||||||
|
FVector(SurfaceBounds.Max.X, SurfaceBounds.Min.Y, SurfaceBounds.Min.Z),
|
||||||
|
FVector(SurfaceBounds.Max.X, SurfaceBounds.Min.Y, SurfaceBounds.Max.Z),
|
||||||
|
FVector(SurfaceBounds.Max.X, SurfaceBounds.Max.Y, SurfaceBounds.Min.Z),
|
||||||
|
FVector(SurfaceBounds.Max.X, SurfaceBounds.Max.Y, SurfaceBounds.Max.Z),
|
||||||
|
};
|
||||||
|
|
||||||
|
float MinScreenX = FLT_MAX, MinScreenY = FLT_MAX;
|
||||||
|
float MaxScreenX = -FLT_MAX, MaxScreenY = -FLT_MAX;
|
||||||
|
int32 BehindCameraCount = 0;
|
||||||
|
|
||||||
|
for (const FVector& Corner : Corners)
|
||||||
|
{
|
||||||
|
const FVector ToCorner = Corner - ViewerPos;
|
||||||
|
const float DotForward = FVector::DotProduct(ToCorner, ViewerForward);
|
||||||
|
|
||||||
|
if (DotForward <= 0.0f)
|
||||||
|
{
|
||||||
|
BehindCameraCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float Distance = ToCorner.Size();
|
||||||
|
const FVector Right = ViewerTransform.GetUnitAxis(EAxis::Y);
|
||||||
|
const FVector Up = ViewerTransform.GetUnitAxis(EAxis::Z);
|
||||||
|
|
||||||
|
const float DotRight = FVector::DotProduct(ToCorner.GetSafeNormal(), Right);
|
||||||
|
const float DotUp = FVector::DotProduct(ToCorner.GetSafeNormal(), Up);
|
||||||
|
|
||||||
|
const float AngleX = FMath::Atan2(DotRight, DotForward);
|
||||||
|
const float AngleY = FMath::Atan2(DotUp, DotForward);
|
||||||
|
|
||||||
|
const float ScreenX = (AngleX / HalfFOVRad) * (ScreenWidth * 0.5f) + (ScreenWidth * 0.5f);
|
||||||
|
const float ScreenY = (ScreenHeight * 0.5f) - (AngleY / HalfFOVRad) * (ScreenHeight * 0.5f);
|
||||||
|
|
||||||
|
MinScreenX = FMath::Min(MinScreenX, ScreenX);
|
||||||
|
MinScreenY = FMath::Min(MinScreenY, ScreenY);
|
||||||
|
MaxScreenX = FMath::Max(MaxScreenX, ScreenX);
|
||||||
|
MaxScreenY = FMath::Max(MaxScreenY, ScreenY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BehindCameraCount >= 8)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp to screen bounds
|
||||||
|
MinScreenX = FMath::Clamp(MinScreenX, 0.0f, static_cast<float>(ScreenWidth));
|
||||||
|
MinScreenY = FMath::Clamp(MinScreenY, 0.0f, static_cast<float>(ScreenHeight));
|
||||||
|
MaxScreenX = FMath::Clamp(MaxScreenX, 0.0f, static_cast<float>(ScreenWidth));
|
||||||
|
MaxScreenY = FMath::Clamp(MaxScreenY, 0.0f, static_cast<float>(ScreenHeight));
|
||||||
|
|
||||||
|
const float ScreenArea = static_cast<float>(ScreenWidth * ScreenHeight);
|
||||||
|
const float BoundingArea = (MaxScreenX - MinScreenX) * (MaxScreenY - MinScreenY);
|
||||||
|
|
||||||
|
return FMath::Clamp(BoundingArea / ScreenArea, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UPlanarCaptureCameraUtils::IsSurfaceVisibleToViewer(
|
||||||
|
const FBox& SurfaceBounds,
|
||||||
|
const FTransform& ViewerTransform,
|
||||||
|
float ViewerFOV,
|
||||||
|
float ViewerAspectRatio,
|
||||||
|
float ViewerNearPlane,
|
||||||
|
float ViewerFarPlane)
|
||||||
|
{
|
||||||
|
if (!SurfaceBounds.IsValid)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick dot-product check: is the surface center roughly in front of the viewer?
|
||||||
|
const FVector SurfaceCenter = SurfaceBounds.GetCenter();
|
||||||
|
const FVector ViewerPos = ViewerTransform.GetLocation();
|
||||||
|
const FVector ToSurface = SurfaceCenter - ViewerPos;
|
||||||
|
const FVector ViewerForward = ViewerTransform.GetUnitAxis(EAxis::X);
|
||||||
|
|
||||||
|
if (FVector::DotProduct(ToSurface.GetSafeNormal(), ViewerForward) < 0.0f)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance check
|
||||||
|
if (ToSurface.Size() > ViewerFarPlane)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ToSurface.Size() < ViewerNearPlane)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Screen coverage check — if coverage is above 0, it's visible
|
||||||
|
const float Coverage = ComputeScreenCoverage(SurfaceBounds, ViewerTransform, ViewerFOV, 1920, 1080);
|
||||||
|
return Coverage > 0.001f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float UPlanarCaptureCameraUtils::ComputeCompositeScore(
|
||||||
|
float ScreenCoverage,
|
||||||
|
float FacingAngle,
|
||||||
|
float DistanceToViewer,
|
||||||
|
float MaxDistance,
|
||||||
|
float ScriptedPriority)
|
||||||
|
{
|
||||||
|
const float DistanceFactor = (MaxDistance > 0.0f)
|
||||||
|
? FMath::Clamp(1.0f - (DistanceToViewer / MaxDistance), 0.0f, 1.0f)
|
||||||
|
: 1.0f;
|
||||||
|
|
||||||
|
const float Score = (ScreenCoverage * 0.5f)
|
||||||
|
+ (FMath::Abs(FacingAngle) * 0.3f)
|
||||||
|
+ (DistanceFactor * 0.1f)
|
||||||
|
+ (ScriptedPriority * 0.1f);
|
||||||
|
|
||||||
|
return FMath::Clamp(Score, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
// UE5 Modular Game Framework — PlanarCaptureCommon implementation (trivial)
|
||||||
|
|
||||||
|
#include "Capture/PlanarCaptureCommon.h"
|
||||||
|
// Struct and enum definitions are header-only.
|
||||||
|
// This file exists for module compilation unity.
|
||||||
432
Source/PG_Framework/Private/Capture/SS_PlanarCaptureManager.cpp
Normal file
432
Source/PG_Framework/Private/Capture/SS_PlanarCaptureManager.cpp
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
// Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
// UE5 Modular Game Framework — SS_PlanarCaptureManager implementation
|
||||||
|
|
||||||
|
#include "Capture/SS_PlanarCaptureManager.h"
|
||||||
|
#include "Capture/BP_PlanarCaptureActor.h"
|
||||||
|
#include "Capture/BPC_PlanarCapture.h"
|
||||||
|
#include "Engine/TextureRenderTarget2D.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
|
||||||
|
USS_PlanarCaptureManager::USS_PlanarCaptureManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void USS_PlanarCaptureManager::Initialize(FSubsystemCollectionBase& Collection)
|
||||||
|
{
|
||||||
|
Super::Initialize(Collection);
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("SS_PlanarCaptureManager: Initialized for world."));
|
||||||
|
}
|
||||||
|
|
||||||
|
void USS_PlanarCaptureManager::Deinitialize()
|
||||||
|
{
|
||||||
|
// Release all render targets
|
||||||
|
for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
|
||||||
|
{
|
||||||
|
if (Entry.RenderTarget)
|
||||||
|
{
|
||||||
|
Entry.RenderTarget->ConditionalBeginDestroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RenderTargetPool.Empty();
|
||||||
|
|
||||||
|
RegisteredSurfaces.Empty();
|
||||||
|
|
||||||
|
Super::Deinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void USS_PlanarCaptureManager::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
TimeSinceLastEvaluation += DeltaTime;
|
||||||
|
|
||||||
|
if (TimeSinceLastEvaluation >= FullEvaluationInterval)
|
||||||
|
{
|
||||||
|
TimeSinceLastEvaluation = 0.0f;
|
||||||
|
EvaluateAllSurfaces();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TStatId USS_PlanarCaptureManager::GetStatId() const
|
||||||
|
{
|
||||||
|
RETURN_QUICK_DECLARE_CYCLE_STAT(USS_PlanarCaptureManager, STATGROUP_Tickables);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Surface Registry
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
void USS_PlanarCaptureManager::RegisterSurface(ABP_PlanarCaptureActor* Surface)
|
||||||
|
{
|
||||||
|
if (!Surface)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicates
|
||||||
|
for (const TWeakObjectPtr<ABP_PlanarCaptureActor>& Existing : RegisteredSurfaces)
|
||||||
|
{
|
||||||
|
if (Existing.Get() == Surface)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("SS_PlanarCaptureManager: Surface '%s' already registered."),
|
||||||
|
*Surface->SurfaceDisplayName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisteredSurfaces.Add(Surface);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("SS_PlanarCaptureManager: Registered surface '%s'. Total: %d"),
|
||||||
|
*Surface->SurfaceDisplayName, RegisteredSurfaces.Num());
|
||||||
|
|
||||||
|
OnSurfaceRegistered.Broadcast(Surface, RegisteredSurfaces.Num());
|
||||||
|
|
||||||
|
// Evaluate immediately to assign initial tier
|
||||||
|
EvaluateAllSurfaces();
|
||||||
|
}
|
||||||
|
|
||||||
|
void USS_PlanarCaptureManager::UnregisterSurface(ABP_PlanarCaptureActor* Surface)
|
||||||
|
{
|
||||||
|
if (!Surface)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisteredSurfaces.RemoveAll([Surface](const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry)
|
||||||
|
{
|
||||||
|
return Entry.Get() == Surface;
|
||||||
|
});
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("SS_PlanarCaptureManager: Unregistered surface '%s'. Total: %d"),
|
||||||
|
*Surface->SurfaceDisplayName, RegisteredSurfaces.Num());
|
||||||
|
|
||||||
|
OnSurfaceUnregistered.Broadcast(Surface, RegisteredSurfaces.Num());
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<ABP_PlanarCaptureActor*> USS_PlanarCaptureManager::GetRegisteredSurfaces() const
|
||||||
|
{
|
||||||
|
TArray<ABP_PlanarCaptureActor*> Result;
|
||||||
|
for (const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry : RegisteredSurfaces)
|
||||||
|
{
|
||||||
|
if (Entry.IsValid())
|
||||||
|
{
|
||||||
|
Result.Add(Entry.Get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Quality Budget Management
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
void USS_PlanarCaptureManager::ForceAllSurfacesToTier(EPlanarCaptureQualityTier Tier)
|
||||||
|
{
|
||||||
|
ForceTierOverride = Tier;
|
||||||
|
|
||||||
|
for (TWeakObjectPtr<ABP_PlanarCaptureActor>& SurfaceWeak : RegisteredSurfaces)
|
||||||
|
{
|
||||||
|
if (ABP_PlanarCaptureActor* Surface = SurfaceWeak.Get())
|
||||||
|
{
|
||||||
|
if (UBPC_PlanarCapture* Capture = Surface->CaptureComponent)
|
||||||
|
{
|
||||||
|
Capture->ApplyQualityTier(Tier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void USS_PlanarCaptureManager::ReleaseForceTier()
|
||||||
|
{
|
||||||
|
ForceTierOverride.Reset();
|
||||||
|
EvaluateAllSurfaces();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Render Target Pool
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
UTextureRenderTarget2D* USS_PlanarCaptureManager::RequestRenderTarget(int32 Size)
|
||||||
|
{
|
||||||
|
// Check pool for an available RT of the right size
|
||||||
|
for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
|
||||||
|
{
|
||||||
|
if (!Entry.bInUse && Entry.CurrentSize == Size && Entry.RenderTarget)
|
||||||
|
{
|
||||||
|
Entry.bInUse = true;
|
||||||
|
return Entry.RenderTarget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new one
|
||||||
|
return CreateRenderTarget(Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void USS_PlanarCaptureManager::ReleaseRenderTarget(UTextureRenderTarget2D* RenderTarget)
|
||||||
|
{
|
||||||
|
if (!RenderTarget)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the entry and mark as free
|
||||||
|
for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
|
||||||
|
{
|
||||||
|
if (Entry.RenderTarget == RenderTarget)
|
||||||
|
{
|
||||||
|
Entry.bInUse = false;
|
||||||
|
Entry.OwningSurface.Reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not in pool — add it
|
||||||
|
FPlanarCaptureRenderTargetEntry NewEntry;
|
||||||
|
NewEntry.RenderTarget = RenderTarget;
|
||||||
|
NewEntry.CurrentSize = RenderTarget->SizeX;
|
||||||
|
NewEntry.bInUse = false;
|
||||||
|
RenderTargetPool.Add(NewEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
float USS_PlanarCaptureManager::GetPoolMemoryUsageMB() const
|
||||||
|
{
|
||||||
|
float TotalBytes = 0.0f;
|
||||||
|
for (const FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
|
||||||
|
{
|
||||||
|
if (Entry.RenderTarget)
|
||||||
|
{
|
||||||
|
// RGBA8 = 4 bytes per pixel
|
||||||
|
TotalBytes += static_cast<float>(Entry.CurrentSize * Entry.CurrentSize * 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TotalBytes / (1024.0f * 1024.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Query
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
ABP_PlanarCaptureActor* USS_PlanarCaptureManager::GetNearestSurfaceOfMode(
|
||||||
|
EPlanarCaptureMode Mode, FVector WorldLocation, float MaxDistance) const
|
||||||
|
{
|
||||||
|
ABP_PlanarCaptureActor* Nearest = nullptr;
|
||||||
|
float NearestDistSq = (MaxDistance > 0.0f) ? (MaxDistance * MaxDistance) : FLT_MAX;
|
||||||
|
|
||||||
|
for (const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry : RegisteredSurfaces)
|
||||||
|
{
|
||||||
|
if (ABP_PlanarCaptureActor* Surface = Entry.Get())
|
||||||
|
{
|
||||||
|
if (!Surface->CaptureComponent || Surface->CaptureComponent->CaptureMode != Mode)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float DistSq = FVector::DistSquared(Surface->GetActorLocation(), WorldLocation);
|
||||||
|
if (DistSq < NearestDistSq)
|
||||||
|
{
|
||||||
|
NearestDistSq = DistSq;
|
||||||
|
Nearest = Surface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Internal Methods
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
void USS_PlanarCaptureManager::EvaluateAllSurfaces()
|
||||||
|
{
|
||||||
|
// Reset tier counts
|
||||||
|
TierAssignmentCounts.Empty();
|
||||||
|
|
||||||
|
// Ensure registered surfaces are still valid
|
||||||
|
RegisteredSurfaces.RemoveAll([](const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry)
|
||||||
|
{
|
||||||
|
return !Entry.IsValid();
|
||||||
|
});
|
||||||
|
|
||||||
|
// If force tier override is active, apply it to all
|
||||||
|
if (ForceTierOverride.IsSet())
|
||||||
|
{
|
||||||
|
ForceAllSurfacesToTier(ForceTierOverride.GetValue());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Score and assign tiers
|
||||||
|
TArray<TPair<ABP_PlanarCaptureActor*, float>> ScoredSurfaces;
|
||||||
|
|
||||||
|
for (TWeakObjectPtr<ABP_PlanarCaptureActor>& SurfaceWeak : RegisteredSurfaces)
|
||||||
|
{
|
||||||
|
if (ABP_PlanarCaptureActor* Surface = SurfaceWeak.Get())
|
||||||
|
{
|
||||||
|
if (!Surface->bIsActive || !Surface->CaptureComponent)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UBPC_PlanarCapture* Capture = Surface->CaptureComponent;
|
||||||
|
FPlanarCaptureScore Score = Capture->GetCurrentScore();
|
||||||
|
ScoredSurfaces.Add(TPair<ABP_PlanarCaptureActor*, float>(Surface, Score.CompositeScore));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by score descending (highest score first)
|
||||||
|
ScoredSurfaces.Sort([](const TPair<ABP_PlanarCaptureActor*, float>& A,
|
||||||
|
const TPair<ABP_PlanarCaptureActor*, float>& B)
|
||||||
|
{
|
||||||
|
return A.Value > B.Value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assign tiers within budget
|
||||||
|
for (const auto& Pair : ScoredSurfaces)
|
||||||
|
{
|
||||||
|
ABP_PlanarCaptureActor* Surface = Pair.Key;
|
||||||
|
float Score = Pair.Value;
|
||||||
|
|
||||||
|
if (!Surface->CaptureComponent)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
EPlanarCaptureQualityTier AssignedTier = ScoreAndAssignTier(Surface->CaptureComponent);
|
||||||
|
|
||||||
|
// Apply tier
|
||||||
|
Surface->CaptureComponent->ApplyQualityTier(AssignedTier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EPlanarCaptureQualityTier USS_PlanarCaptureManager::ScoreAndAssignTier(UBPC_PlanarCapture* Capture)
|
||||||
|
{
|
||||||
|
if (!Capture)
|
||||||
|
{
|
||||||
|
return EPlanarCaptureQualityTier::Off;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FPlanarCaptureScore Score = Capture->GetCurrentScore();
|
||||||
|
|
||||||
|
// If not visible, off
|
||||||
|
if (!Score.bInFrustum && Score.CompositeScore < 0.01f)
|
||||||
|
{
|
||||||
|
return EPlanarCaptureQualityTier::Off;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If too far, off
|
||||||
|
if (Score.DistanceToViewer > MaxCaptureDistance)
|
||||||
|
{
|
||||||
|
return EPlanarCaptureQualityTier::Off;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine natural tier based on composite score
|
||||||
|
EPlanarCaptureQualityTier NaturalTier;
|
||||||
|
|
||||||
|
if (Score.CompositeScore >= 0.8f)
|
||||||
|
{
|
||||||
|
NaturalTier = EPlanarCaptureQualityTier::Hero;
|
||||||
|
}
|
||||||
|
else if (Score.CompositeScore >= 0.5f)
|
||||||
|
{
|
||||||
|
NaturalTier = EPlanarCaptureQualityTier::High;
|
||||||
|
}
|
||||||
|
else if (Score.CompositeScore >= 0.2f)
|
||||||
|
{
|
||||||
|
NaturalTier = EPlanarCaptureQualityTier::Medium;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NaturalTier = EPlanarCaptureQualityTier::Low;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply global quality cap
|
||||||
|
if (static_cast<int32>(NaturalTier) > static_cast<int32>(GlobalQualityCap))
|
||||||
|
{
|
||||||
|
NaturalTier = GlobalQualityCap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check budget limits
|
||||||
|
const int32 CurrentCount = TierAssignmentCounts.FindRef(NaturalTier);
|
||||||
|
|
||||||
|
switch (NaturalTier)
|
||||||
|
{
|
||||||
|
case EPlanarCaptureQualityTier::Hero:
|
||||||
|
if (CurrentCount >= MaxHeroSurfaces)
|
||||||
|
{
|
||||||
|
NaturalTier = EPlanarCaptureQualityTier::High;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EPlanarCaptureQualityTier::High:
|
||||||
|
if (CurrentCount >= MaxHighSurfaces)
|
||||||
|
{
|
||||||
|
NaturalTier = EPlanarCaptureQualityTier::Medium;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EPlanarCaptureQualityTier::Medium:
|
||||||
|
if (CurrentCount >= MaxMediumSurfaces)
|
||||||
|
{
|
||||||
|
NaturalTier = EPlanarCaptureQualityTier::Low;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment the assigned tier count
|
||||||
|
TierAssignmentCounts.FindOrAdd(NaturalTier)++;
|
||||||
|
|
||||||
|
return NaturalTier;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USS_PlanarCaptureManager::EnforceBudgetLimits()
|
||||||
|
{
|
||||||
|
// Additional enforcement for total memory budget
|
||||||
|
float TotalMemoryMB = GetPoolMemoryUsageMB();
|
||||||
|
|
||||||
|
if (TotalMemoryMB > MaxTotalRenderTargetMemoryMB)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("SS_PlanarCaptureManager: Render target memory budget exceeded (%.2f MB / %.2f MB)"),
|
||||||
|
TotalMemoryMB, MaxTotalRenderTargetMemoryMB);
|
||||||
|
|
||||||
|
// If over budget, demote lowest-priority surfaces
|
||||||
|
// Future: implement more sophisticated memory budget enforcement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UTextureRenderTarget2D* USS_PlanarCaptureManager::CreateRenderTarget(int32 Size)
|
||||||
|
{
|
||||||
|
UTextureRenderTarget2D* RT = NewObject<UTextureRenderTarget2D>(this);
|
||||||
|
if (!RT)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
RT->InitCustomFormat(Size, Size, PF_B8G8R8A8, false);
|
||||||
|
RT->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RGBA8;
|
||||||
|
RT->bGPUSharedFlag = false;
|
||||||
|
RT->ClearColor = FLinearColor::Black;
|
||||||
|
RT->UpdateResourceImmediate(true);
|
||||||
|
|
||||||
|
FPlanarCaptureRenderTargetEntry Entry;
|
||||||
|
Entry.RenderTarget = RT;
|
||||||
|
Entry.CurrentSize = Size;
|
||||||
|
Entry.bInUse = true;
|
||||||
|
RenderTargetPool.Add(Entry);
|
||||||
|
|
||||||
|
return RT;
|
||||||
|
}
|
||||||
|
|
||||||
|
UTextureRenderTarget2D* USS_PlanarCaptureManager::GetOrCreateRenderTarget(int32 Size)
|
||||||
|
{
|
||||||
|
// Check pool first
|
||||||
|
for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
|
||||||
|
{
|
||||||
|
if (!Entry.bInUse && Entry.CurrentSize == Size && Entry.RenderTarget)
|
||||||
|
{
|
||||||
|
Entry.bInUse = true;
|
||||||
|
return Entry.RenderTarget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateRenderTarget(Size);
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
#include "Core/DA_GameTagRegistry.h"
|
#include "Core/DA_GameTagRegistry.h"
|
||||||
#include "GameplayTagsManager.h"
|
#include "GameplayTagsManager.h"
|
||||||
#include "Engine/DataTable.h"
|
#include "Engine/DataTable.h"
|
||||||
#include "GameplayTagTableRow.h"
|
|
||||||
|
|
||||||
UDA_GameTagRegistry::UDA_GameTagRegistry()
|
UDA_GameTagRegistry::UDA_GameTagRegistry()
|
||||||
{
|
{
|
||||||
@@ -39,16 +39,7 @@ FText UDA_GameTagRegistry::GetTagDisplayName(const FGameplayTag& Tag) const
|
|||||||
return FText::FromString(TEXT("Invalid Tag"));
|
return FText::FromString(TEXT("Invalid Tag"));
|
||||||
}
|
}
|
||||||
|
|
||||||
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
|
|
||||||
FString DevComment;
|
|
||||||
FString DisplayName = TagManager.GetTagDevCommentAndDisplayName(Tag, DevComment);
|
|
||||||
|
|
||||||
if (DisplayName.IsEmpty())
|
|
||||||
{
|
|
||||||
return FText::FromName(Tag.GetTagName());
|
return FText::FromName(Tag.GetTagName());
|
||||||
}
|
|
||||||
|
|
||||||
return FText::FromString(DisplayName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UDA_GameTagRegistry::ValidateTag(const FGameplayTag& Tag) const
|
bool UDA_GameTagRegistry::ValidateTag(const FGameplayTag& Tag) const
|
||||||
@@ -72,9 +63,9 @@ FGameplayTag UDA_GameTagRegistry::RequestTag(FName TagName, bool bLogWarning) co
|
|||||||
|
|
||||||
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
|
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
|
||||||
TSharedPtr<FGameplayTagNode> TagNode = TagManager.FindTagNode(TagName);
|
TSharedPtr<FGameplayTagNode> TagNode = TagManager.FindTagNode(TagName);
|
||||||
FGameplayTag OutTag;
|
|
||||||
|
|
||||||
if (TagManager.RequestGameplayTag(TagName, OutTag))
|
FGameplayTag OutTag = TagManager.RequestGameplayTag(TagName, false);
|
||||||
|
if (OutTag.IsValid())
|
||||||
{
|
{
|
||||||
return OutTag;
|
return OutTag;
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "Kismet/GameplayStatics.h"
|
#include "Kismet/GameplayStatics.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
#include "GameplayTagsManager.h"
|
#include "GameplayTagsManager.h"
|
||||||
|
#include "GameplayTagAssetInterface.h"
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Subsystem Access
|
// Subsystem Access
|
||||||
@@ -15,7 +16,18 @@
|
|||||||
|
|
||||||
UGI_GameFramework* UFL_GameUtilities::GetGameFramework(const UObject* WorldContextObject)
|
UGI_GameFramework* UFL_GameUtilities::GetGameFramework(const UObject* WorldContextObject)
|
||||||
{
|
{
|
||||||
return GetSubsystemSafe<UGI_GameFramework>(WorldContextObject);
|
if (!WorldContextObject)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UWorld* World = WorldContextObject->GetWorld();
|
||||||
|
if (!World)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cast<UGI_GameFramework>(World->GetGameInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
UGameInstanceSubsystem* UFL_GameUtilities::GetSubsystemByClass(const UObject* WorldContextObject,
|
UGameInstanceSubsystem* UFL_GameUtilities::GetSubsystemByClass(const UObject* WorldContextObject,
|
||||||
@@ -173,9 +185,9 @@ FGameplayTag UFL_GameUtilities::MakeTagFromString(const FString& TagString, bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
|
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
|
||||||
FGameplayTag OutTag;
|
|
||||||
|
|
||||||
if (TagManager.RequestGameplayTag(FName(*TagString), OutTag))
|
FGameplayTag OutTag = TagManager.RequestGameplayTag(FName(*TagString), false);
|
||||||
|
if (OutTag.IsValid())
|
||||||
{
|
{
|
||||||
return OutTag;
|
return OutTag;
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
#include "Kismet/GameplayStatics.h"
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
|
||||||
DEFINE_LOG_CATEGORY_STATIC(LogGameMode, Log, All);
|
DEFINE_LOG_CATEGORY_STATIC(LogFrameworkGameMode, Log, All);
|
||||||
|
|
||||||
AGM_CoreGameMode::AGM_CoreGameMode()
|
AGM_CoreGameMode::AGM_CoreGameMode()
|
||||||
{
|
{
|
||||||
@@ -29,10 +29,10 @@ void AGM_CoreGameMode::InitGame(const FString& MapName, const FString& Options,
|
|||||||
CachedFramework = Cast<UGI_GameFramework>(GetGameInstance());
|
CachedFramework = Cast<UGI_GameFramework>(GetGameInstance());
|
||||||
if (!CachedFramework)
|
if (!CachedFramework)
|
||||||
{
|
{
|
||||||
UE_LOG(LogGameMode, Warning, TEXT("GM_CoreGameMode::InitGame — GI_GameFramework not found as GameInstance"));
|
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::InitGame — GI_GameFramework not found as GameInstance"));
|
||||||
}
|
}
|
||||||
|
|
||||||
UE_LOG(LogGameMode, Log, TEXT("GM_CoreGameMode::InitGame — Map: %s"), *MapName);
|
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::InitGame — Map: %s"), *MapName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AGM_CoreGameMode::BeginPlay()
|
void AGM_CoreGameMode::BeginPlay()
|
||||||
@@ -45,7 +45,7 @@ void AGM_CoreGameMode::BeginPlay()
|
|||||||
CachedFramework->SetGamePhase(EGamePhase::InGame);
|
CachedFramework->SetGamePhase(EGamePhase::InGame);
|
||||||
}
|
}
|
||||||
|
|
||||||
UE_LOG(LogGameMode, Log, TEXT("GM_CoreGameMode::BeginPlay"));
|
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::BeginPlay"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -56,19 +56,19 @@ void AGM_CoreGameMode::TransitionToChapter(FGameplayTag ChapterTag)
|
|||||||
{
|
{
|
||||||
if (!ChapterTag.IsValid())
|
if (!ChapterTag.IsValid())
|
||||||
{
|
{
|
||||||
UE_LOG(LogGameMode, Warning, TEXT("GM_CoreGameMode::TransitionToChapter — Invalid chapter tag"));
|
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::TransitionToChapter — Invalid chapter tag"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent transitions during loading.
|
// Prevent transitions during loading.
|
||||||
if (CachedFramework && CachedFramework->CurrentGamePhase == EGamePhase::Loading)
|
if (CachedFramework && CachedFramework->CurrentGamePhase == EGamePhase::Loading)
|
||||||
{
|
{
|
||||||
UE_LOG(LogGameMode, Warning, TEXT("GM_CoreGameMode::TransitionToChapter — Already loading, ignoring transition to '%s'"),
|
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::TransitionToChapter — Already loading, ignoring transition to '%s'"),
|
||||||
*ChapterTag.GetTagName().ToString());
|
*ChapterTag.GetTagName().ToString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UE_LOG(LogGameMode, Log, TEXT("GM_CoreGameMode::TransitionToChapter — '%s'"), *ChapterTag.GetTagName().ToString());
|
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::TransitionToChapter — '%s'"), *ChapterTag.GetTagName().ToString());
|
||||||
|
|
||||||
CurrentChapterTag = ChapterTag;
|
CurrentChapterTag = ChapterTag;
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ void AGM_CoreGameMode::OnChapterLevelLoaded(FGameplayTag ChapterTag)
|
|||||||
CachedFramework->SetGamePhase(EGamePhase::InGame);
|
CachedFramework->SetGamePhase(EGamePhase::InGame);
|
||||||
}
|
}
|
||||||
|
|
||||||
UE_LOG(LogGameMode, Log, TEXT("GM_CoreGameMode::OnChapterLevelLoaded — '%s'"), *ChapterTag.GetTagName().ToString());
|
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::OnChapterLevelLoaded — '%s'"), *ChapterTag.GetTagName().ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -122,11 +122,11 @@ void AGM_CoreGameMode::HandlePlayerDead(AController* DeadController)
|
|||||||
{
|
{
|
||||||
if (!DeadController)
|
if (!DeadController)
|
||||||
{
|
{
|
||||||
UE_LOG(LogGameMode, Warning, TEXT("GM_CoreGameMode::HandlePlayerDead — Invalid controller"));
|
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::HandlePlayerDead — Invalid controller"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UE_LOG(LogGameMode, Log, TEXT("GM_CoreGameMode::HandlePlayerDead — %s"), *DeadController->GetName());
|
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::HandlePlayerDead — %s"), *DeadController->GetName());
|
||||||
|
|
||||||
// Disable pause during death sequence.
|
// Disable pause during death sequence.
|
||||||
bPauseAllowed = false;
|
bPauseAllowed = false;
|
||||||
@@ -138,7 +138,7 @@ void AGM_CoreGameMode::HandlePlayerDead(AController* DeadController)
|
|||||||
|
|
||||||
// TODO: Decision logic — AltDeathSpace vs checkpoint respawn.
|
// TODO: Decision logic — AltDeathSpace vs checkpoint respawn.
|
||||||
// For now, route to checkpoint respawn through the save system.
|
// For now, route to checkpoint respawn through the save system.
|
||||||
UE_LOG(LogGameMode, Log, TEXT("GM_CoreGameMode::HandlePlayerDead — Routing to respawn..."));
|
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::HandlePlayerDead — Routing to respawn..."));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -149,11 +149,11 @@ void AGM_CoreGameMode::TriggerEnding(FGameplayTag EndingTag)
|
|||||||
{
|
{
|
||||||
if (!EndingTag.IsValid())
|
if (!EndingTag.IsValid())
|
||||||
{
|
{
|
||||||
UE_LOG(LogGameMode, Warning, TEXT("GM_CoreGameMode::TriggerEnding — Invalid ending tag"));
|
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::TriggerEnding — Invalid ending tag"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UE_LOG(LogGameMode, Log, TEXT("GM_CoreGameMode::TriggerEnding — '%s'"), *EndingTag.GetTagName().ToString());
|
UE_LOG(LogFrameworkGameMode, Log, TEXT("GM_CoreGameMode::TriggerEnding — '%s'"), *EndingTag.GetTagName().ToString());
|
||||||
|
|
||||||
OnGameOverTriggered.Broadcast(EndingTag);
|
OnGameOverTriggered.Broadcast(EndingTag);
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
#include "Core/GI_GameFramework.h"
|
#include "Core/GI_GameFramework.h"
|
||||||
#include "Net/UnrealNetwork.h"
|
#include "Net/UnrealNetwork.h"
|
||||||
|
|
||||||
DEFINE_LOG_CATEGORY_STATIC(LogGameState, Log, All);
|
DEFINE_LOG_CATEGORY_STATIC(LogFrameworkGameState, Log, All);
|
||||||
|
|
||||||
AGS_CoreGameState::AGS_CoreGameState()
|
AGS_CoreGameState::AGS_CoreGameState()
|
||||||
{
|
{
|
||||||
@@ -40,11 +40,11 @@ void AGS_CoreGameState::BeginPlay()
|
|||||||
|
|
||||||
if (CachedFramework)
|
if (CachedFramework)
|
||||||
{
|
{
|
||||||
UE_LOG(LogGameState, Log, TEXT("GS_CoreGameState::BeginPlay — Bound to GI_GameFramework"));
|
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::BeginPlay — Bound to GI_GameFramework"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UE_LOG(LogGameState, Warning, TEXT("GS_CoreGameState::BeginPlay — GI_GameFramework not found!"));
|
UE_LOG(LogFrameworkGameState, Warning, TEXT("GS_CoreGameState::BeginPlay — GI_GameFramework not found!"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ void AGS_CoreGameState::SetChapter(FGameplayTag ChapterTag)
|
|||||||
|
|
||||||
ActiveChapterTag = ChapterTag;
|
ActiveChapterTag = ChapterTag;
|
||||||
|
|
||||||
UE_LOG(LogGameState, Log, TEXT("GS_CoreGameState::SetChapter — '%s'"), *ChapterTag.GetTagName().ToString());
|
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::SetChapter — '%s'"), *ChapterTag.GetTagName().ToString());
|
||||||
|
|
||||||
// Broadcast for local (server/listen-host).
|
// Broadcast for local (server/listen-host).
|
||||||
OnChapterChanged.Broadcast(ChapterTag);
|
OnChapterChanged.Broadcast(ChapterTag);
|
||||||
@@ -106,7 +106,7 @@ void AGS_CoreGameState::SetNarrativePhase(FGameplayTag PhaseTag)
|
|||||||
|
|
||||||
ActiveNarrativePhase = PhaseTag;
|
ActiveNarrativePhase = PhaseTag;
|
||||||
|
|
||||||
UE_LOG(LogGameState, Verbose, TEXT("GS_CoreGameState::SetNarrativePhase — '%s'"), *PhaseTag.GetTagName().ToString());
|
UE_LOG(LogFrameworkGameState, Verbose, TEXT("GS_CoreGameState::SetNarrativePhase — '%s'"), *PhaseTag.GetTagName().ToString());
|
||||||
|
|
||||||
OnNarrativePhaseChanged.Broadcast(PhaseTag);
|
OnNarrativePhaseChanged.Broadcast(PhaseTag);
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ void AGS_CoreGameState::SetEncounterActive(bool bActive)
|
|||||||
|
|
||||||
bEncounterActive = bActive;
|
bEncounterActive = bActive;
|
||||||
|
|
||||||
UE_LOG(LogGameState, Log, TEXT("GS_CoreGameState::SetEncounterActive — %s"), bActive ? TEXT("Active") : TEXT("Inactive"));
|
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::SetEncounterActive — %s"), bActive ? TEXT("Active") : TEXT("Inactive"));
|
||||||
|
|
||||||
OnEncounterActiveStateChanged.Broadcast(bActive);
|
OnEncounterActiveStateChanged.Broadcast(bActive);
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ void AGS_CoreGameState::AddObjective(FGameplayTag ObjectiveTag)
|
|||||||
|
|
||||||
ActiveObjectiveTags.Add(ObjectiveTag);
|
ActiveObjectiveTags.Add(ObjectiveTag);
|
||||||
|
|
||||||
UE_LOG(LogGameState, Log, TEXT("GS_CoreGameState::AddObjective — '%s'"), *ObjectiveTag.GetTagName().ToString());
|
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::AddObjective — '%s'"), *ObjectiveTag.GetTagName().ToString());
|
||||||
|
|
||||||
OnObjectiveTagsChanged.Broadcast();
|
OnObjectiveTagsChanged.Broadcast();
|
||||||
}
|
}
|
||||||
@@ -148,7 +148,7 @@ void AGS_CoreGameState::RemoveObjective(FGameplayTag ObjectiveTag)
|
|||||||
{
|
{
|
||||||
if (ActiveObjectiveTags.Remove(ObjectiveTag) > 0)
|
if (ActiveObjectiveTags.Remove(ObjectiveTag) > 0)
|
||||||
{
|
{
|
||||||
UE_LOG(LogGameState, Log, TEXT("GS_CoreGameState::RemoveObjective — '%s'"), *ObjectiveTag.GetTagName().ToString());
|
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::RemoveObjective — '%s'"), *ObjectiveTag.GetTagName().ToString());
|
||||||
OnObjectiveTagsChanged.Broadcast();
|
OnObjectiveTagsChanged.Broadcast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +158,7 @@ void AGS_CoreGameState::ClearAllObjectives()
|
|||||||
if (ActiveObjectiveTags.Num() > 0)
|
if (ActiveObjectiveTags.Num() > 0)
|
||||||
{
|
{
|
||||||
ActiveObjectiveTags.Empty();
|
ActiveObjectiveTags.Empty();
|
||||||
UE_LOG(LogGameState, Log, TEXT("GS_CoreGameState::ClearAllObjectives"));
|
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::ClearAllObjectives"));
|
||||||
OnObjectiveTagsChanged.Broadcast();
|
OnObjectiveTagsChanged.Broadcast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
#include "Engine/LocalPlayer.h"
|
#include "Engine/LocalPlayer.h"
|
||||||
#include "Kismet/GameplayStatics.h"
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
|
||||||
DEFINE_LOG_CATEGORY_STATIC(LogInput, Log, All);
|
DEFINE_LOG_CATEGORY_STATIC(LogFrameworkInput, Log, All);
|
||||||
|
|
||||||
USS_EnhancedInputManager::USS_EnhancedInputManager()
|
USS_EnhancedInputManager::USS_EnhancedInputManager()
|
||||||
{
|
{
|
||||||
@@ -24,7 +24,7 @@ void USS_EnhancedInputManager::Initialize(FSubsystemCollectionBase& Collection)
|
|||||||
{
|
{
|
||||||
Super::Initialize(Collection);
|
Super::Initialize(Collection);
|
||||||
|
|
||||||
UE_LOG(LogInput, Log, TEXT("SS_EnhancedInputManager::Initialize"));
|
UE_LOG(LogFrameworkInput, Log, TEXT("SS_EnhancedInputManager::Initialize"));
|
||||||
|
|
||||||
// Load default contexts on startup (e.g., IMC_Default).
|
// Load default contexts on startup (e.g., IMC_Default).
|
||||||
// Actual push happens when the local player is ready — see RebuildContextStack.
|
// Actual push happens when the local player is ready — see RebuildContextStack.
|
||||||
@@ -43,7 +43,7 @@ void USS_EnhancedInputManager::Initialize(FSubsystemCollectionBase& Collection)
|
|||||||
|
|
||||||
void USS_EnhancedInputManager::Deinitialize()
|
void USS_EnhancedInputManager::Deinitialize()
|
||||||
{
|
{
|
||||||
UE_LOG(LogInput, Log, TEXT("SS_EnhancedInputManager::Deinitialize"));
|
UE_LOG(LogFrameworkInput, Log, TEXT("SS_EnhancedInputManager::Deinitialize"));
|
||||||
ClearAllContexts();
|
ClearAllContexts();
|
||||||
Super::Deinitialize();
|
Super::Deinitialize();
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ void USS_EnhancedInputManager::PushContext(UInputMappingContext* Context,
|
|||||||
{
|
{
|
||||||
if (!Context)
|
if (!Context)
|
||||||
{
|
{
|
||||||
UE_LOG(LogInput, Warning, TEXT("PushContext — Null context"));
|
UE_LOG(LogFrameworkInput, Warning, TEXT("PushContext — Null context"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ void USS_EnhancedInputManager::PushContext(UInputMappingContext* Context,
|
|||||||
Entry.ContextTag = ContextTag;
|
Entry.ContextTag = ContextTag;
|
||||||
ContextStack.Add(Entry);
|
ContextStack.Add(Entry);
|
||||||
|
|
||||||
UE_LOG(LogInput, Log, TEXT("PushContext — '%s' (Priority: %d)"),
|
UE_LOG(LogFrameworkInput, Log, TEXT("PushContext — '%s' (Priority: %d)"),
|
||||||
*ContextTag.GetTagName().ToString(), static_cast<int32>(Priority));
|
*ContextTag.GetTagName().ToString(), static_cast<int32>(Priority));
|
||||||
|
|
||||||
RebuildContextStack();
|
RebuildContextStack();
|
||||||
@@ -98,7 +98,7 @@ void USS_EnhancedInputManager::PopContext(UInputMappingContext* Context)
|
|||||||
EInputContextPriority Prio = ContextStack[i].Priority;
|
EInputContextPriority Prio = ContextStack[i].Priority;
|
||||||
ContextStack.RemoveAt(i);
|
ContextStack.RemoveAt(i);
|
||||||
|
|
||||||
UE_LOG(LogInput, Log, TEXT("PopContext — '%s'"), *Popped.GetTagName().ToString());
|
UE_LOG(LogFrameworkInput, Log, TEXT("PopContext — '%s'"), *Popped.GetTagName().ToString());
|
||||||
|
|
||||||
RebuildContextStack();
|
RebuildContextStack();
|
||||||
OnContextPopped.Broadcast(Popped, Prio);
|
OnContextPopped.Broadcast(Popped, Prio);
|
||||||
@@ -106,7 +106,7 @@ void USS_EnhancedInputManager::PopContext(UInputMappingContext* Context)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UE_LOG(LogInput, Warning, TEXT("PopContext — Context not found in stack"));
|
UE_LOG(LogFrameworkInput, Warning, TEXT("PopContext — Context not found in stack"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void USS_EnhancedInputManager::PopContextByTag(FGameplayTag ContextTag)
|
void USS_EnhancedInputManager::PopContextByTag(FGameplayTag ContextTag)
|
||||||
@@ -120,13 +120,13 @@ void USS_EnhancedInputManager::PopContextByTag(FGameplayTag ContextTag)
|
|||||||
{
|
{
|
||||||
if (ContextStack[i].ContextTag == ContextTag)
|
if (ContextStack[i].ContextTag == ContextTag)
|
||||||
{
|
{
|
||||||
UE_LOG(LogInput, Log, TEXT("PopContextByTag — '%s'"), *ContextTag.GetTagName().ToString());
|
UE_LOG(LogFrameworkInput, Log, TEXT("PopContextByTag — '%s'"), *ContextTag.GetTagName().ToString());
|
||||||
PopContext(ContextStack[i].Context);
|
PopContext(ContextStack[i].Context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UE_LOG(LogInput, Warning, TEXT("PopContextByTag — '%s' not found in stack"),
|
UE_LOG(LogFrameworkInput, Warning, TEXT("PopContextByTag — '%s' not found in stack"),
|
||||||
*ContextTag.GetTagName().ToString());
|
*ContextTag.GetTagName().ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ void USS_EnhancedInputManager::ClearAllContexts()
|
|||||||
}
|
}
|
||||||
|
|
||||||
ContextStack.Empty();
|
ContextStack.Empty();
|
||||||
UE_LOG(LogInput, Log, TEXT("ClearAllContexts — All contexts removed"));
|
UE_LOG(LogFrameworkInput, Log, TEXT("ClearAllContexts — All contexts removed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool USS_EnhancedInputManager::IsContextActive(FGameplayTag ContextTag) const
|
bool USS_EnhancedInputManager::IsContextActive(FGameplayTag ContextTag) const
|
||||||
@@ -187,7 +187,7 @@ void USS_EnhancedInputManager::SetInputMode(bool bUIMode, bool bShowCursor, bool
|
|||||||
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
||||||
if (!PC)
|
if (!PC)
|
||||||
{
|
{
|
||||||
UE_LOG(LogInput, Warning, TEXT("SetInputMode — No PlayerController found"));
|
UE_LOG(LogFrameworkInput, Warning, TEXT("SetInputMode — No PlayerController found"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ void USS_EnhancedInputManager::SetInputMode(bool bUIMode, bool bShowCursor, bool
|
|||||||
|
|
||||||
PC->bShowMouseCursor = bShowCursor;
|
PC->bShowMouseCursor = bShowCursor;
|
||||||
|
|
||||||
UE_LOG(LogInput, Log, TEXT("SetInputMode — UI: %s, Cursor: %s"),
|
UE_LOG(LogFrameworkInput, Log, TEXT("SetInputMode — UI: %s, Cursor: %s"),
|
||||||
bUIMode ? TEXT("ON") : TEXT("OFF"),
|
bUIMode ? TEXT("ON") : TEXT("OFF"),
|
||||||
bShowCursor ? TEXT("Visible") : TEXT("Hidden"));
|
bShowCursor ? TEXT("Visible") : TEXT("Hidden"));
|
||||||
|
|
||||||
@@ -222,7 +222,7 @@ void USS_EnhancedInputManager::RebindKey(UInputAction* Action, FKey NewKey, bool
|
|||||||
{
|
{
|
||||||
if (!Action)
|
if (!Action)
|
||||||
{
|
{
|
||||||
UE_LOG(LogInput, Warning, TEXT("RebindKey — Null action"));
|
UE_LOG(LogFrameworkInput, Warning, TEXT("RebindKey — Null action"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ void USS_EnhancedInputManager::RebindKey(UInputAction* Action, FKey NewKey, bool
|
|||||||
Options.bIgnoreAllPressedKeysUntilRelease = true;
|
Options.bIgnoreAllPressedKeysUntilRelease = true;
|
||||||
// Subsystem->AddPlayerMappedKey(Action, NewKey, Options);
|
// Subsystem->AddPlayerMappedKey(Action, NewKey, Options);
|
||||||
|
|
||||||
UE_LOG(LogInput, Log, TEXT("RebindKey — '%s' → '%s'"),
|
UE_LOG(LogFrameworkInput, Log, TEXT("RebindKey — '%s' → '%s'"),
|
||||||
*Action->GetName(), *NewKey.ToString());
|
*Action->GetName(), *NewKey.ToString());
|
||||||
|
|
||||||
OnKeyRebound.Broadcast(FGameplayTag(), NewKey); // Tag from mapping profile.
|
OnKeyRebound.Broadcast(FGameplayTag(), NewKey); // Tag from mapping profile.
|
||||||
@@ -253,8 +253,9 @@ void USS_EnhancedInputManager::ResetAllBindings()
|
|||||||
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
|
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
|
||||||
if (Subsystem)
|
if (Subsystem)
|
||||||
{
|
{
|
||||||
Subsystem->ResetPlayerMappedKeys();
|
// UE 5.7+: ResetPlayerMappedKeys was removed. Use UEnhancedInputUserSettings or iterate context entries.
|
||||||
UE_LOG(LogInput, Log, TEXT("ResetAllBindings — All keys reset to defaults"));
|
// Subsystem->RequestRebuildPlayerMappedKeys();
|
||||||
|
UE_LOG(LogFrameworkInput, Log, TEXT("ResetAllBindings — All keys reset to defaults"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +332,7 @@ void USS_EnhancedInputManager::RebuildContextStack()
|
|||||||
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
|
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
|
||||||
if (!Subsystem)
|
if (!Subsystem)
|
||||||
{
|
{
|
||||||
UE_LOG(LogInput, Verbose, TEXT("RebuildContextStack — No local player subsystem available yet"));
|
UE_LOG(LogFrameworkInput, Verbose, TEXT("RebuildContextStack — No local player subsystem available yet"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,8 +342,14 @@ void USS_EnhancedInputManager::RebuildContextStack()
|
|||||||
return static_cast<int32>(A.Priority) < static_cast<int32>(B.Priority);
|
return static_cast<int32>(A.Priority) < static_cast<int32>(B.Priority);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear all and re-add in priority order.
|
// UE 5.7+: RemoveAllMappingContexts was removed. Remove individually.
|
||||||
Subsystem->RemoveAllMappingContexts();
|
for (const FInputContextEntry& Existing : ContextStack)
|
||||||
|
{
|
||||||
|
if (Existing.Context)
|
||||||
|
{
|
||||||
|
Subsystem->RemoveMappingContext(Existing.Context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const FInputContextEntry& Entry : ContextStack)
|
for (const FInputContextEntry& Entry : ContextStack)
|
||||||
{
|
{
|
||||||
@@ -352,7 +359,7 @@ void USS_EnhancedInputManager::RebuildContextStack()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UE_LOG(LogInput, Verbose, TEXT("RebuildContextStack — %d contexts applied"), ContextStack.Num());
|
UE_LOG(LogFrameworkInput, Verbose, TEXT("RebuildContextStack — %d contexts applied"), ContextStack.Num());
|
||||||
}
|
}
|
||||||
|
|
||||||
UEnhancedInputLocalPlayerSubsystem* USS_EnhancedInputManager::GetEnhancedInputSubsystem() const
|
UEnhancedInputLocalPlayerSubsystem* USS_EnhancedInputManager::GetEnhancedInputSubsystem() const
|
||||||
13
Source/PG_Framework/Private/Inventory/DA_EquipmentConfig.cpp
Normal file
13
Source/PG_Framework/Private/Inventory/DA_EquipmentConfig.cpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#include "Inventory/DA_EquipmentConfig.h"
|
||||||
|
|
||||||
|
float UDA_EquipmentConfig::GetResistance(FGameplayTag DamageType) const
|
||||||
|
{
|
||||||
|
for (const FDamageTypeResistance& Entry : DamageTypeResistances)
|
||||||
|
{
|
||||||
|
if (Entry.DamageType == DamageType)
|
||||||
|
{
|
||||||
|
return Entry.Resistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
@@ -36,8 +36,8 @@ void UDA_ItemData::PostLoad()
|
|||||||
if (ItemTag.IsValid())
|
if (ItemTag.IsValid())
|
||||||
{
|
{
|
||||||
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
|
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
|
||||||
FGameplayTag CheckTag;
|
FGameplayTag CheckTag = TagManager.RequestGameplayTag(ItemTag.GetTagName(), false);
|
||||||
if (!TagManager.RequestGameplayTag(ItemTag.GetTagName(), CheckTag))
|
if (!CheckTag.IsValid())
|
||||||
{
|
{
|
||||||
UE_LOG(LogItemData, Warning, TEXT("DA_ItemData::PostLoad — '%s': ItemTag '%s' is not registered in the tag table!"),
|
UE_LOG(LogItemData, Warning, TEXT("DA_ItemData::PostLoad — '%s': ItemTag '%s' is not registered in the tag table!"),
|
||||||
*GetName(), *ItemTag.GetTagName().ToString());
|
*GetName(), *ItemTag.GetTagName().ToString());
|
||||||
6
Source/PG_Framework/Private/Player/BPC_HealthSystem.cpp
Normal file
6
Source/PG_Framework/Private/Player/BPC_HealthSystem.cpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#include "Player/BPC_HealthSystem.h"
|
||||||
|
|
||||||
|
UBPC_HealthSystem::UBPC_HealthSystem()
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bCanEverTick = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#include "Player/BPC_MovementStateSystem.h"
|
||||||
|
|
||||||
|
UBPC_MovementStateSystem::UBPC_MovementStateSystem()
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bCanEverTick = true;
|
||||||
|
}
|
||||||
6
Source/PG_Framework/Private/Player/BPC_StaminaSystem.cpp
Normal file
6
Source/PG_Framework/Private/Player/BPC_StaminaSystem.cpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#include "Player/BPC_StaminaSystem.h"
|
||||||
|
|
||||||
|
UBPC_StaminaSystem::UBPC_StaminaSystem()
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bCanEverTick = true;
|
||||||
|
}
|
||||||
@@ -2,10 +2,17 @@
|
|||||||
// UE5 Modular Game Framework — BPC_StateManager Implementation
|
// UE5 Modular Game Framework — BPC_StateManager Implementation
|
||||||
|
|
||||||
#include "Player/BPC_StateManager.h"
|
#include "Player/BPC_StateManager.h"
|
||||||
|
#include "Player/BPC_HealthSystem.h"
|
||||||
|
#include "Player/BPC_StressSystem.h"
|
||||||
|
#include "Player/BPC_StaminaSystem.h"
|
||||||
|
#include "Player/BPC_MovementStateSystem.h"
|
||||||
#include "GameplayTagsManager.h"
|
#include "GameplayTagsManager.h"
|
||||||
|
|
||||||
DEFINE_LOG_CATEGORY_STATIC(LogStateManager, Log, All);
|
DEFINE_LOG_CATEGORY_STATIC(LogStateManager, Log, All);
|
||||||
|
|
||||||
|
// Stub variable for combat encounter check (would come from GS_CoreGameState binding).
|
||||||
|
namespace { bool bEncounterActive = false; }
|
||||||
|
|
||||||
UBPC_StateManager::UBPC_StateManager()
|
UBPC_StateManager::UBPC_StateManager()
|
||||||
{
|
{
|
||||||
PrimaryComponentTick.bCanEverTick = true;
|
PrimaryComponentTick.bCanEverTick = true;
|
||||||
@@ -285,6 +292,3 @@ EHeartRateTier UBPC_StateManager::GetHeartRateTier(float BPM)
|
|||||||
if (BPM < 160.0f) return EHeartRateTier::Panic;
|
if (BPM < 160.0f) return EHeartRateTier::Panic;
|
||||||
return EHeartRateTier::Critical;
|
return EHeartRateTier::Critical;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stub variable for combat encounter check (would come from GS_CoreGameState binding).
|
|
||||||
namespace { bool bEncounterActive = false; }
|
|
||||||
6
Source/PG_Framework/Private/Player/BPC_StressSystem.cpp
Normal file
6
Source/PG_Framework/Private/Player/BPC_StressSystem.cpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#include "Player/BPC_StressSystem.h"
|
||||||
|
|
||||||
|
UBPC_StressSystem::UBPC_StressSystem()
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bCanEverTick = true;
|
||||||
|
}
|
||||||
5
Source/PG_Framework/Private/Player/PC_CoreController.cpp
Normal file
5
Source/PG_Framework/Private/Player/PC_CoreController.cpp
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#include "Player/PC_CoreController.h"
|
||||||
|
|
||||||
|
APC_CoreController::APC_CoreController()
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#include "Player/PS_CorePlayerState.h"
|
||||||
|
|
||||||
|
APS_CorePlayerState::APS_CorePlayerState()
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -327,7 +327,8 @@ bool USS_SaveManager::SaveToFile(int32 SlotIndex, const TArray<uint8>& Data, con
|
|||||||
FMemoryWriter Writer(FileData);
|
FMemoryWriter Writer(FileData);
|
||||||
|
|
||||||
// Write metadata header first.
|
// Write metadata header first.
|
||||||
Writer << const_cast<FSaveSlotInfo&>(Meta);
|
FSaveSlotInfo& MetaRef = const_cast<FSaveSlotInfo&>(Meta);
|
||||||
|
FSaveSlotInfo::StaticStruct()->SerializeItem(Writer, &MetaRef, nullptr);
|
||||||
|
|
||||||
// Then write game state data.
|
// Then write game state data.
|
||||||
Writer.Serialize(const_cast<uint8*>(Data.GetData()), Data.Num());
|
Writer.Serialize(const_cast<uint8*>(Data.GetData()), Data.Num());
|
||||||
@@ -358,7 +359,7 @@ bool USS_SaveManager::LoadFromFile(int32 SlotIndex, TArray<uint8>& OutData, FSav
|
|||||||
|
|
||||||
// Deserialize metadata header.
|
// Deserialize metadata header.
|
||||||
FMemoryReader Reader(FileData);
|
FMemoryReader Reader(FileData);
|
||||||
Reader << OutMeta;
|
FSaveSlotInfo::StaticStruct()->SerializeItem(Reader, &OutMeta, nullptr);
|
||||||
|
|
||||||
// Remaining bytes are game state data.
|
// Remaining bytes are game state data.
|
||||||
int32 HeaderSize = Reader.Tell();
|
int32 HeaderSize = Reader.Tell();
|
||||||
13
Source/PG_Framework/Private/State/DA_StateGatingTable.cpp
Normal file
13
Source/PG_Framework/Private/State/DA_StateGatingTable.cpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#include "State/DA_StateGatingTable.h"
|
||||||
|
|
||||||
|
bool UDA_StateGatingTable::IsActionGated(FGameplayTag ActionTag, FGameplayTag CurrentState) const
|
||||||
|
{
|
||||||
|
for (const FStateGatingRule& Rule : GatingRules)
|
||||||
|
{
|
||||||
|
if (Rule.ActionTag == ActionTag && Rule.BlockedByState == CurrentState)
|
||||||
|
{
|
||||||
|
return Rule.bIsBlocked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -2,8 +2,11 @@
|
|||||||
// UE5 Modular Game Framework — BPC_DamageReceptionSystem Implementation
|
// UE5 Modular Game Framework — BPC_DamageReceptionSystem Implementation
|
||||||
|
|
||||||
#include "Weapons/BPC_DamageReceptionSystem.h"
|
#include "Weapons/BPC_DamageReceptionSystem.h"
|
||||||
|
#include "Player/BPC_HealthSystem.h"
|
||||||
|
#include "Weapons/BPC_ShieldDefenseSystem.h"
|
||||||
|
#include "Weapons/BPC_HitReactionSystem.h"
|
||||||
|
|
||||||
DEFINE_LOG_CATEGORY_STATIC(LogDamage, Log, All);
|
DEFINE_LOG_CATEGORY_STATIC(LogFrameworkDamage, Log, All);
|
||||||
|
|
||||||
UBPC_DamageReceptionSystem::UBPC_DamageReceptionSystem()
|
UBPC_DamageReceptionSystem::UBPC_DamageReceptionSystem()
|
||||||
{
|
{
|
||||||
@@ -67,7 +70,7 @@ float UBPC_DamageReceptionSystem::ApplyDamage(float RawDamage, AActor* DamageCau
|
|||||||
FString::Printf(TEXT("Resistance: %.1f%%"), Resistance * 100.0f));
|
FString::Printf(TEXT("Resistance: %.1f%%"), Resistance * 100.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
UE_LOG(LogDamage, Verbose, TEXT("ApplyDamage — Raw: %.1f → Final: %.1f (Type: %s, Resist: %.1f%%)"),
|
UE_LOG(LogFrameworkDamage, Verbose, TEXT("ApplyDamage — Raw: %.1f → Final: %.1f (Type: %s, Resist: %.1f%%)"),
|
||||||
RawDamage, FinalDamage, *DamageType.GetTagName().ToString(), Resistance * 100.0f);
|
RawDamage, FinalDamage, *DamageType.GetTagName().ToString(), Resistance * 100.0f);
|
||||||
|
|
||||||
return FinalDamage;
|
return FinalDamage;
|
||||||
@@ -119,12 +122,12 @@ void UBPC_DamageReceptionSystem::EvaluateHitReaction(float FinalDamage, AActor*
|
|||||||
{
|
{
|
||||||
if (FinalDamage >= KnockdownThreshold)
|
if (FinalDamage >= KnockdownThreshold)
|
||||||
{
|
{
|
||||||
UE_LOG(LogDamage, Log, TEXT("EvaluateHitReaction — Knockdown! (%.1f damage)"), FinalDamage);
|
UE_LOG(LogFrameworkDamage, Log, TEXT("EvaluateHitReaction — Knockdown! (%.1f damage)"), FinalDamage);
|
||||||
OnKnockedDown.Broadcast(DamageCauser, FinalDamage);
|
OnKnockedDown.Broadcast(DamageCauser, FinalDamage);
|
||||||
}
|
}
|
||||||
else if (FinalDamage >= StaggerThreshold)
|
else if (FinalDamage >= StaggerThreshold)
|
||||||
{
|
{
|
||||||
UE_LOG(LogDamage, Verbose, TEXT("EvaluateHitReaction — Stagger (%.1f damage)"), FinalDamage);
|
UE_LOG(LogFrameworkDamage, Verbose, TEXT("EvaluateHitReaction — Stagger (%.1f damage)"), FinalDamage);
|
||||||
OnStaggered.Broadcast(DamageCauser, FinalDamage);
|
OnStaggered.Broadcast(DamageCauser, FinalDamage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#include "Weapons/BPC_HitReactionSystem.h"
|
||||||
|
|
||||||
|
UBPC_HitReactionSystem::UBPC_HitReactionSystem()
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bCanEverTick = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UBPC_HitReactionSystem::PlayHitReaction(float DamageAmount, FVector HitDirection, AActor* DamageCauser)
|
||||||
|
{
|
||||||
|
// Stub — Blueprint child provides animation selection logic.
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#include "Weapons/BPC_ShieldDefenseSystem.h"
|
||||||
|
|
||||||
|
UBPC_ShieldDefenseSystem::UBPC_ShieldDefenseSystem()
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bCanEverTick = false;
|
||||||
|
}
|
||||||
278
Source/PG_Framework/Public/Capture/BPC_PlanarCapture.h
Normal file
278
Source/PG_Framework/Public/Capture/BPC_PlanarCapture.h
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
// Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
// UE5 Modular Game Framework — BPC_PlanarCapture (136)
|
||||||
|
// The heart of the Planar Capture System. Attached to any actor that needs
|
||||||
|
// a scene capture — mirrors, portals, monitors, horror surfaces.
|
||||||
|
//
|
||||||
|
// Manages USceneCaptureComponent2D lifetime, render target pool allocation,
|
||||||
|
// camera transform computation per mode, oblique clip plane injection,
|
||||||
|
// show/hide actor lists, quality tier application, frame ring buffer,
|
||||||
|
// and MPC parameter pushes.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "Capture/PlanarCaptureCommon.h"
|
||||||
|
#include "BPC_PlanarCapture.generated.h"
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
class USceneCaptureComponent2D;
|
||||||
|
class UTextureRenderTarget2D;
|
||||||
|
class UMaterialInstanceDynamic;
|
||||||
|
class UMaterialParameterCollection;
|
||||||
|
class USS_PlanarCaptureManager;
|
||||||
|
class ABP_PlanarCaptureActor;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Delegates
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnCaptureQualityChanged, EPlanarCaptureQualityTier, OldTier, EPlanarCaptureQualityTier, NewTier);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCaptureInitialized, EPlanarCaptureInitResult, Result);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnCaptureRendered);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BPC_PlanarCapture — Core capture component for mirrors, portals, monitors, etc.
|
||||||
|
*
|
||||||
|
* Owns the USceneCaptureComponent2D lifecycle. All camera math, render target
|
||||||
|
* management, and per-frame capture decisions happen here in C++ for performance.
|
||||||
|
* Designer configuration flows through Blueprint children.
|
||||||
|
*
|
||||||
|
* Multiplayer: Capture is always local-only. Each client renders their own view.
|
||||||
|
* No replication needed. This component only exists on clients.
|
||||||
|
*/
|
||||||
|
UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
|
||||||
|
class PG_FRAMEWORK_API UBPC_PlanarCapture : public UActorComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UBPC_PlanarCapture();
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Lifecycle
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||||
|
virtual void TickComponent(float DeltaTime, ELevelTick TickType,
|
||||||
|
FActorComponentTickFunction* ThisTickFunction) override;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Configuration — Set in Blueprint Defaults
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** What kind of surface this capture represents. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
|
||||||
|
EPlanarCaptureMode CaptureMode = EPlanarCaptureMode::Mirror;
|
||||||
|
|
||||||
|
/** Quality profiles per tier (0=Low, 1=Medium, 2=High, 3=Hero). Index maps to EPlanarCaptureQualityTier. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
|
||||||
|
TArray<FPlanarCaptureQualityProfile> QualityProfiles;
|
||||||
|
|
||||||
|
/** FOV for the capture camera. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
|
||||||
|
float CaptureFOV = 90.0f;
|
||||||
|
|
||||||
|
/** Maximum view distance for the capture (0 = unlimited). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
|
||||||
|
float MaxViewDistance = 5000.0f;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Portal Configuration
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** For Portal mode: the linked target surface actor. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Portal")
|
||||||
|
TSoftObjectPtr<ABP_PlanarCaptureActor> LinkedTargetSurface;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Monitor Configuration
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** For Monitor mode: a fixed camera actor to capture from. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Monitor")
|
||||||
|
TSoftObjectPtr<AActor> FixedCameraActor;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Actor Lists
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** Actors to show exclusively in the capture (empty = show all). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|ActorLists")
|
||||||
|
TArray<FPlanarCaptureActorListEntry> ShowOnlyActors;
|
||||||
|
|
||||||
|
/** Actors to hide from the capture. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|ActorLists")
|
||||||
|
TArray<FPlanarCaptureActorListEntry> HiddenActors;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Horror Configuration
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** Actor to swap into the ShowOnly list during wrong-reflection events. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Horror")
|
||||||
|
TSoftObjectPtr<AActor> WrongReflectionActor;
|
||||||
|
|
||||||
|
/** Mesh component of the surface plane (for clip plane calculation). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Config")
|
||||||
|
TSoftObjectPtr<UStaticMeshComponent> SurfaceMeshComponent;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Runtime State (Read-Only)
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** Current assigned quality tier (set by SS_PlanarCaptureManager). */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Capture|Runtime")
|
||||||
|
EPlanarCaptureQualityTier CurrentQualityTier = EPlanarCaptureQualityTier::Off;
|
||||||
|
|
||||||
|
/** Is this capture currently active (capturing frames)? */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Capture|Runtime")
|
||||||
|
bool bIsCapturing = false;
|
||||||
|
|
||||||
|
/** The render target this capture writes to. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Capture|Runtime")
|
||||||
|
TObjectPtr<UTextureRenderTarget2D> CaptureRenderTarget;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Public API — BlueprintCallable
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the capture component. Allocates render target, creates and
|
||||||
|
* configures the USceneCaptureComponent2D. Called by the owning actor on BeginPlay.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture")
|
||||||
|
EPlanarCaptureInitResult InitializeCapture();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shut down the capture, release render target back to pool, destroy SceneCaptureComponent2D.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture")
|
||||||
|
void ShutdownCapture();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a quality tier profile immediately. Called by SS_PlanarCaptureManager.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture")
|
||||||
|
void ApplyQualityTier(EPlanarCaptureQualityTier Tier);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger a single capture frame immediately (bypasses tick interval).
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture")
|
||||||
|
void CaptureNow();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swap the ShowOnly actor list to the wrong-reflection actor (horror mode).
|
||||||
|
* Original list is preserved and restored on DeactivateHorrorReflection().
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture|Horror")
|
||||||
|
void ActivateHorrorReflection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the original ShowOnly actor list after a horror event.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture|Horror")
|
||||||
|
void DeactivateHorrorReflection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a frame from the ring buffer into the render target for delayed reflection (horror lag).
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture|Horror")
|
||||||
|
void PushDelayedFrame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a scripted priority override (0.0 to 1.0). Higher values force higher quality tier.
|
||||||
|
* Example: a scare event elevates a specific mirror to Hero tier.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture")
|
||||||
|
void SetScriptedPriority(float Priority);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push all Material Parameter Collection values for this surface.
|
||||||
|
* Called every frame when capturing, or on event trigger for horror params.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture|Material")
|
||||||
|
void PushMPCParameters(UMaterialParameterCollection* MPC);
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Compute — BlueprintPure
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** Compute the capture camera transform for the current mode. */
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture")
|
||||||
|
FTransform ComputeCaptureCameraTransform(const FTransform& ViewerCameraTransform) const;
|
||||||
|
|
||||||
|
/** Get the current composite quality score. */
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture")
|
||||||
|
FPlanarCaptureScore GetCurrentScore() const;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Event Dispatchers
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Capture|Events")
|
||||||
|
FOnCaptureQualityChanged OnCaptureQualityChanged;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Capture|Events")
|
||||||
|
FOnCaptureInitialized OnCaptureInitialized;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Capture|Events")
|
||||||
|
FOnCaptureRendered OnCaptureRendered;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// ========================================================================
|
||||||
|
// Internal State
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** The actual UE5 SceneCaptureComponent2D — created at runtime. */
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<USceneCaptureComponent2D> SceneCapture;
|
||||||
|
|
||||||
|
/** Cached reference to the manager subsystem. */
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<USS_PlanarCaptureManager> CachedManager;
|
||||||
|
|
||||||
|
/** Cached reference to the owning actor. */
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<ABP_PlanarCaptureActor> CachedOwningActor;
|
||||||
|
|
||||||
|
/** Time accumulator for capture interval throttling. */
|
||||||
|
float TimeSinceLastCapture = 0.0f;
|
||||||
|
|
||||||
|
/** Current quality profile (cached from QualityProfiles[Tier]). */
|
||||||
|
FPlanarCaptureQualityProfile ActiveProfile;
|
||||||
|
|
||||||
|
/** Scripted priority override (0.0 to 1.0). */
|
||||||
|
float ScriptedPriorityOverride = 0.0f;
|
||||||
|
|
||||||
|
/** Ring buffer of render targets for delayed reflection (horror mode). */
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<TObjectPtr<UTextureRenderTarget2D>> FrameRingBuffer;
|
||||||
|
|
||||||
|
/** Current write index into the frame ring buffer. */
|
||||||
|
int32 RingBufferWriteIndex = 0;
|
||||||
|
|
||||||
|
/** Saved ShowOnly list before horror swap. */
|
||||||
|
TArray<TSoftObjectPtr<AActor>> SavedShowOnlyActors;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Internal Methods
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** Create and configure the USceneCaptureComponent2D. */
|
||||||
|
void CreateSceneCaptureComponent();
|
||||||
|
|
||||||
|
/** Apply show flags from the active quality profile to the SceneCapture. */
|
||||||
|
void ApplyShowFlags();
|
||||||
|
|
||||||
|
/** Update the ShowOnly and Hidden actor lists on the SceneCapture. */
|
||||||
|
void UpdateActorLists();
|
||||||
|
|
||||||
|
/** Resolve soft references on initialization. */
|
||||||
|
void ResolveSoftReferences();
|
||||||
|
|
||||||
|
/** Compute the surface plane in world space for clip plane and mirror math. */
|
||||||
|
FPlane GetSurfacePlane() const;
|
||||||
|
};
|
||||||
170
Source/PG_Framework/Public/Capture/BP_PlanarCaptureActor.h
Normal file
170
Source/PG_Framework/Public/Capture/BP_PlanarCaptureActor.h
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
// Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
// UE5 Modular Game Framework — BP_PlanarCaptureActor (137)
|
||||||
|
// Placeable actor wrapping a BPC_PlanarCapture component. Owns the surface mesh,
|
||||||
|
// material dynamic instances, and the capture component. Handles overlap/proximity
|
||||||
|
// events feeding into the quality manager. Registers with the global subsystem.
|
||||||
|
//
|
||||||
|
// Blueprint children: BP_Mirror, BP_Portal, BP_Monitor, BP_HorrorMirror, BP_FakeWindow.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "Capture/PlanarCaptureCommon.h"
|
||||||
|
#include "BP_PlanarCaptureActor.generated.h"
|
||||||
|
|
||||||
|
class UBPC_PlanarCapture;
|
||||||
|
class UStaticMeshComponent;
|
||||||
|
class UMaterialInstanceDynamic;
|
||||||
|
class UMaterialParameterCollection;
|
||||||
|
class UBoxComponent;
|
||||||
|
|
||||||
|
// Delegates
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlanarCaptureActorModeChanged, EPlanarCaptureMode, NewMode);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlanarCaptureSurfaceDestroyed, ABP_PlanarCaptureActor*, Surface);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BP_PlanarCaptureActor — Placeable actor for mirrors, portals, monitors, etc.
|
||||||
|
*
|
||||||
|
* Blueprint children configure the mode, mesh, materials, and capture settings.
|
||||||
|
* This actor is the designer-facing interface — all runtime logic lives in
|
||||||
|
* BPC_PlanarCapture and SS_PlanarCaptureManager.
|
||||||
|
*
|
||||||
|
* Multiplayer: Spawned on all clients. Capture rendering is local-only.
|
||||||
|
* Surface state (destroyed, on/off) may replicate via RepNotify.
|
||||||
|
*/
|
||||||
|
UCLASS(Blueprintable, BlueprintType)
|
||||||
|
class PG_FRAMEWORK_API ABP_PlanarCaptureActor : public AActor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
ABP_PlanarCaptureActor();
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Components
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** The surface mesh — a plane, quad, or frame. */
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Capture|Components")
|
||||||
|
TObjectPtr<UStaticMeshComponent> SurfaceMesh;
|
||||||
|
|
||||||
|
/** Proximity trigger volume for quality scoring. */
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Capture|Components")
|
||||||
|
TObjectPtr<UBoxComponent> ProximityTrigger;
|
||||||
|
|
||||||
|
/** The core capture component. */
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Capture|Components")
|
||||||
|
TObjectPtr<UBPC_PlanarCapture> CaptureComponent;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Configuration
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** Display name for debug and logging. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
|
||||||
|
FString SurfaceDisplayName;
|
||||||
|
|
||||||
|
/** Whether this surface starts enabled. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
|
||||||
|
bool bStartEnabled = true;
|
||||||
|
|
||||||
|
/** Whether this surface can be destroyed (shattered mirror, broken monitor). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Config")
|
||||||
|
bool bDestructible = false;
|
||||||
|
|
||||||
|
/** Material Parameter Collection for global surface parameters. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Capture|Material")
|
||||||
|
TObjectPtr<UMaterialParameterCollection> SurfaceMPC;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Runtime State
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** Whether this surface is currently active. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Capture|Runtime")
|
||||||
|
bool bIsActive = false;
|
||||||
|
|
||||||
|
/** Dynamic material instance on the surface mesh. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Capture|Runtime")
|
||||||
|
TObjectPtr<UMaterialInstanceDynamic> SurfaceMaterialInstance;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Public API
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** Enable the capture surface. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture")
|
||||||
|
void EnableSurface();
|
||||||
|
|
||||||
|
/** Disable the capture surface (stops rendering, releases budget). */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture")
|
||||||
|
void DisableSurface();
|
||||||
|
|
||||||
|
/** Set the capture mode at runtime. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture")
|
||||||
|
void SetCaptureMode(EPlanarCaptureMode NewMode);
|
||||||
|
|
||||||
|
/** Set the surface material at runtime (e.g., swap between clean/dirty mirror). */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture")
|
||||||
|
void SetSurfaceMaterial(UMaterialInterface* NewMaterial);
|
||||||
|
|
||||||
|
/** Set a single MPC scalar parameter for this surface. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture|Material")
|
||||||
|
void SetSurfaceMPCParameter(FName ParameterName, float Value);
|
||||||
|
|
||||||
|
/** Destroy the surface (shatter mirror, break monitor). Triggers visual effect. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture")
|
||||||
|
void DestroySurface();
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Overrides
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Event Dispatchers
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Capture|Events")
|
||||||
|
FOnPlanarCaptureActorModeChanged OnModeChanged;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Capture|Events")
|
||||||
|
FOnPlanarCaptureSurfaceDestroyed OnSurfaceDestroyed;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// ========================================================================
|
||||||
|
// Overlap Events (Proximity Trigger)
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnProximityBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
|
||||||
|
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnProximityEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
|
||||||
|
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// RepNotify (multiplayer)
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnRep_IsActive();
|
||||||
|
|
||||||
|
/** Replicated active state for server-authoritative surface control. */
|
||||||
|
UPROPERTY(ReplicatedUsing = OnRep_IsActive)
|
||||||
|
bool bRepIsActive = false;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Internal
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** Register with the global manager subsystem. */
|
||||||
|
void RegisterWithManager();
|
||||||
|
|
||||||
|
/** Create the dynamic material instance from the surface mesh material. */
|
||||||
|
void CreateMaterialInstance();
|
||||||
|
};
|
||||||
155
Source/PG_Framework/Public/Capture/PlanarCaptureCameraUtils.h
Normal file
155
Source/PG_Framework/Public/Capture/PlanarCaptureCameraUtils.h
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
// Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
// UE5 Modular Game Framework — PlanarCaptureCameraUtils
|
||||||
|
// Static math library for mirror reflection, portal relative transform,
|
||||||
|
// oblique projection, screen coverage, and visibility computations.
|
||||||
|
// All functions are BlueprintCallable and use UE5's FMatrix/FVector/FPlane types.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||||
|
#include "PlanarCaptureCameraUtils.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static math library for planar capture camera transforms.
|
||||||
|
*
|
||||||
|
* Mirror: reflect the viewer camera across the mirror plane.
|
||||||
|
* Portal: compute the relative transform from source surface to target surface.
|
||||||
|
* All math happens in C++ for performance — Blueprint calls these as pure functions.
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class PG_FRAMEWORK_API UPlanarCaptureCameraUtils : public UBlueprintFunctionLibrary
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
// ========================================================================
|
||||||
|
// Mirror Reflection Math
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the mirrored camera transform for a planar mirror.
|
||||||
|
* Reflects the viewer's camera position and rotation across the mirror plane.
|
||||||
|
*
|
||||||
|
* @param ViewerCameraTransform World transform of the viewer's camera.
|
||||||
|
* @param MirrorPlaneTransform World transform of the mirror surface (XY plane, Z = normal).
|
||||||
|
* @return The world transform for the SceneCapture camera.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|CameraUtils")
|
||||||
|
static FTransform ComputeMirroredTransform(
|
||||||
|
const FTransform& ViewerCameraTransform,
|
||||||
|
const FTransform& MirrorPlaneTransform);
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Portal Relative Math
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the capture camera transform for a portal.
|
||||||
|
* The viewer's position relative to the source surface is mapped to the
|
||||||
|
* target surface's coordinate space.
|
||||||
|
*
|
||||||
|
* @param ViewerCameraTransform World transform of the viewer's camera.
|
||||||
|
* @param SourceSurfaceTransform World transform of the portal entry surface.
|
||||||
|
* @param TargetSurfaceTransform World transform of the portal exit surface.
|
||||||
|
* @return The world transform for the SceneCapture camera.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|CameraUtils")
|
||||||
|
static FTransform ComputePortalTransform(
|
||||||
|
const FTransform& ViewerCameraTransform,
|
||||||
|
const FTransform& SourceSurfaceTransform,
|
||||||
|
const FTransform& TargetSurfaceTransform);
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Oblique Near-Plane Projection
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute an oblique near-clip-plane projection matrix.
|
||||||
|
* Used to prevent geometry behind the portal/mirror surface from clipping
|
||||||
|
* into the capture view.
|
||||||
|
*
|
||||||
|
* @param FOV Horizontal field of view in degrees.
|
||||||
|
* @param AspectRatio Width / Height.
|
||||||
|
* @param NearPlane Near clip distance.
|
||||||
|
* @param FarPlane Far clip distance.
|
||||||
|
* @param ClipPlane World-space plane to clip against.
|
||||||
|
* @param SurfaceTransform Transform of the surface (for converting clip plane to view space).
|
||||||
|
* @return The oblique projection matrix.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|CameraUtils")
|
||||||
|
static FMatrix ComputeObliqueProjectionMatrix(
|
||||||
|
float FOV,
|
||||||
|
float AspectRatio,
|
||||||
|
float NearPlane,
|
||||||
|
float FarPlane,
|
||||||
|
const FPlane& ClipPlane,
|
||||||
|
const FTransform& SurfaceTransform);
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Screen Coverage & Visibility
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate how much of the screen a capture surface occupies (0.0 to 1.0).
|
||||||
|
* Uses the surface's bounding box corners projected to screen-space.
|
||||||
|
*
|
||||||
|
* @param SurfaceBounds Bounding box of the surface mesh in world space.
|
||||||
|
* @param ViewerTransform World transform of the viewer's camera.
|
||||||
|
* @param ViewerFOV Horizontal FOV of the viewer.
|
||||||
|
* @param ScreenWidth Viewport width in pixels.
|
||||||
|
* @param ScreenHeight Viewport height in pixels.
|
||||||
|
* @return Estimated screen coverage ratio (0.0 to 1.0).
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|CameraUtils")
|
||||||
|
static float ComputeScreenCoverage(
|
||||||
|
const FBox& SurfaceBounds,
|
||||||
|
const FTransform& ViewerTransform,
|
||||||
|
float ViewerFOV,
|
||||||
|
int32 ScreenWidth,
|
||||||
|
int32 ScreenHeight);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the surface is visible to the viewer's frustum.
|
||||||
|
*
|
||||||
|
* @param SurfaceBounds Bounding box of the surface mesh in world space.
|
||||||
|
* @param ViewerTransform World transform of the viewer's camera.
|
||||||
|
* @param ViewerFOV Horizontal FOV.
|
||||||
|
* @param ViewerAspectRatio Width / Height.
|
||||||
|
* @param ViewerNearPlane Near clip distance.
|
||||||
|
* @param ViewerFarPlane Far clip distance.
|
||||||
|
* @return True if any part of the surface is within the frustum.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|CameraUtils")
|
||||||
|
static bool IsSurfaceVisibleToViewer(
|
||||||
|
const FBox& SurfaceBounds,
|
||||||
|
const FTransform& ViewerTransform,
|
||||||
|
float ViewerFOV,
|
||||||
|
float ViewerAspectRatio,
|
||||||
|
float ViewerNearPlane,
|
||||||
|
float ViewerFarPlane);
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Quality Scoring
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute a composite priority score for a capture surface.
|
||||||
|
* Higher score = higher quality tier assignment.
|
||||||
|
* Formula: (ScreenCoverage * 0.5) + (FacingAngle * 0.3) + (DistanceFactor * 0.1) + (ScriptedPriority * 0.1)
|
||||||
|
*
|
||||||
|
* @param ScreenCoverage How much screen real estate the surface occupies.
|
||||||
|
* @param FacingAngle Dot product of viewer forward to surface normal.
|
||||||
|
* @param DistanceToViewer Distance in world units.
|
||||||
|
* @param MaxDistance Distance at which score drops to zero.
|
||||||
|
* @param ScriptedPriority Priority override from gameplay systems (0.0 to 1.0).
|
||||||
|
* @return Composite score (0.0 to 1.0).
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|CameraUtils")
|
||||||
|
static float ComputeCompositeScore(
|
||||||
|
float ScreenCoverage,
|
||||||
|
float FacingAngle,
|
||||||
|
float DistanceToViewer,
|
||||||
|
float MaxDistance,
|
||||||
|
float ScriptedPriority);
|
||||||
|
};
|
||||||
189
Source/PG_Framework/Public/Capture/PlanarCaptureCommon.h
Normal file
189
Source/PG_Framework/Public/Capture/PlanarCaptureCommon.h
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
// Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
// UE5 Modular Game Framework — PlanarCaptureCommon
|
||||||
|
// Shared enums, structs, and quality profile definitions for the Planar Capture System.
|
||||||
|
// Used by BPC_PlanarCapture, BP_PlanarCaptureActor, and SS_PlanarCaptureManager.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Engine/TextureRenderTarget2D.h"
|
||||||
|
#include "PlanarCaptureCommon.generated.h"
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Enums
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mode of the capture surface — determines camera math, rendering behavior,
|
||||||
|
* and material configuration.
|
||||||
|
*/
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EPlanarCaptureMode : uint8
|
||||||
|
{
|
||||||
|
Mirror UMETA(DisplayName = "Mirror"),
|
||||||
|
Portal UMETA(DisplayName = "Portal"),
|
||||||
|
Monitor UMETA(DisplayName = "Monitor / Security Screen"),
|
||||||
|
HorrorMirror UMETA(DisplayName = "Horror Mirror"),
|
||||||
|
HorrorPortal UMETA(DisplayName = "Horror Portal"),
|
||||||
|
FakeWindow UMETA(DisplayName = "Fake Window"),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quality tier for a capture surface. Managed globally by SS_PlanarCaptureManager.
|
||||||
|
*/
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EPlanarCaptureQualityTier : uint8
|
||||||
|
{
|
||||||
|
Off UMETA(DisplayName = "Off — No capture"),
|
||||||
|
Low UMETA(DisplayName = "Low — 256px, 4fps"),
|
||||||
|
Medium UMETA(DisplayName = "Medium — 512px, 15fps"),
|
||||||
|
High UMETA(DisplayName = "High — 1024px, 30fps"),
|
||||||
|
Hero UMETA(DisplayName = "Hero — 2048px, 60fps"),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result codes for capture surface initialization.
|
||||||
|
*/
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EPlanarCaptureInitResult : uint8
|
||||||
|
{
|
||||||
|
Success UMETA(DisplayName = "Success"),
|
||||||
|
NoRenderTargetPool UMETA(DisplayName = "Failed — No Render Target in Pool"),
|
||||||
|
InvalidSurfaceMesh UMETA(DisplayName = "Failed — Invalid Surface Mesh"),
|
||||||
|
BudgetExceeded UMETA(DisplayName = "Failed — Budget Exceeded"),
|
||||||
|
ManagerUnavailable UMETA(DisplayName = "Failed — Manager Unavailable"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Structs
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quality profile — defines render target resolution, capture interval,
|
||||||
|
* and per-feature toggles for a single quality tier.
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct PG_FRAMEWORK_API FPlanarCaptureQualityProfile
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** Render target resolution (square: 256, 512, 1024, 2048). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Quality")
|
||||||
|
int32 RenderTargetSize = 512;
|
||||||
|
|
||||||
|
/** Minimum interval between captures in seconds (1.0 / FPS). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Quality")
|
||||||
|
float CaptureInterval = 0.0667f; // ~15fps
|
||||||
|
|
||||||
|
/** Shadow rendering mode for the capture. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
|
||||||
|
bool bEnableShadows = true;
|
||||||
|
|
||||||
|
/** Allow post-process effects in the capture. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
|
||||||
|
bool bEnablePostProcess = false;
|
||||||
|
|
||||||
|
/** Allow exponential height fog in the capture. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
|
||||||
|
bool bEnableFog = false;
|
||||||
|
|
||||||
|
/** Allow bloom in the capture. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
|
||||||
|
bool bEnableBloom = false;
|
||||||
|
|
||||||
|
/** Allow ambient occlusion in the capture. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
|
||||||
|
bool bEnableAO = false;
|
||||||
|
|
||||||
|
/** Allow Lumen global illumination in the capture (expensive). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
|
||||||
|
bool bEnableLumen = false;
|
||||||
|
|
||||||
|
/** Allow motion blur in the capture. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
|
||||||
|
bool bEnableMotionBlur = false;
|
||||||
|
|
||||||
|
/** Enable oblique near-clip plane (required for portals flush with geometry). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Features")
|
||||||
|
bool bEnableClipPlane = true;
|
||||||
|
|
||||||
|
/** Number of frames to delay the reflection (0 = off, N = horror lag effect). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Horror", meta = (ClampMin = "0", ClampMax = "30"))
|
||||||
|
int32 DelayedFrameCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single entry in a capture surface's ShowOnly or Hidden actor list.
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct PG_FRAMEWORK_API FPlanarCaptureActorListEntry
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** The actor to show exclusively or hide. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|ActorList")
|
||||||
|
TSoftObjectPtr<AActor> Actor;
|
||||||
|
|
||||||
|
/** Whether this actor is currently active in the list. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|ActorList")
|
||||||
|
bool bActive = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scoring data for a capture surface — computed each frame by the manager
|
||||||
|
* to determine quality tier assignment.
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct PG_FRAMEWORK_API FPlanarCaptureScore
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** Distance from viewer to surface in world units. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Capture|Score")
|
||||||
|
float DistanceToViewer = FLT_MAX;
|
||||||
|
|
||||||
|
/** Screen coverage percentage (0.0 to 1.0). */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Capture|Score")
|
||||||
|
float ScreenCoverage = 0.0f;
|
||||||
|
|
||||||
|
/** Dot product of viewer forward and surface normal (1.0 = facing, 0.0 = edge-on). */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Capture|Score")
|
||||||
|
float FacingAngle = 0.0f;
|
||||||
|
|
||||||
|
/** Whether the surface is within the viewer's frustum. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Capture|Score")
|
||||||
|
bool bInFrustum = false;
|
||||||
|
|
||||||
|
/** Scripted priority override (0.0 to 1.0, from gameplay systems). */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Capture|Score")
|
||||||
|
float ScriptedPriority = 0.0f;
|
||||||
|
|
||||||
|
/** Composite score (0.0 to 1.0) — higher = more important. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Capture|Score")
|
||||||
|
float CompositeScore = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render target pool entry — managed by SS_PlanarCaptureManager.
|
||||||
|
*/
|
||||||
|
USTRUCT()
|
||||||
|
struct PG_FRAMEWORK_API FPlanarCaptureRenderTargetEntry
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** The allocated render target. */
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UTextureRenderTarget2D> RenderTarget = nullptr;
|
||||||
|
|
||||||
|
/** Current size of the render target. */
|
||||||
|
UPROPERTY()
|
||||||
|
int32 CurrentSize = 0;
|
||||||
|
|
||||||
|
/** Whether this entry is currently assigned to a surface. */
|
||||||
|
UPROPERTY()
|
||||||
|
bool bInUse = false;
|
||||||
|
|
||||||
|
/** Which surface currently owns this entry. */
|
||||||
|
UPROPERTY()
|
||||||
|
TWeakObjectPtr<class ABP_PlanarCaptureActor> OwningSurface;
|
||||||
|
};
|
||||||
239
Source/PG_Framework/Public/Capture/SS_PlanarCaptureManager.h
Normal file
239
Source/PG_Framework/Public/Capture/SS_PlanarCaptureManager.h
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
// Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
// UE5 Modular Game Framework — SS_PlanarCaptureManager (138)
|
||||||
|
// Global budget manager for all planar capture surfaces in the world.
|
||||||
|
//
|
||||||
|
// One instance per World (World Subsystem). Each frame, scores every registered
|
||||||
|
// capture surface by distance, screen coverage, facing angle, and scripted priority.
|
||||||
|
// Assigns quality tiers across all surfaces respecting a global budget
|
||||||
|
// (max simultaneous high-quality captures, max total render target memory).
|
||||||
|
// Forces idle/disabled state on surfaces outside active rooms/sublevels.
|
||||||
|
//
|
||||||
|
// Also manages the render target pool — allocates, reuses, and resizes RTs
|
||||||
|
// to minimize memory churn.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Subsystems/WorldSubsystem.h"
|
||||||
|
#include "Tickable.h"
|
||||||
|
#include "Misc/Optional.h"
|
||||||
|
#include "Capture/PlanarCaptureCommon.h"
|
||||||
|
#include "SS_PlanarCaptureManager.generated.h"
|
||||||
|
|
||||||
|
class ABP_PlanarCaptureActor;
|
||||||
|
class UBPC_PlanarCapture;
|
||||||
|
|
||||||
|
// Delegates
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSurfaceRegistered, ABP_PlanarCaptureActor*, Surface, int32, TotalSurfaces);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSurfaceUnregistered, ABP_PlanarCaptureActor*, Surface, int32, TotalSurfaces);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGlobalQualityCapChanged, EPlanarCaptureQualityTier, NewCap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SS_PlanarCaptureManager — Global Planar Capture Budget Manager.
|
||||||
|
*
|
||||||
|
* Manages all ABP_PlanarCaptureActor instances per world. Evaluates priority
|
||||||
|
* and assigns quality tiers to stay within a configurable budget.
|
||||||
|
* Also owns the render target pool to reduce memory allocation overhead.
|
||||||
|
*
|
||||||
|
* Multiplayer: This subsystem exists on both server and clients.
|
||||||
|
* On the server, it tracks surfaces for replication state.
|
||||||
|
* On clients, it drives actual capture rendering.
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class PG_FRAMEWORK_API USS_PlanarCaptureManager : public UWorldSubsystem, public FTickableGameObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
USS_PlanarCaptureManager();
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Lifecycle
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||||
|
virtual void Deinitialize() override;
|
||||||
|
|
||||||
|
// FTickableGameObject
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
virtual TStatId GetStatId() const override;
|
||||||
|
virtual bool IsTickable() const override { return !IsTemplate(); }
|
||||||
|
virtual bool IsTickableInEditor() const override { return false; }
|
||||||
|
virtual UWorld* GetTickableGameObjectWorld() const override { return GetWorld(); }
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Surface Registry
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a capture surface actor with the global manager.
|
||||||
|
* Called by ABP_PlanarCaptureActor::BeginPlay.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture|Manager")
|
||||||
|
void RegisterSurface(ABP_PlanarCaptureActor* Surface);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister a capture surface actor. Called on EndPlay.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture|Manager")
|
||||||
|
void UnregisterSurface(ABP_PlanarCaptureActor* Surface);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all currently registered surfaces.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|Manager")
|
||||||
|
TArray<ABP_PlanarCaptureActor*> GetRegisteredSurfaces() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of registered surfaces.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|Manager")
|
||||||
|
int32 GetSurfaceCount() const { return RegisteredSurfaces.Num(); }
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Quality Budget Management
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global quality ceiling — caps all surfaces at this tier regardless of score.
|
||||||
|
* Use this for lower-end hardware targets.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
|
||||||
|
EPlanarCaptureQualityTier GlobalQualityCap = EPlanarCaptureQualityTier::High;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of simultaneous Hero-tier captures.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
|
||||||
|
int32 MaxHeroSurfaces = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of simultaneous High-tier captures.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
|
||||||
|
int32 MaxHighSurfaces = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of simultaneous Medium-tier captures.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
|
||||||
|
int32 MaxMediumSurfaces = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum total render target memory in megabytes across all surfaces.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
|
||||||
|
float MaxTotalRenderTargetMemoryMB = 128.0f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distance at which capture quality drops to Off (world units).
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
|
||||||
|
float MaxCaptureDistance = 10000.0f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interval between full re-evaluation of all surfaces (seconds).
|
||||||
|
* Individual surfaces check their own interval every frame via their component.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget")
|
||||||
|
float FullEvaluationInterval = 0.5f;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Render Target Pool
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a render target from the pool. Returns nullptr if none available.
|
||||||
|
*/
|
||||||
|
UTextureRenderTarget2D* RequestRenderTarget(int32 Size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release a render target back to the pool.
|
||||||
|
*/
|
||||||
|
void ReleaseRenderTarget(UTextureRenderTarget2D* RenderTarget);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total memory used by the render target pool (in MB).
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|Manager")
|
||||||
|
float GetPoolMemoryUsageMB() const;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Query
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the nearest capture surface of a given mode to a world location.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|Manager")
|
||||||
|
ABP_PlanarCaptureActor* GetNearestSurfaceOfMode(
|
||||||
|
EPlanarCaptureMode Mode, FVector WorldLocation, float MaxDistance = 0.0f) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force all surfaces to a specific quality tier (e.g., for cutscenes).
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture|Manager")
|
||||||
|
void ForceAllSurfacesToTier(EPlanarCaptureQualityTier Tier);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release the force-tier override and resume normal scoring.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Capture|Manager")
|
||||||
|
void ReleaseForceTier();
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Event Dispatchers
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Capture|Manager|Events")
|
||||||
|
FOnSurfaceRegistered OnSurfaceRegistered;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Capture|Manager|Events")
|
||||||
|
FOnSurfaceUnregistered OnSurfaceUnregistered;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Capture|Manager|Events")
|
||||||
|
FOnGlobalQualityCapChanged OnGlobalQualityCapChanged;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// ========================================================================
|
||||||
|
// Internal State
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** All registered capture surface actors. */
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<TWeakObjectPtr<ABP_PlanarCaptureActor>> RegisteredSurfaces;
|
||||||
|
|
||||||
|
/** Render target pool. */
|
||||||
|
TArray<FPlanarCaptureRenderTargetEntry> RenderTargetPool;
|
||||||
|
|
||||||
|
/** Time accumulator for full re-evaluation interval. */
|
||||||
|
float TimeSinceLastEvaluation = 0.0f;
|
||||||
|
|
||||||
|
/** Force-tier override — if set, all surfaces use this tier. */
|
||||||
|
TOptional<EPlanarCaptureQualityTier> ForceTierOverride;
|
||||||
|
|
||||||
|
/** Count of surfaces at each tier (tracked for budget enforcement). */
|
||||||
|
TMap<EPlanarCaptureQualityTier, int32> TierAssignmentCounts;
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Internal Methods
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/** Evaluate all registered surfaces and assign quality tiers. */
|
||||||
|
void EvaluateAllSurfaces();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Score a single surface and determine its quality tier within budget constraints.
|
||||||
|
* @return The assigned tier.
|
||||||
|
*/
|
||||||
|
EPlanarCaptureQualityTier ScoreAndAssignTier(UBPC_PlanarCapture* Capture);
|
||||||
|
|
||||||
|
/** Enforce budget limits — demote lower-priority surfaces if budget exceeded. */
|
||||||
|
void EnforceBudgetLimits();
|
||||||
|
|
||||||
|
/** Create a new render target of the given size. */
|
||||||
|
UTextureRenderTarget2D* CreateRenderTarget(int32 Size);
|
||||||
|
|
||||||
|
/** Get or create a render target for a given size (first checks pool). */
|
||||||
|
UTextureRenderTarget2D* GetOrCreateRenderTarget(int32 Size);
|
||||||
|
};
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
* bypass them entirely for performance and correctness.
|
* bypass them entirely for performance and correctness.
|
||||||
*/
|
*/
|
||||||
UCLASS(BlueprintType, Blueprintable, meta = (DisplayName = "DA_GameTagRegistry"))
|
UCLASS(BlueprintType, Blueprintable, meta = (DisplayName = "DA_GameTagRegistry"))
|
||||||
class FRAMEWORK_API UDA_GameTagRegistry : public UPrimaryDataAsset
|
class PG_FRAMEWORK_API UDA_GameTagRegistry : public UPrimaryDataAsset
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ class APC_CoreController;
|
|||||||
* here we get clean UFUNCTION(BlueprintCallable) with fast native paths.
|
* here we get clean UFUNCTION(BlueprintCallable) with fast native paths.
|
||||||
*/
|
*/
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class FRAMEWORK_API UFL_GameUtilities : public UBlueprintFunctionLibrary
|
class PG_FRAMEWORK_API UFL_GameUtilities : public UBlueprintFunctionLibrary
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -68,8 +68,8 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFrameworkInitFailed, const FStrin
|
|||||||
* In C++, this replaces the Blueprint "Get Game Instance → Cast → Get Subsystem"
|
* In C++, this replaces the Blueprint "Get Game Instance → Cast → Get Subsystem"
|
||||||
* pattern with clean template access via GetService<T>().
|
* pattern with clean template access via GetService<T>().
|
||||||
*/
|
*/
|
||||||
UCLASS()
|
UCLASS(Blueprintable)
|
||||||
class FRAMEWORK_API UGI_GameFramework : public UGameInstance
|
class PG_FRAMEWORK_API UGI_GameFramework : public UGameInstance
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -14,8 +14,6 @@
|
|||||||
// Forward declarations
|
// Forward declarations
|
||||||
class UGI_GameFramework;
|
class UGI_GameFramework;
|
||||||
class AGS_CoreGameState;
|
class AGS_CoreGameState;
|
||||||
class APC_CoreController;
|
|
||||||
class APS_CorePlayerState;
|
|
||||||
|
|
||||||
// Delegates
|
// Delegates
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnChapterTransition, FGameplayTag, NewChapter);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnChapterTransition, FGameplayTag, NewChapter);
|
||||||
@@ -29,28 +27,19 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGameOverTriggered, FGameplayTag,
|
|||||||
* death routing, and coordinates with the narrative system for story progression.
|
* death routing, and coordinates with the narrative system for story progression.
|
||||||
*
|
*
|
||||||
* Server-authoritative. Extends AGameModeBase for replication support.
|
* Server-authoritative. Extends AGameModeBase for replication support.
|
||||||
|
*
|
||||||
|
* Note: PlayerControllerClass, PlayerStateClass, and GameStateClass are
|
||||||
|
* inherited from AGameModeBase — do not redeclare (UHT forbids shadowing).
|
||||||
|
* Set them in your BP child's Class Defaults.
|
||||||
*/
|
*/
|
||||||
UCLASS()
|
UCLASS(Blueprintable)
|
||||||
class FRAMEWORK_API AGM_CoreGameMode : public AGameModeBase
|
class PG_FRAMEWORK_API AGM_CoreGameMode : public AGameModeBase
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AGM_CoreGameMode();
|
AGM_CoreGameMode();
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// Default Classes (Set in Blueprint or Config)
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Framework|Classes")
|
|
||||||
TSubclassOf<APC_CoreController> PlayerControllerClass;
|
|
||||||
|
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Framework|Classes")
|
|
||||||
TSubclassOf<APS_CorePlayerState> PlayerStateClass;
|
|
||||||
|
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Framework|Classes")
|
|
||||||
TSubclassOf<AGS_CoreGameState> GameStateClass;
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// Chapter Management
|
// Chapter Management
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
@@ -29,8 +29,8 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayTimeUpdated, float, ElapsedSe
|
|||||||
* OnRep_ handlers that mirror broadcast dispatchers for both network
|
* OnRep_ handlers that mirror broadcast dispatchers for both network
|
||||||
* clients and local single-player.
|
* clients and local single-player.
|
||||||
*/
|
*/
|
||||||
UCLASS()
|
UCLASS(Blueprintable)
|
||||||
class FRAMEWORK_API AGS_CoreGameState : public AGameStateBase
|
class PG_FRAMEWORK_API AGS_CoreGameState : public AGameStateBase
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -20,13 +20,13 @@ class UDA_InteractionData;
|
|||||||
// I_Interactable — World objects the player can interact with
|
// I_Interactable — World objects the player can interact with
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
UINTERFACE(MinimalAPI, BlueprintType, meta = (CannotImplementInterfaceInBlueprint))
|
UINTERFACE(MinimalAPI, BlueprintType)
|
||||||
class UInteractable : public UInterface
|
class UInteractable : public UInterface
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
};
|
};
|
||||||
|
|
||||||
class FRAMEWORK_API IInteractable
|
class PG_FRAMEWORK_API IInteractable
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ class UInspectable : public UInterface
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
};
|
};
|
||||||
|
|
||||||
class FRAMEWORK_API IInspectable
|
class PG_FRAMEWORK_API IInspectable
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ class UDamageable : public UInterface
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
};
|
};
|
||||||
|
|
||||||
class FRAMEWORK_API IDamageable
|
class PG_FRAMEWORK_API IDamageable
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ class UHoldable : public UInterface
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
};
|
};
|
||||||
|
|
||||||
class FRAMEWORK_API IHoldable
|
class PG_FRAMEWORK_API IHoldable
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ class ULockable : public UInterface
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
};
|
};
|
||||||
|
|
||||||
class FRAMEWORK_API ILockable
|
class PG_FRAMEWORK_API ILockable
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ class UUsableItem : public UInterface
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
};
|
};
|
||||||
|
|
||||||
class FRAMEWORK_API IUsableItem
|
class PG_FRAMEWORK_API IUsableItem
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ class UPersistable : public UInterface
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
};
|
};
|
||||||
|
|
||||||
class FRAMEWORK_API IPersistable
|
class PG_FRAMEWORK_API IPersistable
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ class UToggleable : public UInterface
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
};
|
};
|
||||||
|
|
||||||
class FRAMEWORK_API IToggleable
|
class PG_FRAMEWORK_API IToggleable
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ class UAdjustable : public UInterface
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
};
|
};
|
||||||
|
|
||||||
class FRAMEWORK_API IAdjustable
|
class PG_FRAMEWORK_API IAdjustable
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ enum class EInputContextPriority : uint8
|
|||||||
* Mapping context entry in the stack.
|
* Mapping context entry in the stack.
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct FRAMEWORK_API FInputContextEntry
|
struct PG_FRAMEWORK_API FInputContextEntry
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnKeyRebound, FGameplayTag, Action
|
|||||||
* "Get Enhanced Input Local Player Subsystem" node chains.
|
* "Get Enhanced Input Local Player Subsystem" node chains.
|
||||||
*/
|
*/
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class FRAMEWORK_API USS_EnhancedInputManager : public UGameInstanceSubsystem
|
class PG_FRAMEWORK_API USS_EnhancedInputManager : public UGameInstanceSubsystem
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ class UDA_ItemData;
|
|||||||
* Single inventory slot entry.
|
* Single inventory slot entry.
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct FRAMEWORK_API FInventorySlot
|
struct PG_FRAMEWORK_API FInventorySlot
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -63,8 +63,8 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnWeightChanged, float, CurrentWei
|
|||||||
* C++ TArray operations (FindByPredicate, Sort, Filter) are natively compiled —
|
* C++ TArray operations (FindByPredicate, Sort, Filter) are natively compiled —
|
||||||
* no BP interpretive array node overhead.
|
* no BP interpretive array node overhead.
|
||||||
*/
|
*/
|
||||||
UCLASS(ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
|
UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
|
||||||
class FRAMEWORK_API UBPC_InventorySystem : public UActorComponent
|
class PG_FRAMEWORK_API UBPC_InventorySystem : public UActorComponent
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
37
Source/PG_Framework/Public/Inventory/DA_EquipmentConfig.h
Normal file
37
Source/PG_Framework/Public/Inventory/DA_EquipmentConfig.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Engine/DataAsset.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "DA_EquipmentConfig.generated.h"
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct PG_FRAMEWORK_API FDamageTypeResistance
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
FGameplayTag DamageType;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
float Resistance = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
UCLASS(BlueprintType)
|
||||||
|
class PG_FRAMEWORK_API UDA_EquipmentConfig : public UPrimaryDataAsset
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Config")
|
||||||
|
TArray<FDamageTypeResistance> DamageTypeResistances;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Config")
|
||||||
|
float Durability = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Config")
|
||||||
|
float Weight = 1.0f;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Config")
|
||||||
|
float GetResistance(FGameplayTag DamageType) const;
|
||||||
|
};
|
||||||
@@ -49,7 +49,7 @@ enum class EEquipmentSlot : uint8
|
|||||||
* Equipment-specific data (shown when ItemType is Weapon or Tool).
|
* Equipment-specific data (shown when ItemType is Weapon or Tool).
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct FRAMEWORK_API FItemEquipmentData
|
struct PG_FRAMEWORK_API FItemEquipmentData
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ struct FRAMEWORK_API FItemEquipmentData
|
|||||||
* Consumable-specific data (shown when ItemType is Consumable).
|
* Consumable-specific data (shown when ItemType is Consumable).
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct FRAMEWORK_API FItemConsumableData
|
struct PG_FRAMEWORK_API FItemConsumableData
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ struct FRAMEWORK_API FItemConsumableData
|
|||||||
* Inspect-specific data (shown when bHasInspectMode is true).
|
* Inspect-specific data (shown when bHasInspectMode is true).
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct FRAMEWORK_API FItemInspectData
|
struct PG_FRAMEWORK_API FItemInspectData
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ struct FRAMEWORK_API FItemInspectData
|
|||||||
* shows relevant sub-structs based on ItemType — a massive UX win for designers.
|
* shows relevant sub-structs based on ItemType — a massive UX win for designers.
|
||||||
*/
|
*/
|
||||||
UCLASS(BlueprintType, Blueprintable, meta = (DisplayName = "Item Data"))
|
UCLASS(BlueprintType, Blueprintable, meta = (DisplayName = "Item Data"))
|
||||||
class FRAMEWORK_API UDA_ItemData : public UPrimaryDataAsset
|
class PG_FRAMEWORK_API UDA_ItemData : public UPrimaryDataAsset
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
29
Source/PG_Framework/Public/Player/BPC_HealthSystem.h
Normal file
29
Source/PG_Framework/Public/Player/BPC_HealthSystem.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "BPC_HealthSystem.generated.h"
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float, CurrentHealth, float, MaxHealth);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
|
||||||
|
|
||||||
|
UCLASS(Blueprintable, ClassGroup=(Framework), meta=(BlueprintSpawnableComponent))
|
||||||
|
class PG_FRAMEWORK_API UBPC_HealthSystem : public UActorComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UBPC_HealthSystem();
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Health")
|
||||||
|
float MaxHealth = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Framework|Health")
|
||||||
|
float CurrentHealth = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
|
||||||
|
FOnHealthChanged OnHealthChanged;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
|
||||||
|
FOnDeath OnDeath;
|
||||||
|
};
|
||||||
23
Source/PG_Framework/Public/Player/BPC_MovementStateSystem.h
Normal file
23
Source/PG_Framework/Public/Player/BPC_MovementStateSystem.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "BPC_MovementStateSystem.generated.h"
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnMovementModeChanged, FGameplayTag, NewMode, FGameplayTag, OldMode);
|
||||||
|
|
||||||
|
UCLASS(Blueprintable, ClassGroup=(Framework), meta=(BlueprintSpawnableComponent))
|
||||||
|
class PG_FRAMEWORK_API UBPC_MovementStateSystem : public UActorComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UBPC_MovementStateSystem();
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Framework|Movement")
|
||||||
|
FGameplayTag CurrentMovementMode;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
|
||||||
|
FOnMovementModeChanged OnMovementModeChanged;
|
||||||
|
};
|
||||||
25
Source/PG_Framework/Public/Player/BPC_StaminaSystem.h
Normal file
25
Source/PG_Framework/Public/Player/BPC_StaminaSystem.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "BPC_StaminaSystem.generated.h"
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnExhaustionStateChanged, bool, bExhausted);
|
||||||
|
|
||||||
|
UCLASS(Blueprintable, ClassGroup=(Framework), meta=(BlueprintSpawnableComponent))
|
||||||
|
class PG_FRAMEWORK_API UBPC_StaminaSystem : public UActorComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UBPC_StaminaSystem();
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Stamina")
|
||||||
|
float MaxStamina = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Framework|Stamina")
|
||||||
|
float CurrentStamina = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
|
||||||
|
FOnExhaustionStateChanged OnExhaustionStateChanged;
|
||||||
|
};
|
||||||
@@ -65,8 +65,8 @@ enum class EHeartRateTier : uint8
|
|||||||
* directly. Gating rules are defined in DA_StateGatingTable (37 rules).
|
* directly. Gating rules are defined in DA_StateGatingTable (37 rules).
|
||||||
* In C++, the Chooser Table iteration is native-speed — no BP interpretive overhead.
|
* In C++, the Chooser Table iteration is native-speed — no BP interpretive overhead.
|
||||||
*/
|
*/
|
||||||
UCLASS(ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
|
UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
|
||||||
class FRAMEWORK_API UBPC_StateManager : public UActorComponent
|
class PG_FRAMEWORK_API UBPC_StateManager : public UActorComponent
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
32
Source/PG_Framework/Public/Player/BPC_StressSystem.h
Normal file
32
Source/PG_Framework/Public/Player/BPC_StressSystem.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "BPC_StressSystem.generated.h"
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EStressTier : uint8
|
||||||
|
{
|
||||||
|
Calm UMETA(DisplayName = "Calm"),
|
||||||
|
Tense UMETA(DisplayName = "Tense"),
|
||||||
|
Distressed UMETA(DisplayName = "Distressed"),
|
||||||
|
Panic UMETA(DisplayName = "Panic"),
|
||||||
|
Catatonic UMETA(DisplayName = "Catatonic"),
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStressTierChanged, EStressTier, NewTier);
|
||||||
|
|
||||||
|
UCLASS(Blueprintable, ClassGroup=(Framework), meta=(BlueprintSpawnableComponent))
|
||||||
|
class PG_FRAMEWORK_API UBPC_StressSystem : public UActorComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UBPC_StressSystem();
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Framework|Stress")
|
||||||
|
EStressTier StressTier = EStressTier::Calm;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Framework|Events")
|
||||||
|
FOnStressTierChanged OnStressTierChanged;
|
||||||
|
};
|
||||||
14
Source/PG_Framework/Public/Player/PC_CoreController.h
Normal file
14
Source/PG_Framework/Public/Player/PC_CoreController.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
|
#include "PC_CoreController.generated.h"
|
||||||
|
|
||||||
|
UCLASS(Blueprintable)
|
||||||
|
class PG_FRAMEWORK_API APC_CoreController : public APlayerController
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
APC_CoreController();
|
||||||
|
};
|
||||||
14
Source/PG_Framework/Public/Player/PS_CorePlayerState.h
Normal file
14
Source/PG_Framework/Public/Player/PS_CorePlayerState.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/PlayerState.h"
|
||||||
|
#include "PS_CorePlayerState.generated.h"
|
||||||
|
|
||||||
|
UCLASS(Blueprintable)
|
||||||
|
class PG_FRAMEWORK_API APS_CorePlayerState : public APlayerState
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
APS_CorePlayerState();
|
||||||
|
};
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
* Save slot metadata returned by GetSlotManifest().
|
* Save slot metadata returned by GetSlotManifest().
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct FRAMEWORK_API FSaveSlotInfo
|
struct PG_FRAMEWORK_API FSaveSlotInfo
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSaveManifestUpdated, const TArray
|
|||||||
* and async save operations — impossible to match in Blueprint.
|
* and async save operations — impossible to match in Blueprint.
|
||||||
*/
|
*/
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class FRAMEWORK_API USS_SaveManager : public UGameInstanceSubsystem
|
class PG_FRAMEWORK_API USS_SaveManager : public UGameInstanceSubsystem
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
34
Source/PG_Framework/Public/State/DA_StateGatingTable.h
Normal file
34
Source/PG_Framework/Public/State/DA_StateGatingTable.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Engine/DataAsset.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "DA_StateGatingTable.generated.h"
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct PG_FRAMEWORK_API FStateGatingRule
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
FGameplayTag ActionTag;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
FGameplayTag BlockedByState;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
bool bIsBlocked = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
UCLASS(BlueprintType)
|
||||||
|
class PG_FRAMEWORK_API UDA_StateGatingTable : public UPrimaryDataAsset
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gating")
|
||||||
|
TArray<FStateGatingRule> GatingRules;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Gating")
|
||||||
|
bool IsActionGated(FGameplayTag ActionTag, FGameplayTag CurrentState) const;
|
||||||
|
};
|
||||||
@@ -28,7 +28,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnKnockedDown, AActor*, KnockdownC
|
|||||||
* Damage modifier for a specific damage type.
|
* Damage modifier for a specific damage type.
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct FRAMEWORK_API FDamageModifier
|
struct PG_FRAMEWORK_API FDamageModifier
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -56,8 +56,8 @@ struct FRAMEWORK_API FDamageModifier
|
|||||||
* triggers hit reactions (stagger, knockdown), and routes final damage to the
|
* triggers hit reactions (stagger, knockdown), and routes final damage to the
|
||||||
* health system. In C++, the damage pipeline is native-speed vectorized math.
|
* health system. In C++, the damage pipeline is native-speed vectorized math.
|
||||||
*/
|
*/
|
||||||
UCLASS(ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
|
UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
|
||||||
class FRAMEWORK_API UBPC_DamageReceptionSystem : public UActorComponent
|
class PG_FRAMEWORK_API UBPC_DamageReceptionSystem : public UActorComponent
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
24
Source/PG_Framework/Public/Weapons/BPC_HitReactionSystem.h
Normal file
24
Source/PG_Framework/Public/Weapons/BPC_HitReactionSystem.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "BPC_HitReactionSystem.generated.h"
|
||||||
|
|
||||||
|
UCLASS(Blueprintable, ClassGroup=(Framework), meta=(BlueprintSpawnableComponent))
|
||||||
|
class PG_FRAMEWORK_API UBPC_HitReactionSystem : public UActorComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UBPC_HitReactionSystem();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Framework|Combat")
|
||||||
|
void PlayHitReaction(float DamageAmount, FVector HitDirection, AActor* DamageCauser);
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|HitReaction")
|
||||||
|
float FlinchThreshold = 5.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|HitReaction")
|
||||||
|
float RagdollThreshold = 50.0f;
|
||||||
|
};
|
||||||
26
Source/PG_Framework/Public/Weapons/BPC_ShieldDefenseSystem.h
Normal file
26
Source/PG_Framework/Public/Weapons/BPC_ShieldDefenseSystem.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "BPC_ShieldDefenseSystem.generated.h"
|
||||||
|
|
||||||
|
UCLASS(Blueprintable, ClassGroup=(Framework), meta=(BlueprintSpawnableComponent))
|
||||||
|
class PG_FRAMEWORK_API UBPC_ShieldDefenseSystem : public UActorComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UBPC_ShieldDefenseSystem();
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Shield")
|
||||||
|
float ShieldHealth = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Shield")
|
||||||
|
float MaxShieldHealth = 100.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Framework|Shield")
|
||||||
|
float BlockAngle = 90.0f;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Framework|Shield")
|
||||||
|
bool bShieldBroken = false;
|
||||||
|
};
|
||||||
15
Source/PG_FrameworkEditor.Target.cs
Normal file
15
Source/PG_FrameworkEditor.Target.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// // Copyright Ngonart OU. All Rights Reserved.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class PG_FrameworkEditorTarget : TargetRules
|
||||||
|
{
|
||||||
|
public PG_FrameworkEditorTarget(TargetInfo Target) : base(Target)
|
||||||
|
{
|
||||||
|
Type = TargetType.Editor;
|
||||||
|
DefaultBuildSettings = BuildSettingsVersion.V6;
|
||||||
|
|
||||||
|
ExtraModuleNames.AddRange( new string[] { "PG_Framework" } );
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
202
docs/architecture/planar-capture-system.md
Normal file
202
docs/architecture/planar-capture-system.md
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
# Planar Capture System Architecture — UE5 Modular Game Framework
|
||||||
|
|
||||||
|
**Version:** 1.0 | **Systems:** 136-147 (12 systems) | **Phase:** 17 — Rendering & Visual
|
||||||
|
|
||||||
|
This document specifies the architecture of the Planar Capture System — a unified rendering pipeline for mirrors, portals, monitors, and horror surfaces. One capture pipeline, one quality manager, one material interface.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ BLUEPRINT LAYER (Designer-Facing) │
|
||||||
|
│ BP_Mirror BP_HorrorMirror BP_Portal BP_Monitor BP_FakeWindow │
|
||||||
|
│ DA_PlanarCaptureProfile MPC_CaptureSurface MI_Mirror_Clean/Dirty/Steam │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ C++ LAYER (Performance-Critical) │
|
||||||
|
│ UBPC_PlanarCapture — SceneCapture2D lifecycle, camera math │
|
||||||
|
│ ABP_PlanarCaptureActor — Surface mesh, MDI, overlap/proximity │
|
||||||
|
│ USS_PlanarCaptureManager — Global budget, RT pool, scoring (WorldSubsystem + FTickable) │
|
||||||
|
│ UPlanarCaptureCameraUtils — Mirror/Portal/Oblique math (static) │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ UE5 ENGINE │
|
||||||
|
│ USceneCaptureComponent2D UTextureRenderTarget2D UMaterialParameterColl │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## System Map (12 Systems — Files 136-147)
|
||||||
|
|
||||||
|
| # | System | Type | Parent Class | Purpose |
|
||||||
|
|---|--------|------|-------------|---------|
|
||||||
|
| 136 | `BPC_PlanarCapture` | BPC_ Component | `ActorComponent` | Core capture: SceneCapture2D lifecycle, camera math, RT management, actor lists, horror ring buffer |
|
||||||
|
| 137 | `BP_PlanarCaptureActor` | BP_ Actor | `Actor` | Placeable surface actor: mesh, MDI, proximity trigger, manager registration |
|
||||||
|
| 138 | `SS_PlanarCaptureManager` | SS_ Subsystem | `WorldSubsystem` | Global budget manager: surface scoring, quality tier assignment, RT pool |
|
||||||
|
| 139 | `BP_Mirror` | BP_ Actor | `BP_PlanarCaptureActor` | Standard mirror: Mode=Mirror, dirt/steam/condensation layers, aging |
|
||||||
|
| 140 | `BP_Portal` | BP_ Actor | `BP_PlanarCaptureActor` | Portal surface: Mode=Portal, linked target, teleport on overlap, clip plane |
|
||||||
|
| 141 | `BP_Monitor` | BP_ Actor | `BP_PlanarCaptureActor` | Security screen/TV: Mode=Monitor, fixed camera ref, low update rate, scanlines |
|
||||||
|
| 142 | `BP_HorrorMirror` | BP_ Actor | `BP_Mirror` | Horror mirror: wrong reflection actor, delayed frame, steam text reveal, scare events |
|
||||||
|
| 143 | `BP_FakeWindow` | BP_ Actor | `BP_PlanarCaptureActor` | Architectural fake window: Mode=FakeWindow, linked sublevel, weather overlay |
|
||||||
|
| 144 | `M_CaptureSurface_Master` | Material | `Material` | Master unlit material: RT sample, condensation, dirt, steam, horror layers |
|
||||||
|
| 145 | `MPC_CaptureSurface` | MPC | `MaterialParameterCollection` | Global MPC: SteamIntensity, DirtOpacity, MirrorDarkness, WrongReflectionBlend, etc. (10 params) |
|
||||||
|
| 146 | `DA_PlanarCaptureProfile` | DA_ Data Asset | `PrimaryDataAsset` | Per-surface capture config: default mode, quality profile overrides, actor lists |
|
||||||
|
| 147 | `MI_Mirror_Clean/Dirty/Steam/Horror` | MI_ Instances | `M_CaptureSurface_Master` | Pre-configured material instances for common mirror states |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Enums
|
||||||
|
|
||||||
|
### `EPlanarCaptureMode` (C++: defined in `PlanarCaptureCommon.h`)
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `Mirror` | Standard planar mirror reflection |
|
||||||
|
| `Portal` | Portal with linked target surface |
|
||||||
|
| `Monitor` | Fixed-camera security screen / TV |
|
||||||
|
| `HorrorMirror` | Mirror with horror features (wrong reflection, delayed frame) |
|
||||||
|
| `HorrorPortal` | Portal with horror features |
|
||||||
|
| `FakeWindow` | Architectural fake window (parallax, weather) |
|
||||||
|
|
||||||
|
### `EPlanarCaptureQualityTier` (C++: defined in `PlanarCaptureCommon.h`)
|
||||||
|
| Value | RT Size | FPS | Shadows | Lumen | Post | Clip Plane |
|
||||||
|
|-------|---------|-----|---------|-------|------|-----------|
|
||||||
|
| `Off` | — | 0 | — | — | — | — |
|
||||||
|
| `Low` | 256 | 4 | No | No | No | No |
|
||||||
|
| `Medium` | 512 | 15 | Dynamic | Optional | No | Yes |
|
||||||
|
| `High` | 1024 | 30 | Full | Yes | Minimal | Yes |
|
||||||
|
| `Hero` | 2048 | 60 | Full | Yes | Full | Yes |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Communication Matrix
|
||||||
|
|
||||||
|
| Source | Target | Method | What |
|
||||||
|
|--------|--------|--------|------|
|
||||||
|
| `BP_PlanarCaptureActor` | `SS_PlanarCaptureManager` | Direct (RegisterSurface) | Registers on BeginPlay |
|
||||||
|
| `SS_PlanarCaptureManager` | `BPC_PlanarCapture` | Direct (ApplyQualityTier) | Assigns quality tier |
|
||||||
|
| `BPC_PlanarCapture` | `USceneCaptureComponent2D` | Direct (owns) | Camera math + CaptureScene |
|
||||||
|
| `BPC_PlanarCapture` | `MPC_CaptureSurface` | Direct (SetScalarParameter) | Pushes steam/dirt/horror params |
|
||||||
|
| `BP_HorrorMirror` | `BPC_ScareEventSystem` (101) | Interface / Dispatcher | Triggers coordinated scares |
|
||||||
|
| `BP_HorrorMirror` | `SS_AudioManager` (132) | Direct | Plays mirror horror SFX |
|
||||||
|
| `BPC_PlanarCapture` | `BPC_StateManager` (130) | IsActionPermitted() | Checks if capture can activate |
|
||||||
|
| `BP_Portal` | Player Pawn | Overlap Event | Teleports player through portal |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quality Scoring Algorithm
|
||||||
|
|
||||||
|
```
|
||||||
|
CompositeScore = (ScreenCoverage × 0.5) + (FacingAngle × 0.3) + (DistanceFactor × 0.1) + (ScriptedPriority × 0.1)
|
||||||
|
|
||||||
|
Tier assignment:
|
||||||
|
Score ≥ 0.8 → Hero (capped at GlobalQualityCap, max MaxHeroSurfaces)
|
||||||
|
Score ≥ 0.5 → High (capped at max MaxHighSurfaces)
|
||||||
|
Score ≥ 0.2 → Medium (capped at max MaxMediumSurfaces)
|
||||||
|
Score > 0 → Low
|
||||||
|
Score = 0 or !inFrustum → Off
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Material Layer Stack (M_CaptureSurface_Master)
|
||||||
|
|
||||||
|
```
|
||||||
|
Layer 0: Render target sample (UV-flipped for mirror, straight for portal/monitor)
|
||||||
|
Layer 1: Condensation normal map → distorts UV of RT sample
|
||||||
|
Layer 2: Fresnel edge fade mask
|
||||||
|
Layer 3: Dirt/scratch multiply (DirtOpacity MPC param)
|
||||||
|
Layer 4: Steam/fog lerp (animated noise, SteamIntensity MPC param)
|
||||||
|
Layer 5: Steam emissive glow (backlit fog, SteamEmissiveIntensity)
|
||||||
|
Layer 6: Text reveal mask lerp (TextRevealProgress, uses steam as carrier)
|
||||||
|
Layer 7: Mirror darkness multiply (MirrorDarkness MPC param)
|
||||||
|
Layer 8: Wrong reflection crossfade (WrongReflectionBlend MPC param)
|
||||||
|
Output: Unlit shading model
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Horror Features
|
||||||
|
|
||||||
|
| Feature | Driven By | Layer |
|
||||||
|
|---------|-----------|-------|
|
||||||
|
| Mirror-only ghost actor | ShowOnly list in C++ | C++ |
|
||||||
|
| Wrong reflection Metahuman swap | ActivateHorrorReflection() | C++ |
|
||||||
|
| Delayed frame reflection | FrameRingBuffer + DelayedReflectionBlend MPC | C++ + Material |
|
||||||
|
| Steam fogging | SteamIntensity MPC → Layer 4 | Material |
|
||||||
|
| Dirt / scratches | DirtOpacity MPC → Layer 3 | Material |
|
||||||
|
| Condensation rivulets | CondensationFlow MPC → Layer 1 | Material |
|
||||||
|
| UV distortion / breathing | DistortionAmplitude MPC → Layer 1 | Material |
|
||||||
|
| Text in steam reveal | TextRevealProgress MPC → Layer 6 | Material |
|
||||||
|
| Mirror goes dark | MirrorDarkness MPC → Layer 7 | Material |
|
||||||
|
| Wrong reflection crossfade | WrongReflectionBlend MPC → Layer 8 | Material |
|
||||||
|
| Surface aging/oxidation | SurfaceAge MPC → Layer 3 (tint) | Material |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Blueprint Limitations & Workarounds
|
||||||
|
|
||||||
|
### `USceneCaptureComponent2D::CaptureScene()` (C++ Only)
|
||||||
|
**Workaround:** Call from C++ in `UBPC_PlanarCapture`. Blueprint calls `CaptureNow()` which invokes C++ capture.
|
||||||
|
**Affected Files:** 136, 139-143
|
||||||
|
|
||||||
|
### `FSceneView::ViewMatrices` for Oblique Projection (C++ Only)
|
||||||
|
**Workaround:** Use `UPlanarCaptureCameraUtils::ComputeObliqueProjectionMatrix()` (BlueprintCallable).
|
||||||
|
**Affected Files:** 136, 140
|
||||||
|
|
||||||
|
### `USceneCaptureComponent2D::ShowOnlyActors` Runtime Modification (Partial C++)
|
||||||
|
**Workaround:** `UBPC_PlanarCapture::UpdateActorLists()` resolves TSoftObjectPtrs into the array.
|
||||||
|
**Affected Files:** 136, 142
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Budget Guidelines
|
||||||
|
|
||||||
|
| Tier | Max Simultaneous | RT Memory per Surface | GPU Cost |
|
||||||
|
|------|-----------------|----------------------|----------|
|
||||||
|
| Hero | 1 | 16 MB (2048² × 4B) | Very High |
|
||||||
|
| High | 3 | 4 MB (1024² × 4B) | High |
|
||||||
|
| Medium | 6 | 1 MB (512² × 4B) | Medium |
|
||||||
|
| Low | Unlimited | 256 KB (256² × 4B) | Low |
|
||||||
|
| Off | — | 0 | None |
|
||||||
|
|
||||||
|
**Total Budget:** 128 MB default (configurable via `SS_PlanarCaptureManager.MaxTotalRenderTargetMemoryMB`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Multiplayer Considerations
|
||||||
|
|
||||||
|
- Capture rendering is **local-only** — each client renders their own view
|
||||||
|
- `BP_PlanarCaptureActor` replicates `bIsActive` for server-authoritative surface control
|
||||||
|
- Horror events (wrong reflection activation) are triggered server-side and replicated via gameplay tags
|
||||||
|
- No client prediction needed — captures are cosmetic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration Points with Existing Systems
|
||||||
|
|
||||||
|
| Existing System | Integration |
|
||||||
|
|----------------|-------------|
|
||||||
|
| `BPC_StateManager` (130) | Query `IsActionPermitted()` before enabling capture, teleport gating |
|
||||||
|
| `BPC_ScareEventSystem` (101) | Horror mirror events trigger coordinated scares |
|
||||||
|
| `SS_AudioManager` (132) | Mirror/portal SFX route through audio subsystem |
|
||||||
|
| `SS_EnhancedInputManager` (128) | Input context switch for portal inspection mode |
|
||||||
|
| `BPC_DiegeticDisplay` (18) | Monitors can display diegetic UI render targets |
|
||||||
|
| `BPC_CameraStateLayer` (14) | Camera FOV adjustments during portal transitions |
|
||||||
|
| `I_Persistable` (36) | Mirror surface state (destroyed, dirty, oxidized) saves/loads |
|
||||||
|
| `DA_ScareEvent` (127) | Mirror apparition scare event data asset |
|
||||||
|
| `WBP_SettingsMenu` (57) | Capture surface quality settings (Performance section) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build Order (Phase 17)
|
||||||
|
|
||||||
|
1. **Phase 17a:** C++ Core — `PlanarCaptureCommon.h`, `PlanarCaptureCameraUtils`, `BPC_PlanarCapture`, `SS_PlanarCaptureManager`, `BP_PlanarCaptureActor`
|
||||||
|
2. **Phase 17b:** Material Foundation — `M_CaptureSurface_Master`, `MPC_CaptureSurface`, `MI_Mirror_*` instances
|
||||||
|
3. **Phase 17c:** Blueprint Actors — `BP_Mirror`, `BP_Portal`, `BP_Monitor`, `BP_HorrorMirror`, `BP_FakeWindow`
|
||||||
|
4. **Phase 17d:** Data Assets — `DA_PlanarCaptureProfile`
|
||||||
|
5. **Phase 17e:** Integration — Wire horror events to `BPC_ScareEventSystem`, audio to `SS_AudioManager`, persistence to `I_Persistable`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Planar Capture System Architecture v1.0 — See `docs/blueprints/17-capture/` for per-system blueprint specs.*
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# Audit Request: Cross-Reference Master Document vs Existing Blueprint Spec Files
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
Systematically map every system defined in `UE5_Modular_Game_Framework.md` (3703 lines, ~131 systems across 13 sections) against the 83 existing blueprint spec files in `docs/blueprints/` to identify:
|
|
||||||
1. Which systems have corresponding blueprint spec files (Present)
|
|
||||||
2. Which systems are missing (Missing)
|
|
||||||
3. Which existing files have no clear corresponding master-document system (Orphaned / Renamed)
|
|
||||||
|
|
||||||
## Input Files
|
|
||||||
|
|
||||||
### Master Document
|
|
||||||
- `UE5_Modular_Game_Framework.md` (in workspace root)
|
|
||||||
|
|
||||||
### Existing Files (83 total)
|
|
||||||
Located under `docs/blueprints/` with the following structure:
|
|
||||||
```
|
|
||||||
docs/blueprints/
|
|
||||||
TEMPLATE.md
|
|
||||||
01-core/ (files 01-07)
|
|
||||||
02-player/ (files 08-15)
|
|
||||||
03-interaction/ (files 16-21)
|
|
||||||
04-inventory/ (files 22-27)
|
|
||||||
05-saveload/ (files 28-31)
|
|
||||||
06-ui/ (files 32-37)
|
|
||||||
07-narrative/ (files 38-48)
|
|
||||||
08-weapons/ (files 49-54)
|
|
||||||
09-ai/ (files 55-61)
|
|
||||||
10-adaptive/ (files 62-70)
|
|
||||||
11-polish/ (files 71-83)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Master Document Section Structure
|
|
||||||
|
|
||||||
| Section | Title | Table Count | Lines |
|
|
||||||
|---------|-------|-------------|-------|
|
|
||||||
| 1 | Core Framework Systems | 8 | 154-537 |
|
|
||||||
| 2 | Player State & Embodiment | 9 | 540-939 |
|
|
||||||
| 3 | Interaction & World Manipulation | 11 | 941-1305 |
|
|
||||||
| 4 | Inventory, Items & Collectibles | 12 | 1307-1643 |
|
|
||||||
| 5 | Weapon, Equipment & Damage | 10 | 1645-1892 |
|
|
||||||
| 6 | UI, Menus & Diegetic Presentation | 13 | 1894-2143 |
|
|
||||||
| 7 | Narrative, Dialogue, Objective & Choice | 11 | 2146-2467 |
|
|
||||||
| 8 | Save, Load, Persistence & Death Loop | 11 | 2469-2691 |
|
|
||||||
| 9 | Adaptive Environment, Atmosphere & Scare | 9 | 2693-2935 |
|
|
||||||
| 10 | AI, Perception & Encounters | 9 | 2937-3109 |
|
|
||||||
| 11 | Achievements, Progression & Meta | 10 | 3112-3286 |
|
|
||||||
| 12 | Settings, Accessibility & Platform | 9 | 3288-3389 |
|
|
||||||
| 13 | Editor / Data / Content Authoring | 9 | 3391-3498 |
|
|
||||||
|
|
||||||
## Existing Files Reference (83 files)
|
|
||||||
|
|
||||||
### 01-core/ (7 files)
|
|
||||||
01_GI_GameTagRegistry.md, 02_FL_GameUtilities.md, 03_I_InterfaceLibrary.md, 04_GI_GameFramework.md, 05_GM_CoreGameMode.md, 06_GS_CoreGameState.md, 07_DA_ItemData.md
|
|
||||||
|
|
||||||
### 02-player/ (8 files)
|
|
||||||
08_BPC_HealthSystem.md, 09_BPC_StaminaSystem.md, 10_BPC_StressSystem.md, 11_BPC_MovementStateSystem.md, 12_BPC_HidingSystem.md, 13_BPC_EmbodimentSystem.md, 14_BPC_CameraStateLayer.md, 15_BPC_PlayerMetricsTracker.md
|
|
||||||
|
|
||||||
### 03-interaction/ (6 files)
|
|
||||||
16_BPC_InteractionDetector.md, 17_BPC_PickupComponent.md, 18_I_HidingSpot.md, 19_BPC_LeverPuzzleComponent.md, 20_BPC_InteractableDoorComponent.md, 21_BPC_DiegeticDisplay.md
|
|
||||||
|
|
||||||
### 04-inventory/ (6 files)
|
|
||||||
22_BPC_InventoryComponent.md, 23_BPC_InventoryWeightSystem.md, 24_BPC_InventoryQuickSlot.md, 25_BPC_EquipmentSystem.md, 26_BPC_ContainerInventory.md, 27_BP_ItemPickup.md
|
|
||||||
|
|
||||||
### 05-saveload/ (4 files)
|
|
||||||
28_SS_SaveManager.md, 29_I_Persistable.md, 30_BPC_CheckpointSystem.md, 31_BPC_DeathHandlingSystem.md
|
|
||||||
|
|
||||||
### 06-ui/ (6 files)
|
|
||||||
32_SS_UIManager.md, 33_WBP_HUD.md, 34_WBP_InventoryUI.md, 35_WBP_InteractionUI.md, 36_WBP_MenuWidgets.md, 37_WBP_AccessibilityUI.md
|
|
||||||
|
|
||||||
### 07-narrative/ (11 files)
|
|
||||||
38_BPC_NarrativeStateSystem.md, 39_BPC_ObjectiveSystem.md, 40_BPC_DialoguePlaybackSystem.md, 41_BPC_DialogueChoiceSystem.md, 42_BPC_BranchingConsequenceSystem.md, 43_BPC_TrialScenarioSystem.md, 44_BPC_CutsceneBridge.md, 45_BPC_EndingAccumulatorSystem.md, 46_BPC_LoreUnlockSystem.md, 47_BPC_NarrativeTriggerVolume.md, 48_DA_NarrativeDataAssets.md
|
|
||||||
|
|
||||||
### 08-weapons/ (6 files)
|
|
||||||
49_BP_WeaponBase.md, 50_BP_RangedWeapon.md, 51_BP_MeleeWeapon.md, 52_BPC_AmmoComponent.md, 53_BPC_DamageHandlerComponent.md, 54_BPC_CombatFeedbackComponent.md
|
|
||||||
|
|
||||||
### 09-ai/ (7 files)
|
|
||||||
55_BPC_AIControllerBase.md, 56_BPC_PerceptionComponent.md, 57_BPC_BehaviorTreeManager.md, 58_BP_EnemyBase.md, 59_BP_PatrolPath.md, 60_BPC_AlertSystem.md, 61_BPC_AIStateMachine.md
|
|
||||||
|
|
||||||
### 10-adaptive/ (9 files)
|
|
||||||
62_BPC_DifficultyManager.md, 63_BPC_FearSystem.md, 64_BPC_AtmosphereController.md, 65_BPC_LightingManager.md, 66_BPC_AudioManager.md, 67_BPC_VFXManager.md, 68_BP_DynamicEvent.md, 69_BPC_PerformanceScaler.md, 70_BPC_ProceduralEncounter.md
|
|
||||||
|
|
||||||
### 11-polish/ (13 files)
|
|
||||||
71_SS_SettingsManager.md, 72_BPC_AccessibilitySettings.md, 73_BPC_TutorialSystem.md, 74_BPC_AchievementManager.md, 75_BPC_StatsTracker.md, 76_BPC_LoadingScreen.md, 77_WBP_CreditsScreen.md, 78_WBP_SplashScreen.md, 79_BPC_FPSCounter.md, 80_BPC_DevCheatManager.md, 81_WBP_DebugMenu.md, 82_BPC_AnalyticsTracker.md, 83_BPC_ErrorHandler.md
|
|
||||||
|
|
||||||
## Methodology
|
|
||||||
|
|
||||||
Read each section of the master document and extract:
|
|
||||||
- Every named system (by its prefix + name, e.g., `BPC_HealthSystem`, `BP_DoorActor`, `SS_SaveSystem`, `WBP_HUDController`)
|
|
||||||
- Cross-reference against the 83 existing files by matching:
|
|
||||||
- Exact name match (e.g., `BPC_HealthSystem` → `08_BPC_HealthSystem.md`)
|
|
||||||
- Semantic match (e.g., `SS_SaveSystem` in master doc → `28_SS_SaveManager.md` on disk)
|
|
||||||
- Bundled match (e.g., `WBP_MainMenu`, `WBP_PauseMenu`, `WBP_SettingsMenu` in master doc → `36_WBP_MenuWidgets.md` on disk)
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
Produce a Markdown table for each section of the master document with columns:
|
|
||||||
| Master Doc System | Corresponding File | Status | Notes |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `BPC_HealthSystem` | `08_BPC_HealthSystem.md` | ExactMatch | Same name |
|
|
||||||
| `SS_SaveSystem` | `28_SS_SaveManager.md` | RenamedMatch | Master calls it SaveSystem, file calls it SaveManager |
|
|
||||||
| `BPC_InteractionExecutor` | (none) | Missing | No corresponding file |
|
|
||||||
|
|
||||||
Also flag files that exist on disk but have no obvious match in the master document (orphaned).
|
|
||||||
|
|
||||||
## Final Summary
|
|
||||||
At the end, provide:
|
|
||||||
1. Count of ExactMatch systems
|
|
||||||
2. Count of RenamedMatch systems
|
|
||||||
3. Count of Missing systems
|
|
||||||
4. Count of Orphaned files
|
|
||||||
5. Total systems in master doc
|
|
||||||
6. Total files on disk
|
|
||||||
7. List of all missing system names organized by their source section
|
|
||||||
@@ -5,10 +5,12 @@
|
|||||||
> **Category:** 01-Core / Foundation
|
> **Category:** 01-Core / Foundation
|
||||||
> **Build Phase:** Phase 0 — Item 1
|
> **Build Phase:** Phase 0 — Item 1
|
||||||
> **Dependencies:** None (this is the first system)
|
> **Dependencies:** None (this is the first system)
|
||||||
|
>
|
||||||
---
|
> ---
|
||||||
|
>
|
||||||
## 1. Purpose
|
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Core/DA_GameTagRegistry.h` provides all tag query, validation, and export functions. The Blueprint spec below documents the API and Data Table configuration. **Do NOT create a Blueprint child** — create a **Data Asset instance** (Right-click → Miscellaneous → Data Asset → pick `DA_GameTagRegistry`). Assign the 11 `TagDataTables`. All functions are `BlueprintCallable` — call them from any BP. See `docs/developer/cpp-integration-guide.md` for exact setup steps.
|
||||||
|
>
|
||||||
|
> ---## 1. Purpose
|
||||||
|
|
||||||
Centralises every `GameplayTag` namespace used across the framework in a single asset. All other systems reference tags from this registry — never raw strings, never `FName` comparisons, never hardcoded booleans for state.
|
Centralises every `GameplayTag` namespace used across the framework in a single asset. All other systems reference tags from this registry — never raw strings, never `FName` comparisons, never hardcoded booleans for state.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# FL_GameUtilities — Blueprint Function Library
|
# FL_GameUtilities — Blueprint Function Library
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Core/FL_GameUtilities.h` provides all utility functions (subsystem access, math, tags, text, screen, debug). All functions are `BlueprintCallable` — they appear under `Framework|Utilities` in every BP right-click menu. **Nothing to build.** The spec below documents each function's API for reference. The old "Blueprint-Only Alternative" section is kept for context but is no longer needed — the C++ class is the canonical implementation. See `docs/developer/cpp-integration-guide.md` for usage patterns.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Asset Details
|
## Asset Details
|
||||||
|
|
||||||
| Property | Value |
|
| Property | Value |
|
||||||
|
|||||||
1482
docs/blueprints/01-core/02a_ML_GameUtilities.md
Normal file
1482
docs/blueprints/01-core/02a_ML_GameUtilities.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,9 @@
|
|||||||
# I_InterfaceLibrary — Interface Collection
|
# I_InterfaceLibrary — Interface Collection
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Full Implementation** — All 9 interfaces (`I_Interactable`, `I_Damageable`, `I_Persistable`, `I_Lockable`, `I_Holdable`, `I_UsableItem`, `I_Toggleable`, `I_Adjustable`, `I_Inspectable`) are defined in C++ at `Source/PG_Framework/Public/Core/I_InterfaceLibrary.h` as `BlueprintNativeEvent`. **Do NOT create Blueprint Interface assets.** On any BP actor: Class Settings → Interfaces → Add → type the interface name (e.g., `UInteractable`). Compile, and the interface events appear in your Event Graph. The spec below documents each interface's functions and purpose for reference. See `docs/developer/cpp-integration-guide.md`.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This document defines all **9 Blueprint Interfaces** that form the backbone of cross-system communication in the framework. Interfaces are the **preferred communication method** over direct references — they decouple the caller from the callee's concrete class. Any Actor or Component can implement any of these interfaces, and any system can query for them and call them without knowing the underlying type.
|
This document defines all **9 Blueprint Interfaces** that form the backbone of cross-system communication in the framework. Interfaces are the **preferred communication method** over direct references — they decouple the caller from the callee's concrete class. Any Actor or Component can implement any of these interfaces, and any system can query for them and call them without knowing the underlying type.
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# 04 — Game Instance Kernel (`GI_GameFramework`)
|
# 04 — Game Instance Kernel (`GI_GameFramework`)
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Core/GI_GameFramework.h` provides the complete application kernel: game phase state machine, session flags, save slot management, service resolution, platform initialization, and all event dispatchers. **Create a BP child** (Blueprint Class → All Classes → `GI_GameFramework`) for configuration only — set `TagRegistry` in Class Defaults. Do NOT rebuild the state machine or service registry in BP. See `docs/developer/cpp-integration-guide.md` for exact setup steps.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
The single persistent object that lives for the entire application session. It owns global subsystems, manages save slot ownership, and provides the canonical entry point for service resolution.
|
The single persistent object that lives for the entire application session. It owns global subsystems, manages save slot ownership, and provides the canonical entry point for service resolution.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# GM_CoreGameMode — Core GameMode
|
# GM_CoreGameMode — Core GameMode
|
||||||
|
|
||||||
---
|
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Core/GM_CoreGameMode.h` provides chapter transitions, death handling, ending triggers, and pause control. **Create a BP child** (Blueprint Class → All Classes → `GM_CoreGameMode`). In Class Defaults → **"Classes"** section (inherited from `AGameModeBase`): set `PlayerControllerClass`, `PlayerStateClass`, `GameStateClass`, and `DefaultPawnClass`. Do NOT redeclare these variables — they're inherited. See `docs/developer/cpp-integration-guide.md` for exact setup steps.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Asset Details
|
## Asset Details
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# 06 — Core GameState (`GS_CoreGameState`)
|
# 06 — Core GameState (`GS_CoreGameState`)
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Core/GS_CoreGameState.h` provides 5 replicated state variables (ElapsedPlayTime, ActiveChapterTag, ActiveNarrativePhase, bEncounterActive, ActiveObjectiveTags) with `OnRep_` handlers and dispatchers. **Create a BP child** (Blueprint Class → All Classes → `GS_CoreGameState`). No logic to add — all replication works out of the box. Set in `BP_CoreGameMode` → Class Defaults → Classes → `Game State Class`. See `docs/developer/cpp-integration-guide.md`.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
The single replicated game state object visible to all players (and potential future co-op clients). Tracks session-level progression data such as elapsed play time, active chapter, narrative phase, encounter activity, and active objectives.
|
The single replicated game state object visible to all players (and potential future co-op clients). Tracks session-level progression data such as elapsed play time, active chapter, narrative phase, encounter activity, and active objectives.
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,261 @@
|
|||||||
## Purpose
|
## Purpose
|
||||||
The single source of truth for every item in the game. Each item is represented by one `DA_ItemData` Primary Data Asset. No item data lives in Blueprint logic; all item definitions are data-driven from these assets.
|
The single source of truth for every item in the game. Each item is represented by one `DA_ItemData` Primary Data Asset. No item data lives in Blueprint logic; all item definitions are data-driven from these assets.
|
||||||
|
|
||||||
|
**C++ Status:** ✅ Full — `Source/PG_Framework/Public/Inventory/DA_ItemData.h` + `.cpp`. All enums, structs, variables, and editor validation are implemented in C++ with `UPROPERTY` metadata (EditCondition, EditConditionHides, ClampMin/Max) for designer-friendly editor UX.
|
||||||
|
|
||||||
|
> **Concrete examples:** See [`docs/game/`](../../game/README.md) for step-by-step walkthroughs of building actual items: [Flashlight Tool](../../game/item-flashlight.md), [Pistol Weapon](../../game/item-pistol.md), [MedKit Consumable](../../game/item-medkit.md), [Keycard Key Item](../../game/item-keycard.md). Each shows exact Blueprint graphs, component lists, and verification checklists.
|
||||||
|
|
||||||
|
> **Critical distinction:** `DA_ItemData` is a **Data Asset** — it lives in the Content Browser, NOT in the level. It defines *what* an item is (name, weight, icon, effects, type). To place an item in the world, create a `BP_ItemPickup` actor (spec #25) and assign the `DA_ItemData` to it. The Data Asset is the item's *identity card*; `BP_ItemPickup` is its *physical body* in the world. See [How to Set Up](#how-to-set-up) below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How to Set Up
|
||||||
|
|
||||||
|
### Step 1 — Create the Data Asset
|
||||||
|
|
||||||
|
```
|
||||||
|
Content Browser → Framework/DataAssets/Items/
|
||||||
|
Right-click → Miscellaneous → Data Asset
|
||||||
|
Class: DA_ItemData
|
||||||
|
Name: DA_Item_[ShortName] (e.g., DA_Item_MedKit, DA_Item_RustyKey, DA_Item_Pistol)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2 — Fill in Core Properties
|
||||||
|
|
||||||
|
Open the Data Asset and fill in:
|
||||||
|
|
||||||
|
| Property | Purpose | Example |
|
||||||
|
|----------|---------|---------|
|
||||||
|
| `Item Tag` | **Unique identifier** — registered in `DA_GameTagRegistry` | `Framework.Item.Consumable.MedKit` |
|
||||||
|
| `Display Name` | Player-visible name | "MedKit" |
|
||||||
|
| `Description` | Flavor text / gameplay hint | "A standard medical kit. Restores 25 health." |
|
||||||
|
| `Icon` | Inventory thumbnail texture | `T_MedKit_Icon` |
|
||||||
|
| `World Mesh` | 3D mesh when dropped in world | `SM_MedKit` |
|
||||||
|
| `Weight` | Weight units for carry limit | `1.0` |
|
||||||
|
| `Stack Limit` | Max per slot (weapons = 1, ammo = 999, consumables = 5) | `5` |
|
||||||
|
| `Item Type` | Category enum — controls which sub-data to fill | `Consumable` |
|
||||||
|
|
||||||
|
### Step 3 — Fill Type-Specific Sub-Data
|
||||||
|
|
||||||
|
Based on `Item Type`, fill the relevant sub-struct (other sub-panels auto-hide via `EditCondition`):
|
||||||
|
|
||||||
|
| Item Type | Sub-Struct to Fill | Example Values |
|
||||||
|
|-----------|-------------------|-----------------|
|
||||||
|
| `Weapon` / `Tool` | `Equipment Data` | Damage=0, FireRate=0, MagazineSize=0, Slot=PrimaryWeapon |
|
||||||
|
| `Consumable` | `Consumable Data` | HealthRestore=25, UseDuration=2.0, bConsumedOnUse=true |
|
||||||
|
| `Ammo` | `AmmoData` (legacy) | AmmoTypeTag, PerPickupCount |
|
||||||
|
| `KeyItem`, `Document`, `Collectible`, `Resource`, `Misc` | None — core properties only | |
|
||||||
|
|
||||||
|
### Step 4 — Build the BP_ItemPickup Blueprint (one-time)
|
||||||
|
|
||||||
|
The Data Asset does **not** appear in the world. You need a Blueprint Actor that references it. Here is the exact setup:
|
||||||
|
|
||||||
|
#### 4.1 — Create the Blueprint
|
||||||
|
|
||||||
|
```
|
||||||
|
Content Browser → Framework/Inventory/
|
||||||
|
Right-click → Blueprint Class → Actor
|
||||||
|
Name: BP_ItemPickup
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2 — Add Components
|
||||||
|
|
||||||
|
Open `BP_ItemPickup` → **Viewport** tab → **Add Component** (top-left button):
|
||||||
|
|
||||||
|
| # | Component Class | Name | Purpose |
|
||||||
|
|---|----------------|------|---------|
|
||||||
|
| 1 | `StaticMeshComponent` | `Mesh` | Renders the item's 3D model in the world |
|
||||||
|
| 2 | `SphereComponent` | `InteractionCollision` | Detects when the player is near enough to pick up |
|
||||||
|
| 3 | *(optional)* `AudioComponent` | `PickupSound` | Plays a sound when collected |
|
||||||
|
|
||||||
|
**Select `InteractionCollision` → Details panel:**
|
||||||
|
- `Sphere Radius` → `150.0` (how close the player must be)
|
||||||
|
- Under **Collision Presets** → set to `OverlapOnlyPawn` (only the player triggers it)
|
||||||
|
|
||||||
|
#### 4.3 — Create the Config Variable
|
||||||
|
|
||||||
|
1. In the **My Blueprint** panel (left side), click **+ Variable**
|
||||||
|
2. Name: `Config`
|
||||||
|
3. Variable Type: **Struct → `S_PickupConfig`** (you may need to create this struct first — see spec #25 for its fields)
|
||||||
|
|
||||||
|
If `S_PickupConfig` doesn't exist yet, create it manually:
|
||||||
|
```
|
||||||
|
Content Browser → Framework/Inventory/
|
||||||
|
Right-click → Blueprints → Structure
|
||||||
|
Name: S_PickupConfig
|
||||||
|
Add fields:
|
||||||
|
ItemData → Type: DA_ItemData (Object Reference)
|
||||||
|
Quantity → Type: Integer, default: 1
|
||||||
|
bAutoRotate → Type: Boolean, default: true
|
||||||
|
bBobUpDown → Type: Boolean, default: true
|
||||||
|
BobAmplitude → Type: Float, default: 10.0
|
||||||
|
BobFrequency → Type: Float, default: 1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
4. After creating the variable, select it → Details panel:
|
||||||
|
- **Instance Editable** → ✓ checked (so you can set it per-instance in the level)
|
||||||
|
- **Category** → `Pickup Config`
|
||||||
|
- **Tooltip** → "The item data asset this pickup represents"
|
||||||
|
|
||||||
|
#### 4.4 — Wire the Construction Script
|
||||||
|
|
||||||
|
Go to the **Construction Script** tab (or open the Construction Script graph). This runs every time you change the Config in the editor, so the mesh updates immediately.
|
||||||
|
|
||||||
|
```
|
||||||
|
Construction Script
|
||||||
|
│
|
||||||
|
├─ Get Config → Break S_PickupConfig → ItemData
|
||||||
|
│ │
|
||||||
|
│ ├─ Is Valid (ItemData)?
|
||||||
|
│ │ ├─ True → continue
|
||||||
|
│ │ └─ False → Print String "No ItemData assigned" (Editor-only) → Return
|
||||||
|
│ │
|
||||||
|
│ └─ ItemData → Get World Mesh (this is a soft reference — may not be loaded)
|
||||||
|
│ │
|
||||||
|
│ ├─ Is Valid (WorldMesh)?
|
||||||
|
│ │ ├─ True:
|
||||||
|
│ │ │ → Set Static Mesh (Mesh component, New Mesh = WorldMesh)
|
||||||
|
│ │ │ → ItemData → Get Display Name → Set Actor Label
|
||||||
|
│ │ └─ False:
|
||||||
|
│ │ → ItemData → World Mesh → Async Load Asset
|
||||||
|
│ │ → On Load Complete → Set Static Mesh
|
||||||
|
│ │
|
||||||
|
│ └─ ItemData → Get Icon → (cache for UI prompt)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key node-by-node:**
|
||||||
|
1. Drag the `Config` variable from My Blueprint → **Get Config**
|
||||||
|
2. **Break S_PickupConfig** (right-click the pin → Split Struct Pin, or drag off → "Break")
|
||||||
|
3. From the `ItemData` pin, drag off → Create a `IsValid` node
|
||||||
|
4. From the valid `ItemData` output, drag off → type `Get World Mesh` (this appears because `WorldMesh` is a `UPROPERTY` on `DA_ItemData`; if using soft reference, you'll get a `TSoftObjectPtr<UStaticMesh>` — connect to **Async Load Asset** or **Load Synchronous** for Construction Script)
|
||||||
|
5. From the resolved mesh, connect to `Set Static Mesh` node → target is the `Mesh` component (drag Mesh from the Components panel into the graph → Get)
|
||||||
|
|
||||||
|
#### 4.5 — Wire Interaction (I_Interactable)
|
||||||
|
|
||||||
|
**Step A — Add the Interface:**
|
||||||
|
1. **Class Settings** (toolbar button) → **Interfaces** → **Add** → select `UInteractable` (the C++ interface from `I_InterfaceLibrary.h`)
|
||||||
|
2. Compile. Now `Interact`, `Can Interact`, `Get Interaction Prompt` events appear in the right-click menu.
|
||||||
|
|
||||||
|
**Step B — Wire the Events:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Event Interact (from I_Interactable)
|
||||||
|
│ Interactor: AActor*
|
||||||
|
│
|
||||||
|
├─ Get Config → Break → ItemData
|
||||||
|
│
|
||||||
|
├─ Interactor → Get Component by Class (BPC_InventorySystem)
|
||||||
|
│ │
|
||||||
|
│ └─ Is Valid?
|
||||||
|
│ ├─ False → Return (no inventory system on interactor)
|
||||||
|
│ └─ True → InventorySystem
|
||||||
|
│ │
|
||||||
|
│ ├─ Call Can Add Item(ItemData, Quantity from Config)
|
||||||
|
│ │ │
|
||||||
|
│ │ ├─ False → Print "Inventory Full" → Return
|
||||||
|
│ │ └─ True:
|
||||||
|
│ │ ├─ Call Add Item(ItemData, Quantity)
|
||||||
|
│ │ ├─ Play Sound at Location (PickupSound, GetActorLocation)
|
||||||
|
│ │ ├─ Destroy Actor
|
||||||
|
│ │ └─ Return
|
||||||
|
│ │
|
||||||
|
│ └─ (If ItemData has bIsInfinite or respawn logic — skip Destroy)
|
||||||
|
|
||||||
|
Event Can Interact (from I_Interactable)
|
||||||
|
│ Interactor, OutBlockReason (by ref)
|
||||||
|
│
|
||||||
|
├─ Is Config.ItemData Valid?
|
||||||
|
│ ├─ False → OutBlockReason = "No item data" → Return False
|
||||||
|
│ └─ True → Return True
|
||||||
|
|
||||||
|
Event Get Interaction Prompt (from I_Interactable)
|
||||||
|
│
|
||||||
|
├─ Get Config → ItemData → Get Display Name
|
||||||
|
└─ Return "Pick up [ItemName]"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.6 — Add Bobbing Rotation (optional polish)
|
||||||
|
|
||||||
|
In **Event Graph → Event BeginPlay**:
|
||||||
|
|
||||||
|
```
|
||||||
|
Event BeginPlay
|
||||||
|
│
|
||||||
|
├─ Get Config → Break → bAutoRotate
|
||||||
|
│ └─ True → Add Timeline → name: "BobAndRotate"
|
||||||
|
│ ├─ Update track:
|
||||||
|
│ │ ├─ Mesh → Add World Rotation (DeltaRotation = 0,0,Timeline.Rotation * 90)
|
||||||
|
│ │ └─ Mesh → Add World Offset (Delta = 0,0,Timeline.Bob * BobAmplitude * sin(time), ...)
|
||||||
|
│ │
|
||||||
|
│ └─ Timeline: float track 0→1 looping over 2 seconds
|
||||||
|
├─ Enable collision on InteractionCollision
|
||||||
|
└─ Bind OnComponentBeginOverlap(InteractionCollision) → Highlight mesh, show prompt
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.7 — Compile and Use
|
||||||
|
|
||||||
|
1. **Compile → Save**
|
||||||
|
2. **Drag `BP_ItemPickup` into your level**
|
||||||
|
3. Select the instance → Details panel → **Pickup Config → Item Data** → select `DA_Item_MedKit`
|
||||||
|
4. Set `Quantity` → `1` (or more for stacked items)
|
||||||
|
5. The **Construction Script** runs immediately — the mesh should appear in the viewport
|
||||||
|
6. Position the actor where you want it
|
||||||
|
|
||||||
|
**Verification in PIE (Play In Editor):**
|
||||||
|
- The pickup should show its mesh, slowly rotating
|
||||||
|
- Walk up to it — the overlap event fires
|
||||||
|
- Press Interact — item goes into inventory, pickup disappears
|
||||||
|
- Open inventory → item should be in a slot with the correct name and icon
|
||||||
|
|
||||||
|
```
|
||||||
|
DA_Item_MedKit (Content Browser) ← defines IDENTITY
|
||||||
|
↑ referenced by
|
||||||
|
BP_ItemPickup (Actor in level) ← physical BODY in world
|
||||||
|
├── StaticMeshComponent "Mesh" ← renders WorldMesh from Data Asset
|
||||||
|
├── SphereComponent "Collision" ← detects player proximity
|
||||||
|
└── Config struct ← holds pointer to DA_ItemData + quantity
|
||||||
|
↑ interacts via
|
||||||
|
BPC_InteractionDetector → I_Interactable → BPC_InventorySystem.AddItem()
|
||||||
|
```
|
||||||
|
|
||||||
|
> **For concrete, step-by-step examples** of building specific item types (flashlight, pistol, medkit, keycard) with complete Blueprint graphs, component lists, and wiring diagrams, see the [`docs/game/`](../../game/README.md) directory.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Single Identity Tag — Why One Tag Is Enough
|
||||||
|
|
||||||
|
Each item has exactly one `ItemTag` GameplayTag. This is intentional — the tag is the item's **unique identifier**, not a categorization bucket.
|
||||||
|
|
||||||
|
Categorization and behavioral flags are handled by separate, purpose-built properties:
|
||||||
|
|
||||||
|
| Concern | Mechanism | Example |
|
||||||
|
|---------|-----------|---------|
|
||||||
|
| **Identity** | `ItemTag` (single GameplayTag) | `Framework.Item.Consumable.MedKit` |
|
||||||
|
| **Category** | `ItemType` (enum) | Consumable, Weapon, KeyItem, Document, etc. |
|
||||||
|
| **Is non-droppable?** | `bIsKeyItem` (bool) | Key items are auto-protected from drop/clear |
|
||||||
|
| **Can be dropped?** | `bCanBeDropped` (bool) | Ammo/documents can be dropped; key items cannot |
|
||||||
|
| **Has 3D inspection?** | `bHasInspectMode` (bool) | Enables rotate-examine on pickup |
|
||||||
|
| **Crafting relationships** | `CombinesWith` (array of GameplayTag) | `MedKit` + `Bandage` → `SuperMedKit` |
|
||||||
|
| **Project-specific data** | `CustomProperties` (TMap<FName, FString>) | Key=`"QuestRewardID"`, Value=`"Q12"` |
|
||||||
|
| **Tag-based queries** | Hierarchical tag structure | `ItemTag.MatchesTag(Framework.Item.Consumable)` returns true for all consumables |
|
||||||
|
|
||||||
|
**Why not multiple ItemTags?**
|
||||||
|
- A single unique ID prevents ambiguity: inventory lookups, save/load references, and crafting recipes all need one canonical name for each item.
|
||||||
|
- Hierarchical GameplayTags support partial matching: `Framework.Item.Consumable.MedKit` matches `Framework.Item.Consumable` for category-wide queries.
|
||||||
|
- Adding a `FGameplayTagContainer ItemTags` would create confusion — which tag is the "real" ID? Do you check all of them? Do you sort by the first one?
|
||||||
|
- The `ItemType` enum + boolean flags + `CombinesWith` array already cover every use case without tag collision risks.
|
||||||
|
|
||||||
|
**If you truly need multiple filtering tags**, the `CustomProperties` map can store them (e.g., `"FilterTags"` → `"Flammable,Magnetic,QuestItem"`), and your BP can parse the comma-separated string at runtime. But in practice, this is rarely needed because:
|
||||||
|
- `ItemType` already categorizes items
|
||||||
|
- `bIsKeyItem` / `bCanBeDropped` already handle behavioral flags
|
||||||
|
- `CombinesWith` already handles cross-item relationships
|
||||||
|
- Hierarchical tag matching handles category queries (e.g., `MatchesTag(Framework.Item.Weapon)`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
- **Requires:** `GameplayTag` system (`DA_GameTagRegistry`), `UPrimaryDataAsset` (engine base)
|
- **Requires:** `GameplayTag` system (`DA_GameTagRegistry`), `UPrimaryDataAsset` (engine base)
|
||||||
- **Required By:** `BPC_InventorySystem`, `BPC_EquipmentSlotSystem`, `BPC_ConsumableSystem`, `BPC_AmmoResourceSystem`, `BPC_ItemCombineSystem`, `BPC_KeyItemSystem`, `BPC_ActiveItemSystem`, `DA_ItemDatabase` (collection)
|
- **Required By:** `BPC_InventorySystem`, `BPC_EquipmentSlotSystem`, `BPC_ConsumableSystem`, `BPC_AmmoComponent` (70), `BPC_ItemCombineSystem`, `BPC_KeyItemSystem`, `BPC_ActiveItemSystem`
|
||||||
- **Engine/Plugin Requirements:** `GameplayTags`, `AssetManager` (Primary Data Asset registration)
|
- **Engine/Plugin Requirements:** `GameplayTags`, `AssetManager` (Primary Data Asset registration)
|
||||||
- **Parent Class:** `UPrimaryDataAsset`
|
- **Parent Class:** `UPrimaryDataAsset`
|
||||||
|
|
||||||
@@ -13,9 +265,10 @@ The single source of truth for every item in the game. Each item is represented
|
|||||||
| Property | Value |
|
| Property | Value |
|
||||||
|----------|-------|
|
|----------|-------|
|
||||||
| **Parent Class** | `UPrimaryDataAsset` |
|
| **Parent Class** | `UPrimaryDataAsset` |
|
||||||
| **Class Type** | Blueprint Function Library (Data Asset) |
|
| **C++ Class** | `UDA_ItemData` (in `Source/PG_Framework/Public/Inventory/DA_ItemData.h`) |
|
||||||
| **Asset Path** | `Content/Data/Items/DA_Item_[Name]` |
|
| **Class Type** | Primary Data Asset |
|
||||||
| **Implements Interfaces** | None |
|
| **Asset Path** | `Content/Framework/DataAssets/Items/DA_Item_[Name]` |
|
||||||
|
| **Implements Interfaces** | None (passive data container) | |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -87,19 +340,28 @@ No custom structs defined in this Data Asset class. Consumed structs from other
|
|||||||
|
|
||||||
## 4. Functions
|
## 4. Functions
|
||||||
|
|
||||||
No runtime functions. This is a pure data container. The only accessible operations are direct variable reads from Blueprints that hold a reference to the asset.
|
`DA_ItemData` is a pure data container. All properties are read directly from Blueprints that hold a reference to the asset. The following functions exist in the C++ class (`Source/PG_Framework/Public/Inventory/DA_ItemData.h`):
|
||||||
|
|
||||||
|
### Runtime (All BlueprintCallable / BlueprintPure)
|
||||||
|
|
||||||
|
| Function | Returns | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `GetResistance(DamageType)` (on `DA_EquipmentConfig`) | `float` | Returns resistance value for a damage type (armor/equipment) |
|
||||||
|
| *(All UPROPERTY reads)* | — | `ItemTag`, `DisplayName`, `ItemType`, `Weight`, `StackLimit`, etc. are read directly — no function call needed |
|
||||||
|
|
||||||
### Editor-Only Functions (for content team validation)
|
### Editor-Only Functions (for content team validation)
|
||||||
|
|
||||||
#### `ValidateItemData` → `Bool` (BlueprintCallable, Editor only)
|
#### `ValidateItemData` → `Bool` (BlueprintCallable, Editor only)
|
||||||
|
- **C++ Implementation:** Full — `Source/PG_Framework/Private/Inventory/DA_ItemData.cpp`
|
||||||
- **Description:** Runs editor validation checks (tag uniqueness, required fields filled).
|
- **Description:** Runs editor validation checks (tag uniqueness, required fields filled).
|
||||||
- **Parameters:** None
|
- **Parameters:** `OutErrors` (FString, out) — human-readable error messages
|
||||||
- **Flow:**
|
- **Flow:**
|
||||||
1. Check `ItemTag != None` — if None, log warning
|
1. Check `ItemTag != None` — if None, append error
|
||||||
2. Check `DisplayName != ""` — if empty, log warning
|
2. Check `DisplayName != ""` — if empty, append error
|
||||||
3. If `StackLimit < 1`, reset to 1
|
3. If `StackLimit < 1`, append error
|
||||||
4. If `bIsKeyItem` then `bCanBeDropped = false` (forced)
|
4. If `bIsKeyItem` then warn if `bCanBeDropped == true`
|
||||||
5. Return true if all validations pass
|
5. If Consumable, check at least one effect value > 0
|
||||||
|
6. Return true if all validations pass; false if any failed
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -115,21 +377,40 @@ No event overrides. Data Assets do not tick or have BeginPlay.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Blueprint Graph Logic Flow
|
## 7. Content Creation Flow
|
||||||
|
|
||||||
No blueprint graph. This asset is created and edited in the Content Browser via "Create Advanced Asset -> Blueprint -> Data Asset -> DA_ItemData".
|
`DA_ItemData` has **no blueprint graph**. It is created and edited in the Content Browser as a Data Asset instance.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
A[Content Browser] --> B[Right-click > Data Asset > DA_ItemData]
|
A[Content Browser] --> B[Right-click > Miscellaneous > Data Asset]
|
||||||
B --> C[Name: DA_Item_MedKit]
|
B --> C[Select Class: DA_ItemData]
|
||||||
C --> D[Fill ItemTag: Item.MedKit]
|
C --> D[Name: DA_Item_MedKit]
|
||||||
D --> E[Fill DisplayName: Med Kit]
|
D --> E[Open Asset > Fill Properties]
|
||||||
E --> F[Assign Icon, Mesh]
|
E --> F[ItemTag: Framework.Item.Consumable.MedKit]
|
||||||
F --> G[Set ItemType: Consumable]
|
F --> G[DisplayName: Med Kit]
|
||||||
G --> H[Fill ConsumableData]
|
G --> H[Set Icon, WorldMesh]
|
||||||
H --> I[Save Asset]
|
H --> I[ItemType: Consumable]
|
||||||
I --> J[Registered in Asset Manager]
|
I --> J[Fill ConsumableData: HealthRestore=25]
|
||||||
|
J --> K[Save Asset]
|
||||||
|
K --> L[Ready — referenced by BP_ItemPickup actors]
|
||||||
|
```
|
||||||
|
|
||||||
|
### How Systems Use the Data Asset (Read-Only)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
DA[DA_Item_MedKit<br/>Data Asset] -->|referenced by| PICKUP[BP_ItemPickup<br/>Actor in world]
|
||||||
|
PICKUP -->|OnInteract| INV[BPC_InventorySystem.AddItem(DA, 1)]
|
||||||
|
INV -->|stores reference| SLOT[FInventorySlot.Item = DA]
|
||||||
|
|
||||||
|
DA -->|reads EquipmentData| EQUIP[BPC_EquipmentSlotSystem]
|
||||||
|
DA -->|reads ConsumableData| CONSUM[BPC_ConsumableSystem]
|
||||||
|
DA -->|reads CombinesWith| COMBINE[BPC_ItemCombineSystem]
|
||||||
|
DA -->|reads bIsKeyItem| KEY[BPC_KeyItemSystem]
|
||||||
|
DA -->|reads ItemType| ACTIVE[BPC_ActiveItemSystem<br/>routes to correct handler]
|
||||||
|
|
||||||
|
INV -->|dispatches OnItemAdded| UI[WBP_InventoryMenu<br/>reads DisplayName, Icon, Description]
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -170,15 +451,18 @@ flowchart LR
|
|||||||
|
|
||||||
## 10. Reuse Notes
|
## 10. Reuse Notes
|
||||||
|
|
||||||
- Create one `DA_ItemData` per item. Name convention: `DA_Item_[ShortName]` (e.g. `DA_Item_MedKit`, `DA_Item_Flashlight`, `DA_Item_KeyCard_Omega`).
|
- **Create one `DA_ItemData` per item.** Naming convention: `DA_Item_[ShortName]` (e.g., `DA_Item_MedKit`, `DA_Item_Flashlight`, `DA_Item_KeyCard_Omega`).
|
||||||
- All `ItemTag` values must be registered in `DA_GameTagRegistry` before use.
|
- **Data Assets are not world actors.** They cannot be dragged into a level. To place an item in the world, create a `BP_ItemPickup` actor (spec #25) and assign the `DA_ItemData` to its `Config.ItemData` property.
|
||||||
- The `CustomProperties` map future-proofs any per-project additions without modifying the base asset class.
|
- All `ItemTag` values must be registered in `DA_GameTagRegistry` before use. Use the hierarchical tag structure: `Framework.Item.[Type].[Name]`.
|
||||||
|
- The `CustomProperties` map future-proofs any per-project additions without modifying the base C++ class.
|
||||||
- For non-stackable items (weapons, key items, tools), set `StackLimit = 1`.
|
- For non-stackable items (weapons, key items, tools), set `StackLimit = 1`.
|
||||||
- For stacks, choose a sensible `StackLimit` (e.g. ammo = 999, consumables = 5).
|
- For stacks, choose a sensible `StackLimit` (e.g., ammo = 999, consumables = 5, resources = 99).
|
||||||
- The `AssetManager` should be configured with `PrimaryAssetType = Item` and `PrimaryAssetLabel = Item` for async loading support.
|
- The `AssetManager` should be configured with `PrimaryAssetType = Item` and `PrimaryAssetLabel = Item` for async loading support.
|
||||||
- Document items (`E_ItemType.Document`) additionally populate `BPC_DocumentArchiveSystem` with their content; the `Description` field serves as document body text.
|
- Document items (`E_ItemType.Document`) additionally populate `BPC_DocumentArchiveSystem` with their content; the `Description` field serves as document body text.
|
||||||
- To add a new item property across all items, add a new variable to `DA_ItemData`. Do not create a separate asset class per item type — use the `ItemType` enum for branching logic.
|
- To add a new item property across all items, add a new `UPROPERTY` to `DA_ItemData` in C++. Do not create a separate asset class per item type — use the `ItemType` enum + `EditCondition` metadata for branching display.
|
||||||
- The `ValidateItemData` editor function can be exposed as a Python command for batch validation on check-in.
|
- The `ValidateItemData` editor function can be exposed as a Python command for batch validation on check-in.
|
||||||
|
- **C++ EditCondition metadata** auto-hides irrelevant panels based on `ItemType` (e.g., `ConsumableData` only shows when `ItemType == Consumable`). Designers never see irrelevant fields.
|
||||||
|
- **Inventory systems interact with `DA_ItemData` through C++ `TObjectPtr<UDA_ItemData>`** references — the Data Asset is never copied, only referenced by pointer. All inventory operations pass the `DA_ItemData*` pointer.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
406
docs/blueprints/01-core/150_BPC_PlatformServiceAbstraction.md
Normal file
406
docs/blueprints/01-core/150_BPC_PlatformServiceAbstraction.md
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
# 150 — Platform Service Abstraction (`BPC_PlatformServiceAbstraction`)
|
||||||
|
|
||||||
|
> **Blueprint-Only Implementation** — UE 5.5–5.7 supports all platform detection and SDK routing from Blueprints via `UGameplayStatics::GetPlatformName()`, conditional compilation checks, and plugin API calls. This component is the **single source of truth** for platform identity, replacing the three fragmented detection systems that currently exist across the framework.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Central platform authority attached to the Game Instance. Detects the platform once at startup and provides a unified `EPlatformFamily` enum that ALL subsystems query instead of detecting platform independently. Routes platform-specific SDK calls (achievement submission to Steam/PSN/Xbox Live/Nintendo, cloud saves, overlays, input device profiles). Returns stub/no-op implementations on unsupported platforms so game code never needs `#if PLATFORM_PS5` checks.
|
||||||
|
|
||||||
|
## The Problem Being Solved
|
||||||
|
|
||||||
|
Currently the framework has **three independent platform detection systems**, each calling `GetPlatformName()` separately:
|
||||||
|
|
||||||
|
| System | Enum | Platform Families |
|
||||||
|
|--------|------|-------------------|
|
||||||
|
| `BPC_RenderPipelineManager` (149) | `EPlatformFamily` | PS5, PS4, Xbox_Series, Xbox_One, Switch, PC_High, PC_Low, SteamDeck |
|
||||||
|
| `SS_EnhancedInputManager` (128) | `E_InputPlatform` | PC_KeyboardMouse, Xbox, PS5_DualSense |
|
||||||
|
| `BPC_HapticsController` (148) | `EControllerPlatform` | PC_Generic, Xbox, PS5_DualSense, PS4_DualShock |
|
||||||
|
|
||||||
|
These are **different enums with different values** and don't talk to each other. If you change platform in one system, nothing cascades.
|
||||||
|
|
||||||
|
**After this component is integrated, all three systems query `BPC_PlatformServiceAbstraction.GetPlatformFamily()` instead of detecting platform themselves. The old enums are deprecated and mapped to `EPlatformFamily`.**
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- **Requires:** [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) (attached as component), [`DA_GameTagRegistry`](../01-core/01_DA_GameTagRegistry.md) (platform tag validation)
|
||||||
|
- **Required By:** [`BPC_RenderPipelineManager`](../12-settings/149_BPC_RenderPipelineManager.md) (GFX quality profile selection), [`SS_EnhancedInputManager`](../15-input/128_SS_EnhancedInputManager.md) (input device profile selection), [`BPC_HapticsController`](../12-settings/148_BPC_HapticsController.md) (controller platform detection), [`SS_AchievementSystem`](../11-meta/103_SS_AchievementSystem.md) (SDK routing), [`SS_SettingsSystem`](105_SS_SettingsSystem.md) (platform-aware defaults)
|
||||||
|
- **Engine/Plugin Requirements:** GameplayTags, `UGameplayStatics`, Platform SDK plugins (Steamworks, PSN, Xbox GDK, Nintendo SDK — all optional)
|
||||||
|
|
||||||
|
## Class Info
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| **Parent Class** | `ActorComponent` |
|
||||||
|
| **Class Type** | Blueprint Component |
|
||||||
|
| **Asset Path** | `Content/Framework/Core/BPC_PlatformServiceAbstraction` |
|
||||||
|
| **Implements Interfaces** | None |
|
||||||
|
| **Attachment** | Game Instance (`GI_GameFramework`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Enums
|
||||||
|
|
||||||
|
### `EPlatformFamily` (UNIFIED — all systems use this)
|
||||||
|
|
||||||
|
| Value | Description | Input Device | Render Pipeline | Achievements SDK | Store |
|
||||||
|
|-------|-------------|-------------|-----------------|-----------------|-------|
|
||||||
|
| `Unknown = 0` | Not yet detected | — | — | — | — |
|
||||||
|
| `PC_Win64 = 1` | Windows PC (any GPU) | KB+M / Xbox / PS5 gamepad | Detected per GPU capability | Steam / EGS | Steam / EGS |
|
||||||
|
| `PC_Linux = 2` | Linux / SteamOS | Same as PC | Vulkan | Steam | Steam |
|
||||||
|
| `PS5 = 3` | PlayStation 5 | DualSense | Lumen + Nanite | PSN Trophies | PS Store |
|
||||||
|
| `PS5_Pro = 4` | PlayStation 5 Pro | DualSense | Lumen + PSSR | PSN Trophies | PS Store |
|
||||||
|
| `PS4 = 5` | PlayStation 4 / 4 Pro | DualShock 4 | Baked only | PSN Trophies | PS Store |
|
||||||
|
| `Xbox_Series = 6` | Xbox Series X\|S | Xbox Wireless | Lumen + Nanite | Xbox Live | MS Store |
|
||||||
|
| `Xbox_One = 7` | Xbox One / One X | Xbox Wireless | Baked only | Xbox Live | MS Store |
|
||||||
|
| `Switch = 8` | Nintendo Switch | Joy-Con / Pro | Baked + Proxy | Nintendo | eShop |
|
||||||
|
| `Switch_2 = 9` | Nintendo Switch 2 | Joy-Con 2 / Pro 2 | Baked + CSM + FSR | Nintendo | eShop |
|
||||||
|
| `SteamDeck = 10` | Steam Deck | Built-in / external | Baked/SSGI + CSM | Steam | Steam |
|
||||||
|
| `Mac = 11` | macOS | KB+M / gamepad | Metal / Baked | Steam | Steam / App Store |
|
||||||
|
|
||||||
|
### `EPlatformSDK`
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `None = 0` | No SDK available (editor, generic build) |
|
||||||
|
| `Steam = 1` | Steamworks SDK (achievements, cloud, overlay, stats) |
|
||||||
|
| `PSN = 2` | PlayStation Network SDK (trophies, cloud, overlay, invites) |
|
||||||
|
| `XboxLive = 3` | Xbox Live / GDK (achievements, cloud, overlay, invites) |
|
||||||
|
| `Nintendo = 4` | Nintendo SDK (achievements, cloud, overlay) |
|
||||||
|
| `EGS = 5` | Epic Games Store SDK |
|
||||||
|
|
||||||
|
### `EPlatformOverlayType`
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `Achievements = 0` | Show platform achievements/trophies page |
|
||||||
|
| `Friends = 1` | Open friends list |
|
||||||
|
| `Invite = 2` | Send game invite |
|
||||||
|
| `Store = 3` | Open store page (DLC, etc.) |
|
||||||
|
| `SocialFeed = 4` | Community / social feed |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Structs
|
||||||
|
|
||||||
|
### `SPlatformCapabilities`
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `Platform` | `EPlatformFamily` | Current platform |
|
||||||
|
| `SDK` | `EPlatformSDK` | Available platform SDK |
|
||||||
|
| `bSupportsLumen` | `bool` | Platform GPU supports Lumen GI |
|
||||||
|
| `bSupportsNanite` | `bool` | Platform GPU supports Nanite |
|
||||||
|
| `bSupportsHWRT` | `bool` | Platform supports hardware ray tracing |
|
||||||
|
| `bSupportsCloudSaves` | `bool` | Platform has cloud save API |
|
||||||
|
| `bSupportsAchievements` | `bool` | Platform has achievement/trophy system |
|
||||||
|
| `bSupportsOverlay` | `bool` | Platform has in-game overlay |
|
||||||
|
| `bSupportsRichPresence` | `bool` | Platform supports rich presence / "now playing" |
|
||||||
|
| `bSupportsUserGeneratedContent` | `bool` | Platform supports UGC/mod sharing |
|
||||||
|
| `bIsConsole` | `bool` | Is this a console platform (certification required)? |
|
||||||
|
| `bIsHandheld` | `bool` | Is this a handheld device? |
|
||||||
|
| `DefaultInputType` | `FName` | Default input device for this platform ("Gamepad", "Keyboard", "Touch") |
|
||||||
|
| `MaxLocalPlayers` | `int32` | Maximum local split-screen players |
|
||||||
|
| `StoreID` | `FString` | Platform store ID for DLC checks |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Variables
|
||||||
|
|
||||||
|
### Configuration (Instance Editable)
|
||||||
|
| Variable | Type | Default | Category | Description |
|
||||||
|
|----------|------|---------|----------|-------------|
|
||||||
|
| `bAutoDetectPlatform` | `bool` | `true` | `Config` | Auto-detect on Initialize |
|
||||||
|
| `OverridePlatform` | `EPlatformFamily` | `Unknown` | `Config` | Force a platform (for testing in editor) |
|
||||||
|
| `bEnableSDKIntegration` | `bool` | `true` | `Config` | Enable platform SDK calls |
|
||||||
|
|
||||||
|
### Internal (Private)
|
||||||
|
| Variable | Type | Default | Category | Description |
|
||||||
|
|----------|------|---------|----------|-------------|
|
||||||
|
| `CurrentPlatform` | `EPlatformFamily` | `Unknown` | `State` | Detected or overridden platform |
|
||||||
|
| `CurrentSDK` | `EPlatformSDK` | `None` | `State` | Active platform SDK |
|
||||||
|
| `PlatformCapabilities` | `SPlatformCapabilities` | — | `State` | Full capabilities snapshot |
|
||||||
|
| `bIsInitialized` | `bool` | `false` | `State` | Whether Initialize completed |
|
||||||
|
| `CachedGameInstance` | `GI_GameFramework` | `None` | `Cache` | Owner Game Instance reference |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Functions
|
||||||
|
|
||||||
|
### Public Functions
|
||||||
|
|
||||||
|
#### `Initialize` → `void`
|
||||||
|
- **Description:** Detect platform, resolve SDK, build capabilities, broadcast ready signal. Called once at Game Instance startup.
|
||||||
|
- **Flow:**
|
||||||
|
1. Get Owner → Cast to `GI_GameFramework` → cache
|
||||||
|
2. If `OverridePlatform != Unknown`: use override
|
||||||
|
3. Else if `bAutoDetectPlatform`: call `DetectPlatform()`
|
||||||
|
4. Resolve which SDK is available: check plugin state, build configuration
|
||||||
|
5. Build `SPlatformCapabilities` for detected platform
|
||||||
|
6. Register platform in `DA_GameTagRegistry` as `Framework.Platform.{Name}`
|
||||||
|
7. Set `bIsInitialized = true`
|
||||||
|
8. Broadcast `OnPlatformReady(CurrentPlatform)`
|
||||||
|
9. Broadcast `OnPlatformCapabilitiesReady(PlatformCapabilities)`
|
||||||
|
|
||||||
|
#### `DetectPlatform` → `EPlatformFamily`
|
||||||
|
- **Description:** Detects the current platform using UE5 APIs.
|
||||||
|
- **Flow:**
|
||||||
|
1. `UGameplayStatics::GetPlatformName()` → switch:
|
||||||
|
- "PS5" → check if Pro model available → `PS5_Pro` or `PS5`
|
||||||
|
- "PS4" → `PS4`
|
||||||
|
- "XboxOne" → `Xbox_One`
|
||||||
|
- "XSX" or "XboxAnaconda" → `Xbox_Series` (check if S model → adjust capabilities)
|
||||||
|
- "Switch" → check model revision → `Switch_2` or `Switch`
|
||||||
|
- "Win64" → check if running on Steam Deck (`IsSteamDeck()`) → `SteamDeck`
|
||||||
|
- "Win64" → `PC_Win64`
|
||||||
|
- "Linux" → `PC_Linux`
|
||||||
|
- "Mac" → `Mac`
|
||||||
|
2. Store in `CurrentPlatform`
|
||||||
|
3. Return `CurrentPlatform`
|
||||||
|
|
||||||
|
#### `GetPlatformFamily` → `EPlatformFamily`
|
||||||
|
- **Description:** Returns the current platform. **This is the function ALL other systems call.** Read-only, always available after initialization.
|
||||||
|
|
||||||
|
#### `GetSDK` → `EPlatformSDK`
|
||||||
|
- **Description:** Returns the available platform SDK. Used by achievement and save systems for routing.
|
||||||
|
|
||||||
|
#### `GetPlatformCapabilities` → `SPlatformCapabilities`
|
||||||
|
- **Description:** Returns the full capabilities struct. Read-only.
|
||||||
|
|
||||||
|
#### `SubmitAchievementToPlatform` → `bool`
|
||||||
|
- **Description:** Route an achievement unlock to the correct platform SDK.
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `AchievementID` | `FString` | Platform-specific achievement ID |
|
||||||
|
- **Flow:**
|
||||||
|
1. Switch on `CurrentSDK`:
|
||||||
|
- `Steam` → call Steamworks `ISteamUserStats::SetAchievement(AchievementID)` + `StoreStats()`
|
||||||
|
- `PSN` → call PSN Trophy API `UnlockTrophy(AchievementID)`
|
||||||
|
- `XboxLive` → call Xbox Live `AchievementsService::UpdateAchievement(AchievementID, 100)`
|
||||||
|
- `Nintendo` → call Nintendo achievement API
|
||||||
|
- `None` → log "Achievement submitted (editor mode)" → return true (no-op stub)
|
||||||
|
2. Return true if successful, false on SDK error
|
||||||
|
|
||||||
|
#### `SyncCloudSave` → `bool`
|
||||||
|
- **Description:** Upload or sync save data to platform cloud.
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `SlotIndex` | `int32` | Which save slot |
|
||||||
|
| `SaveData` | `TArray<uint8>` | Serialized save data |
|
||||||
|
- **Flow:** Route to Steam Cloud / PSN Cloud / Xbox Cloud / Nintendo Cloud based on `CurrentSDK`.
|
||||||
|
|
||||||
|
#### `ShowPlatformOverlay` → `void`
|
||||||
|
- **Description:** Open a platform-specific overlay.
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `OverlayType` | `EPlatformOverlayType` | Which overlay to show |
|
||||||
|
- **Flow:**
|
||||||
|
1. Switch on `CurrentSDK`:
|
||||||
|
- `Steam` → `ISteamFriends::ActivateGameOverlay(URL)`
|
||||||
|
- `PSN` → System software overlay API
|
||||||
|
- Others → nop (not supported)
|
||||||
|
|
||||||
|
#### `GetDefaultInputProfile` → `FName`
|
||||||
|
- **Description:** Returns the default input profile for this platform. Used by `SS_EnhancedInputManager` to select the correct `DA_InputMappingProfile`.
|
||||||
|
- **Returns:** "PC_KeyboardMouse", "Xbox", "PS5_DualSense", "PS4_DualShock", "Switch_JoyCon"
|
||||||
|
|
||||||
|
#### `IsConsole` → `bool`, `IsHandheld` → `bool`, `IsPS5` → `bool`, `IsXbox` → `bool`, `IsSwitch` → `bool`, `IsPC` → `bool`, `IsSteamDeck` → `bool`
|
||||||
|
- **Description:** Convenience boolean checks for common platform queries. All return from `PlatformCapabilities` struct.
|
||||||
|
|
||||||
|
#### `GetConsoleCertificationRequirements` → `TArray<FText>`
|
||||||
|
- **Description:** Returns a list of TRC (Technical Requirement Checklist) items for the current console platform. Empty on PC.
|
||||||
|
- **Flow:**
|
||||||
|
1. Switch on `CurrentPlatform`:
|
||||||
|
- `PS5` → return Sony TRC items (60 FPS perf mode, HDR support, controller speaker, activity cards, etc.)
|
||||||
|
- `PS4` → return Sony PS4 TRC items
|
||||||
|
- `Xbox_Series` → return Microsoft XR items (Quick Resume, Smart Delivery, VRR, etc.)
|
||||||
|
- `Xbox_One` → return Microsoft XR items
|
||||||
|
- `Switch` → return Nintendo guidelines (dynamic resolution, Handheld/Docked parity, etc.)
|
||||||
|
- `PC_*` → return empty (no console cert needed)
|
||||||
|
|
||||||
|
#### `GetStoreID` → `FString`
|
||||||
|
- **Description:** Returns the platform store app ID for DLC entitlement checks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Event Dispatchers
|
||||||
|
|
||||||
|
| Dispatcher | Parameters | Bind Access | Description |
|
||||||
|
|------------|-----------|-------------|-------------|
|
||||||
|
| `OnPlatformReady` | `EPlatformFamily Platform` | `Public` | Fired after platform detection completes — all other systems bind to this for deferred init |
|
||||||
|
| `OnPlatformCapabilitiesReady` | `SPlatformCapabilities Capabilities` | `Public` | Fired alongside OnPlatformReady with full capability info |
|
||||||
|
| `OnPlatformSDKReady` | `EPlatformSDK SDK` | `Public` | Fired when platform SDK is ready for API calls |
|
||||||
|
| `OnPlatformOverlayOpened` | `EPlatformOverlayType Type` | `Public` | Fired when platform overlay opens (game should pause) |
|
||||||
|
| `OnPlatformOverlayClosed` | — | `Public` | Fired when platform overlay closes (game resumes) |
|
||||||
|
| `OnCloudSaveCompleted` | `bool bSuccess`, `int32 SlotIndex` | `Public` | Fired when cloud save upload completes |
|
||||||
|
| `OnAchievementSubmitted` | `FString AchievementID`, `bool bSuccess` | `Public` | Fired after platform API achievement call |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Overridden Events
|
||||||
|
|
||||||
|
### Event: On Component Created (attached to GI_GameFramework via BeginPlay)
|
||||||
|
- **Description:** Auto-initializes at game start.
|
||||||
|
- **Flow:**
|
||||||
|
1. Call `Initialize()`
|
||||||
|
2. All dependent subsystems that bound to `OnPlatformReady` receive the event and complete their initialization
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Blueprint Graph Logic Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[GI_GameFramework: BeginPlay] --> B[BPC_PlatformServiceAbstraction: Initialize]
|
||||||
|
B --> C{OverridePlatform?}
|
||||||
|
C -->|Yes| D[Use Override]
|
||||||
|
C -->|No| E[DetectPlatform]
|
||||||
|
E --> F[Switch on GetPlatformName]
|
||||||
|
F --> G[Set CurrentPlatform]
|
||||||
|
D --> G
|
||||||
|
|
||||||
|
G --> H[Resolve available SDK]
|
||||||
|
H --> I[Build SPlatformCapabilities]
|
||||||
|
I --> J[Register Platform Tag in DA_GameTagRegistry]
|
||||||
|
J --> K[Broadcast OnPlatformReady]
|
||||||
|
|
||||||
|
K --> L[ALL BOUND SYSTEMS RECEIVE]
|
||||||
|
L --> M[RenderPipelineManager: load DA_RPP for platform]
|
||||||
|
L --> N[EnhancedInputManager: load DA_InputMapping for platform]
|
||||||
|
L --> O[HapticsController: set EControllerPlatform from platform]
|
||||||
|
L --> P[AchievementSystem: route SDK via GetSDK]
|
||||||
|
L --> Q[SettingsSystem: load platform defaults]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Communication Matrix
|
||||||
|
|
||||||
|
| Who Talks | How | What Is Sent |
|
||||||
|
|-----------|-----|-------------|
|
||||||
|
| `GI_GameFramework` | `Owner` | Hosts this component |
|
||||||
|
| `BPC_PlatformServiceAbstraction` | `Dispatcher` → ALL systems | `OnPlatformReady(Platform)` — every system's deferred init trigger |
|
||||||
|
| `BPC_RenderPipelineManager` (149) | `Function Call` | `GetPlatformFamily()` → selects correct `DA_RenderPipelineProfile` |
|
||||||
|
| `SS_EnhancedInputManager` (128) | `Function Call` | `GetDefaultInputProfile()` → selects correct `DA_InputMappingProfile` |
|
||||||
|
| `BPC_HapticsController` (148) | `Function Call` | `GetPlatformFamily()` → maps to `EControllerPlatform` |
|
||||||
|
| `SS_AchievementSystem` (103) | `Function Call` | `SubmitAchievementToPlatform(ID)` → routes to Steam/PSN/Xbox/Nintendo |
|
||||||
|
| `SS_SaveManager` (35) | `Function Call` | `SyncCloudSave(Slot, Data)` → routes to platform cloud API |
|
||||||
|
| `SS_SettingsSystem` (105) | `Function Call` | `GetPlatformFamily()` → sets platform-appropriate defaults |
|
||||||
|
| `SS_UIManager` (44) | `Dispatcher` | `OnPlatformOverlayOpened/Closed` → pause/resume game |
|
||||||
|
| `DA_GameTagRegistry` (01) | `Function Call` | Register `Framework.Platform.{Name}` tag |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Platform Enum Mapping (Deprecation of old enums)
|
||||||
|
|
||||||
|
The old scattered platform enums are mapped to `EPlatformFamily` as follows:
|
||||||
|
|
||||||
|
| Deprecated Enum | Old Value | EPlatformFamily |
|
||||||
|
|----------------|-----------|-----------------|
|
||||||
|
| `E_InputPlatform::PC_KeyboardMouse` | 0 | `PC_Win64` (or `PC_Linux`, `Mac`) |
|
||||||
|
| `E_InputPlatform::Xbox` | 1 | `Xbox_Series` or `Xbox_One` (detected) |
|
||||||
|
| `E_InputPlatform::PS5_DualSense` | 2 | `PS5` or `PS5_Pro` |
|
||||||
|
| `EControllerPlatform::PC_Generic` | 1 | `PC_Win64` |
|
||||||
|
| `EControllerPlatform::Xbox` | 2 | `Xbox_Series` or `Xbox_One` |
|
||||||
|
| `EControllerPlatform::PS5_DualSense` | 3 | `PS5` |
|
||||||
|
| `EControllerPlatform::PS4_DualShock` | 4 | `PS4` |
|
||||||
|
|
||||||
|
All systems that previously used their own platform enum now call:
|
||||||
|
```
|
||||||
|
BPC_PlatformServiceAbstraction → GetPlatformFamily() → map to local enum internally
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Validation / Testing Checklist
|
||||||
|
|
||||||
|
- [ ] `DetectPlatform` correctly identifies all 12 platform families
|
||||||
|
- [ ] `OverridePlatform = PS4` → all systems use PS4 profiles (baked lighting, DualShock, PSN SDK)
|
||||||
|
- [ ] `OverridePlatform = Switch` → all systems use Switch profiles (baked, NIS, Joy-Con, Nintendo SDK)
|
||||||
|
- [ ] `OverridePlatform = PS5_Pro` → RenderPipelineManager enables PSSR upscaler
|
||||||
|
- [ ] `GetDefaultInputProfile` returns correct input profile for each platform
|
||||||
|
- [ ] `SubmitAchievementToPlatform` on Steam build successfully calls Steam API
|
||||||
|
- [ ] `SubmitAchievementToPlatform` in Editor returns true (no-op stub, no crash)
|
||||||
|
- [ ] `SyncCloudSave` routes to correct platform cloud API
|
||||||
|
- [ ] `ShowPlatformOverlay(Achievements)` opens correct platform overlay
|
||||||
|
- [ ] `OnPlatformReady` fires before any dependent system queries `GetPlatformFamily()`
|
||||||
|
- [ ] Changing `OverridePlatform` at runtime (dev cheat) cascades to all bound systems
|
||||||
|
- [ ] Edge case: No SDK available (editor, generic build) → all SDK calls return no-op stubs
|
||||||
|
- [ ] Edge case: Platform SDK initialization fails → `CurrentSDK = None`, systems fallback gracefully
|
||||||
|
- [ ] Edge case: Steam Deck detection in Desktop Mode → reported as `PC_Linux`, not `SteamDeck`
|
||||||
|
- [ ] Edge case: Xbox Series S (Lockhart) vs Series X (Anaconda) → different capabilities for GPU
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Manual Implementation Guide
|
||||||
|
|
||||||
|
### 11.1 Class Setup
|
||||||
|
1. Create Blueprint Class: parent `ActorComponent`, name `BPC_PlatformServiceAbstraction`
|
||||||
|
2. Path: `Content/Framework/Core/`
|
||||||
|
3. Attach to `GI_GameFramework` (or BP child of it)
|
||||||
|
|
||||||
|
### 11.2 Key UE5 Nodes
|
||||||
|
| Node | Where to Find | Used For |
|
||||||
|
|------|---------------|----------|
|
||||||
|
| `Get Platform Name` | Right-click → "Get Platform Name" | Primary detection API |
|
||||||
|
| `Switch on String` | Right-click → "Switch" | Platform name branching |
|
||||||
|
| `Is Steam Deck` | Custom node / check for "SteamDeck" in device name | Steam Deck detection |
|
||||||
|
| `Is In Editor` | Right-click → "Is In Editor" | SDK substitution in editor |
|
||||||
|
| `Switch on EPlatformFamily` | Right-click → "Switch" | Platform-specific logic |
|
||||||
|
|
||||||
|
### 11.3 Node-by-Node: DetectPlatform
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: DetectPlatform]
|
||||||
|
Step 1: Get Platform Name → Store as PlatformStr
|
||||||
|
Step 2: Switch on String (PlatformStr):
|
||||||
|
"PS5" →
|
||||||
|
Step 2a: Get device model info → if "Pro" → PS5_Pro, else PS5
|
||||||
|
"PS4" → PS4
|
||||||
|
"XboxOne" → Xbox_One
|
||||||
|
"XSX" → Xbox_Series (check if "Lockhart" variant for Series S)
|
||||||
|
"XboxAnaconda" → Xbox_Series
|
||||||
|
"Switch" → Switch (check model revision for Switch_2)
|
||||||
|
"Win64" →
|
||||||
|
Step 2b: Check if running on Steam Deck → if yes → SteamDeck
|
||||||
|
Step 2c: Else → PC_Win64
|
||||||
|
"Linux" → PC_Linux
|
||||||
|
"Mac" → Mac
|
||||||
|
Default → PC_Win64 (fallback)
|
||||||
|
Step 3: Store result in CurrentPlatform
|
||||||
|
Step 4: Return CurrentPlatform
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.4 Networking
|
||||||
|
- **Platform is per-client.** Each client may be on a different platform (PC host + PS5 client).
|
||||||
|
- `BPC_PlatformServiceAbstraction` runs on ALL instances (server + each client).
|
||||||
|
- Platform SDK calls are local-to-client.
|
||||||
|
- For listen server: host's platform SDK is used for host's achievements; clients use their own.
|
||||||
|
|
||||||
|
### 11.5 Multiplayer Note
|
||||||
|
- In a multiplayer session, each player's platform is detected independently
|
||||||
|
- Cross-platform play: a PC host may have Steam SDK active while a PS5 client has PSN SDK
|
||||||
|
- The Game Instance's PlatformServiceAbstraction only represents the LOCAL player's platform
|
||||||
|
- For dedicated servers: `CurrentPlatform = PC_Linux` or `PC_Win64`, `CurrentSDK = None` (no achievements on dedicated server)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Blueprint Build Checklist
|
||||||
|
|
||||||
|
- [ ] Create Blueprint class: `BPC_PlatformServiceAbstraction` (parent: `ActorComponent`)
|
||||||
|
- [ ] Add enums: `EPlatformFamily` (12 values), `EPlatformSDK` (6 values), `EPlatformOverlayType` (5 values)
|
||||||
|
- [ ] Add struct: `SPlatformCapabilities`
|
||||||
|
- [ ] Add all variables from Section 3
|
||||||
|
- [ ] Build `Initialize` → `DetectPlatform` → `BuildCapabilities` → broadcast chain
|
||||||
|
- [ ] Implement `DetectPlatform` with Switch on GetPlatformName
|
||||||
|
- [ ] Implement `SubmitAchievementToPlatform` with Switch on CurrentSDK
|
||||||
|
- [ ] Implement `SyncCloudSave`, `ShowPlatformOverlay`
|
||||||
|
- [ ] Implement all boolean convenience functions (`IsConsole`, `IsPS5`, etc.)
|
||||||
|
- [ ] Create all 7 event dispatchers
|
||||||
|
- [ ] Wire OnPlatformReady as deferred init trigger for all dependent subsystems
|
||||||
|
- [ ] Test: OverridePlatform = PS5 → OnPlatformReady fires with PS5
|
||||||
|
- [ ] Test: Editor mode → SDK calls return no-op stubs
|
||||||
|
- [ ] Test: Steam build → SubmitAchievement calls Steam API
|
||||||
|
- [ ] Test: Platform hot-swap (dev cheat) cascades to all bound systems
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Blueprint Spec: Platform Service Abstraction. Conforms to TEMPLATE.md v2.0 — part of the UE5 Modular Game Framework, CORE layer.*
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
# 08 — Health System (`BPC_HealthSystem`)
|
# 08 — Health System (`BPC_HealthSystem`)
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Stub** — `Source/PG_Framework/Public/Player/BPC_HealthSystem.h` provides the UCLASS shell, `MaxHealth`/`CurrentHealth` variables, `OnHealthChanged` and `OnDeath` event dispatchers. **The C++ stub has NO gameplay logic** — it exists so other C++ classes (`BPC_StateManager`, `BPC_DamageReceptionSystem`) can forward-reference it. **Create a BP child** and build ALL logic from this spec: `TakeDamage()`, `Heal()`, `OnDeath()` transition, damage resistance modifiers, health regen tick, `I_Damageable` interface. See `docs/developer/cpp-integration-guide.md` for setup steps.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
Manages the player's health pool, damage application with type-based resistance, health regeneration, death state, and critical-health events. Serves as the central survivability component on the player character.
|
Manages the player's health pool, damage application with type-based resistance, health regeneration, death state, and critical-health events. Serves as the central survivability component on the player character.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# 09 — Stamina System (`BPC_StaminaSystem`)
|
# 09 — Stamina System (`BPC_StaminaSystem`)
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Stub** — `Source/PG_Framework/Public/Player/BPC_StaminaSystem.h` provides the UCLASS shell, `MaxStamina`/`CurrentStamina` variables, and `OnExhaustionStateChanged` dispatcher. **The C++ stub has NO gameplay logic.** **Create a BP child** and build ALL logic: sprint drain, action costs, exhaustion state machine, regen delay + rate, `CanAffordAction(Cost)` query. See `docs/developer/cpp-integration-guide.md`.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
Manages the player's stamina pool — drain during sprinting, jumping, and special actions; regeneration during idle/walking. Prevents action spamming by enforcing minimum stamina thresholds. Drives exhaustion VFX and audio cues.
|
Manages the player's stamina pool — drain during sprinting, jumping, and special actions; regeneration during idle/walking. Prevents action spamming by enforcing minimum stamina thresholds. Drives exhaustion VFX and audio cues.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# 10 — Stress / Sanity System (`BPC_StressSystem`)
|
# 10 — Stress / Sanity System (`BPC_StressSystem`)
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Stub** — `Source/PG_Framework/Public/Player/BPC_StressSystem.h` provides the UCLASS shell, `EStressTier` enum (Calm→Catatonic), `StressTier` variable, and `OnStressTierChanged` dispatcher. **The C++ stub has NO gameplay logic.** **Create a BP child** and build ALL logic: stress accumulation sources, decay during safety, tier transitions, hallucination thresholds. See `docs/developer/cpp-integration-guide.md`.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
Tracks the player's psychological state via a stress meter that accumulates from environmental horror, enemy proximity, health deficits, and narrative events. Drives visual distortion, audio hallucinations, gameplay penalties, and narrative branching based on sanity thresholds.
|
Tracks the player's psychological state via a stress meter that accumulates from environmental horror, enemy proximity, health deficits, and narrative events. Drives visual distortion, audio hallucinations, gameplay penalties, and narrative branching based on sanity thresholds.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# 11 — Movement State System (`BPC_MovementStateSystem`)
|
# 11 — Movement State System (`BPC_MovementStateSystem`)
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Stub** — `Source/PG_Framework/Public/Player/BPC_MovementStateSystem.h` provides the UCLASS shell, `CurrentMovementMode` GameplayTag variable, and `OnMovementModeChanged` dispatcher. **The C++ stub has NO gameplay logic.** **Create a BP child** and build ALL logic: CMC reads, posture detection (standing/crouching/prone), sprint state, footstep events, GASP ABP variable bridge. See `docs/developer/cpp-integration-guide.md`.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
Tracks the player's current movement mode and posture state. Acts as a central query point for other systems (animations, stamina, audio, camera) to react to how the player is moving. Does not control movement input — only reports and reacts to state changes.
|
Tracks the player's current movement mode and posture state. Acts as a central query point for other systems (animations, stamina, audio, camera) to react to how the player is moving. Does not control movement input — only reports and reacts to state changes.
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,67 @@
|
|||||||
**Parent Class:** `Actor`
|
**Parent Class:** `Actor`
|
||||||
**Category:** Inventory
|
**Category:** Inventory
|
||||||
**Target UE Version:** 5.5–5.7
|
**Target UE Version:** 5.5–5.7
|
||||||
**Build Phase:** 3 — Inventory
|
**Build Phase:** 4 — Inventory
|
||||||
|
**C++ Status:** 🔵 BP-Only
|
||||||
|
|
||||||
|
> **Concrete step-by-step examples** of building specific item pickups (flashlight, pistol, medkit, keycard) with exact Blueprint graphs are in [`docs/game/`](../../game/README.md). The spec below defines the full architecture; the game examples show you exactly what nodes to connect.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Quick Setup — How to Place an Item in the World
|
||||||
|
|
||||||
|
This is the most common question: "I created a `DA_ItemData` but I can't drag it into the level." Here's the correct workflow:
|
||||||
|
|
||||||
|
### Step A — Create the Data Asset (one-time per item type)
|
||||||
|
|
||||||
|
```
|
||||||
|
Content Browser → Framework/DataAssets/Items/
|
||||||
|
Right-click → Miscellaneous → Data Asset
|
||||||
|
Class: DA_ItemData
|
||||||
|
Name: DA_Item_MedKit
|
||||||
|
Fill in: ItemTag, DisplayName, Icon, WorldMesh, Weight, StackLimit, ItemType
|
||||||
|
If ItemType=Consumable → fill ConsumableData (HealthRestore=25, UseDuration=2.0)
|
||||||
|
Save
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step B — Create the BP_ItemPickup Blueprint (one-time)
|
||||||
|
|
||||||
|
```
|
||||||
|
Content Browser → Framework/Inventory/
|
||||||
|
Right-click → Blueprint Class → Actor
|
||||||
|
Name: BP_ItemPickup
|
||||||
|
Implement I_Interactable interface
|
||||||
|
Add: StaticMeshComponent (named "Mesh"), SphereComponent (named "InteractionCollision")
|
||||||
|
Add: Config variable of type S_PickupConfig
|
||||||
|
In Construction Script: read Config.ItemData → set Mesh to ItemData.WorldMesh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step C — Place Items in Your Level (repeat per item instance)
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Drag BP_ItemPickup into the level
|
||||||
|
2. Select it → Details panel → Config → Item Data → DA_Item_MedKit
|
||||||
|
3. Set Quantity → 1 (or more for stacked items)
|
||||||
|
4. Position it where you want it in the world
|
||||||
|
```
|
||||||
|
|
||||||
|
The `BP_ItemPickup` reads the mesh, name, icon, and interaction prompt from the `DA_ItemData`. You never drag the Data Asset itself into the level — only the actor that references it.
|
||||||
|
|
||||||
|
```
|
||||||
|
DA_Item_MedKit (Content Browser) ← defines WHAT
|
||||||
|
↑ referenced by
|
||||||
|
BP_ItemPickup (Actor in level) ← physical body in world
|
||||||
|
↑ interacts via
|
||||||
|
Player → BPC_InteractionDetector → I_Interactable → BPC_InventorySystem.AddItem()
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Overview
|
## 1. Overview
|
||||||
|
|
||||||
`BP_ItemPickup` is a world-placed or runtime-spawned `Actor` representing an item the player can pick up. It implements [`I_Interactable`](docs/blueprints/01-core/03_I_InterfaceLibrary.md) and optionally [`I_Persistable`](docs/blueprints/01-core/03_I_InterfaceLibrary.md).
|
`BP_ItemPickup` is a world-placed or runtime-spawned `Actor` representing an item the player can pick up. It implements [`I_Interactable`](../01-core/03_I_InterfaceLibrary.md) and optionally [`I_Persistable`](../01-core/03_I_InterfaceLibrary.md).
|
||||||
|
|
||||||
When the player interacts (via [`BPC_InteractionDetector`](docs/blueprints/03-interaction/16_BPC_InteractionDetector.md)), the pickup calls `BPC_InventoryComponent.AddItem` with the associated item data, plays feedback, and either destroys itself or depletes a stack count.
|
When the player interacts (via [`BPC_InteractionDetector`](../03-interaction/16_BPC_InteractionDetector.md)), the pickup calls `BPC_InventorySystem.AddItem` with the associated item data, plays feedback, and either destroys itself or depletes a stack count.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -99,7 +151,7 @@ When the player interacts (via [`BPC_InteractionDetector`](docs/blueprints/03-in
|
|||||||
| Variable | Type | Description |
|
| Variable | Type | Description |
|
||||||
|----------|------|-------------|
|
|----------|------|-------------|
|
||||||
| `WorldState` | `S_PickupWorldState` | Tracks pickup history. |
|
| `WorldState` | `S_PickupWorldState` | Tracks pickup history. |
|
||||||
| `OwningInventory` | `BPC_InventoryComponent*` | Cached when player overlaps interaction trigger. |
|
| `OwningInventory` | `UBPC_InventorySystem*` | Cached when player overlaps interaction trigger. |
|
||||||
| `RespawnTimerHandle` | `FTimerHandle` | Handle for respawn delay. |
|
| `RespawnTimerHandle` | `FTimerHandle` | Handle for respawn delay. |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -110,7 +162,8 @@ When the player interacts (via [`BPC_InteractionDetector`](docs/blueprints/03-in
|
|||||||
|
|
||||||
| Function | Description |
|
| Function | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `Interact_Implementation` | Implements `I_Interactable`: adds item to player inventory, plays feedback, destroys or depletes. |
|
| `Public Functions` | Description |
|
||||||
|
| `Interact_Implementation` | Implements `I_Interactable`: calls `BPC_InventorySystem.AddItem()`, plays feedback, destroys or depletes. |
|
||||||
| `OnDroppedFromInventory` | Called by inventory drop system: sets transform, velocity, and respawn behaviour. |
|
| `OnDroppedFromInventory` | Called by inventory drop system: sets transform, velocity, and respawn behaviour. |
|
||||||
| `BeginPlay` | Caches references, sets up bobbing/rotation timeline, configures collision. |
|
| `BeginPlay` | Caches references, sets up bobbing/rotation timeline, configures collision. |
|
||||||
| `SetPickupEnabled` | Toggles visibility and collision. |
|
| `SetPickupEnabled` | Toggles visibility and collision. |
|
||||||
@@ -151,7 +204,7 @@ Event BeginPlay
|
|||||||
OnOverlapBegin (Player overlaps InteractionCollision)
|
OnOverlapBegin (Player overlaps InteractionCollision)
|
||||||
→ Set VisualState = Highlighted
|
→ Set VisualState = Highlighted
|
||||||
→ Show pickup prompt widget (if assigned)
|
→ Show pickup prompt widget (if assigned)
|
||||||
→ Cache player's BPC_InventoryComponent
|
→ Cache player's BPC_InventorySystem
|
||||||
|
|
||||||
OnOverlapEnd (Player leaves InteractionCollision)
|
OnOverlapEnd (Player leaves InteractionCollision)
|
||||||
→ Set VisualState = Idle
|
→ Set VisualState = Idle
|
||||||
@@ -163,7 +216,7 @@ Interact_Implementation (Called from BPC_InteractionDetector)
|
|||||||
→ Validate player inventory has space → if full, show "Inventory Full" feedback → return
|
→ Validate player inventory has space → if full, show "Inventory Full" feedback → return
|
||||||
→ Set VisualState = BeingPickedUp
|
→ Set VisualState = BeingPickedUp
|
||||||
→ Play pickup sound + particle effect
|
→ Play pickup sound + particle effect
|
||||||
→ Call BPC_InventoryComponent.AddItem(Config.ItemData, Config.Quantity)
|
→ Call BPC_InventorySystem.AddItem(Config.ItemData, Config.Quantity)
|
||||||
→ If AddItem succeeded:
|
→ If AddItem succeeded:
|
||||||
→ Broadcast OnPickupCollected(InteractingPlayer)
|
→ Broadcast OnPickupCollected(InteractingPlayer)
|
||||||
→ If bIsInfinite → do not destroy
|
→ If bIsInfinite → do not destroy
|
||||||
@@ -214,12 +267,12 @@ OnRespawnComplete
|
|||||||
|--------|--------------|
|
|--------|--------------|
|
||||||
| `I_Interactable` | Implements the interface for interaction detection. |
|
| `I_Interactable` | Implements the interface for interaction detection. |
|
||||||
| `BPC_InteractionDetector` | Sends overlap events and routes interaction call. |
|
| `BPC_InteractionDetector` | Sends overlap events and routes interaction call. |
|
||||||
| `BPC_InventoryComponent` | Target for item addition. |
|
| `BPC_InventorySystem` (31) | **Target for item addition.** C++ component — call `AddItem(ItemData, Quantity)`. |
|
||||||
| `DA_ItemData` | Supplies mesh, name, icon, stack limits. |
|
| `DA_ItemData` (07) | Supplies mesh, name, icon, stack limits, weight. |
|
||||||
| `BPC_PlayerMetricsTracker` | Logs pickup event (item type, quantity, location). |
|
| `BPC_PlayerMetricsTracker` (15) | Logs pickup event (item type, quantity, location). |
|
||||||
| `I_Persistable` | Optional: saves pickup state (available, transform, quantity). |
|
| `I_Persistable` | Optional: saves pickup state (available, transform, quantity). |
|
||||||
| `BPC_InventoryWeightSystem` | Checks weight capacity before allowing pickup. |
|
| `BPC_KeyItemSystem` (34) | Checks `bIsKeyItem` — key items auto-protect from drop/clear. |
|
||||||
| `BPC_AdaptiveDifficulty` | May adjust item availability based on difficulty. |
|
| `SS_AchievementSystem` (103) | Notified on collectible pickups for completion tracking. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -248,7 +301,7 @@ Player approaches world item → Overlap event
|
|||||||
Player presses Interact key
|
Player presses Interact key
|
||||||
→ BPC_InteractionDetector.InteractTarget → BP_ItemPickup.Interact_Implementation
|
→ BPC_InteractionDetector.InteractTarget → BP_ItemPickup.Interact_Implementation
|
||||||
→ Validate availability + inventory space
|
→ Validate availability + inventory space
|
||||||
→ BPC_InventoryComponent.AddItem (ItemData, Quantity)
|
→ BPC_InventorySystem.AddItem (ItemData, Quantity)
|
||||||
→ Play pickup sound + particle
|
→ Play pickup sound + particle
|
||||||
→ Broadcast OnPickupCollected
|
→ Broadcast OnPickupCollected
|
||||||
→ If bRespawns → StartRespawn (hide, wait, reappear)
|
→ If bRespawns → StartRespawn (hide, wait, reappear)
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
**Parent Class:** `ActorComponent`
|
**Parent Class:** `ActorComponent`
|
||||||
**Category:** Inventory
|
**Category:** Inventory
|
||||||
**Target UE Version:** 5.5–5.7
|
**Target UE Version:** 5.5–5.7
|
||||||
**Build Phase:** 3 — Inventory
|
**Build Phase:** 4 — Inventory
|
||||||
|
|
||||||
---
|
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Inventory/BPC_InventorySystem.h` provides the complete inventory grid: add/remove/query/sort/consolidate/weight tracking with native-speed TArray operations. **Attach directly to player pawn** (Add Component → `BPC_InventorySystem`). Set `GridWidth`, `GridHeight`, `MaxWeight` in Details panel. Do NOT create a BP child — the C++ component has all logic. See `docs/developer/cpp-integration-guide.md` for usage patterns.
|
||||||
|
>
|
||||||
## 1. Overview
|
> ---## 1. Overview
|
||||||
|
|
||||||
`BPC_InventorySystem` is the central inventory authority on the player. It manages an array of item instances (stackable, tagged, data-driven), handles add/remove/use/transfer operations, enforces capacity limits, and communicates state changes via Event Dispatchers. All other inventory subsystems (weight, quick slots, UI, equipment) read from this component as their single source of truth.
|
`BPC_InventorySystem` is the central inventory authority on the player. It manages an array of item instances (stackable, tagged, data-driven), handles add/remove/use/transfer operations, enforces capacity limits, and communicates state changes via Event Dispatchers. All other inventory subsystems (weight, quick slots, UI, equipment) read from this component as their single source of truth.
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
**File:** [`Content/Framework/Save/SS_SaveManager`](Content/Framework/Save/SS_SaveManager.uasset)
|
**File:** [`Content/Framework/Save/SS_SaveManager`](Content/Framework/Save/SS_SaveManager.uasset)
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Save/SS_SaveManager.h` provides the complete save/load subsystem: slot manifest, save/load/delete, quick-save/load, checkpoint management, backup, FArchive binary serialization. **Auto-created** by UE's subsystem system when `GI_GameFramework` initializes — no BP child, no spawning needed. Access from any BP: `Get Game Instance → Get Subsystem(SS_SaveManager)`. See `docs/developer/cpp-integration-guide.md`.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
**Purpose:** The single authority for all serialisation and deserialisation of game state to disk. Supports multiple save slots, checkpoint saves, hard saves, auto-saves, and world object persistence.
|
**Purpose:** The single authority for all serialisation and deserialisation of game state to disk. Supports multiple save slots, checkpoint saves, hard saves, auto-saves, and world object persistence.
|
||||||
|
|
||||||
**Depends On:** [`GI_GameFramework`](../01-core/04_GI_GameFramework.md), [`I_Persistable`](30_I_Persistable.md)
|
**Depends On:** [`GI_GameFramework`](../01-core/04_GI_GameFramework.md), [`I_Persistable`](30_I_Persistable.md)
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
**File:** [`Content/Framework/Save/I_Persistable`](Content/Framework/Save/I_Persistable.uasset)
|
**File:** [`Content/Framework/Save/I_Persistable`](Content/Framework/Save/I_Persistable.uasset)
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Full Implementation** — Defined in `Source/PG_Framework/Public/Core/I_InterfaceLibrary.h` as `BlueprintNativeEvent`. **Do NOT create a Blueprint Interface asset.** On any actor that needs save/load: Class Settings → Interfaces → Add → `UPersistable`. Override `On Save`, `On Load`, `Get Save Tag`, `Needs Save`. See `docs/developer/cpp-integration-guide.md`.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
**Purpose:** Interface for any Actor or Component in the world that needs to save or restore per-instance state. Implemented by doors, containers, lootable objects, hidden collectibles, puzzle elements, destructibles, and any other persistent world actor.
|
**Purpose:** Interface for any Actor or Component in the world that needs to save or restore per-instance state. Implemented by doors, containers, lootable objects, hidden collectibles, puzzle elements, destructibles, and any other persistent world actor.
|
||||||
|
|
||||||
**Depends On:** None
|
**Depends On:** None
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
|
|
||||||
**Parent:** `UUserWidget`
|
**Parent:** `UUserWidget`
|
||||||
**Used by:** `SS_UIManager.PushMenu("SettingsMenu")`
|
**Used by:** `SS_UIManager.PushMenu("SettingsMenu")`
|
||||||
**Depends On:** `SS_UIManager`, `SS_SettingsSystem`
|
**Depends On:** `SS_UIManager`, `SS_SettingsSystem`, `BPC_RenderPipelineManager`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Purpose
|
### Purpose
|
||||||
Full settings screen with sections: Audio, Video/Graphics, Gameplay, Controls, Accessibility.
|
Full settings screen with tabs: Video/Graphics, Audio, Gameplay, Controls, Accessibility. The Video tab includes quality presets with platform-aware render pipeline options, upscaling configuration, individual feature toggles, and visual warnings for settings that require a level reload.
|
||||||
|
|
||||||
### Enums (local or global)
|
### Enums (local or global)
|
||||||
|
|
||||||
```cpp
|
```
|
||||||
E_SettingsTab
|
E_SettingsTab
|
||||||
{
|
{
|
||||||
Audio,
|
|
||||||
Video,
|
Video,
|
||||||
|
Audio,
|
||||||
Gameplay,
|
Gameplay,
|
||||||
Controls,
|
Controls,
|
||||||
Accessibility
|
Accessibility
|
||||||
@@ -29,75 +29,215 @@ E_SettingsTab
|
|||||||
| `ActiveTab` | `E_SettingsTab` | Currently visible tab |
|
| `ActiveTab` | `E_SettingsTab` | Currently visible tab |
|
||||||
| `TabButtons` | Array of `Button` | Tab navigation buttons |
|
| `TabButtons` | Array of `Button` | Tab navigation buttons |
|
||||||
| `TabPanels` | Array of `PanelWidget` | Tab content panels |
|
| `TabPanels` | Array of `PanelWidget` | Tab content panels |
|
||||||
|
| `ReloadWarningBanner` | `Border` | Warning banner for reload-required settings |
|
||||||
|
|
||||||
**Audio Tab Children:**
|
---
|
||||||
|
|
||||||
|
### Video Tab Children (EXPANDED — was 5, now 25+)
|
||||||
|
|
||||||
|
**Quality Preset:**
|
||||||
|
| Name | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| `QualityPresetDropdown` | `ComboBoxString` | Low, Medium, High, Ultra, Cinematic, Custom |
|
||||||
|
| `PresetDescriptionText` | `RichTextBlock` | Describes what each preset changes |
|
||||||
|
| `ReloadWarningIcon` | `Image` | ⚠ icon visible when preset change requires reload |
|
||||||
|
|
||||||
|
**Display:**
|
||||||
|
| Name | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| `ResolutionScaleSlider` | `Slider` | 25%–200% screen percentage |
|
||||||
|
| `ResolutionScaleValue` | `TextBlock` | Shows current % |
|
||||||
|
| `WindowModeDropdown` | `ComboBoxString` | Fullscreen, Windowed Fullscreen, Windowed |
|
||||||
|
| `VSyncToggle` | `CheckBox` | On/Off |
|
||||||
|
| `FrameRateLimitSlider` | `Slider` | 30–240 FPS |
|
||||||
|
| `BrightnessSlider` | `Slider` | 0.5–2.0 gamma |
|
||||||
|
| `HDRToggle` | `CheckBox` | HDR output (if display supports) |
|
||||||
|
|
||||||
|
**Render Pipeline (⚠ reload required section):**
|
||||||
|
| Name | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| `GlobalIlluminationDropdown` | `ComboBoxString` | Lumen, Baked Lightmass, SSGI, Off |
|
||||||
|
| `ShadowMethodDropdown` | `ComboBoxString` | Virtual Shadow Maps, Cascaded, Distance Field, Off |
|
||||||
|
| `ReflectionMethodDropdown` | `ComboBoxString` | Lumen, Screen Space, Captures, Off |
|
||||||
|
| `MeshStrategyDropdown` | `ComboBoxString` | Nanite, Traditional LOD, Proxy Geometry |
|
||||||
|
| `PipelineReloadBanner` | `Border` | "These settings require a level transition to apply" |
|
||||||
|
|
||||||
|
**Upscaling:**
|
||||||
|
| Name | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| `UpscalerMethodDropdown` | `ComboBoxString` | TSR, DLSS, FSR, PSSR, XeSS, NIS, TAAU, Off |
|
||||||
|
| `UpscalerQualityDropdown` | `ComboBoxString` | Ultra Performance, Performance, Balanced, Quality, Ultra Quality |
|
||||||
|
| `DynamicResolutionToggle` | `CheckBox` | Enable dynamic resolution scaling |
|
||||||
|
| `DynamicResTargetSlider` | `Slider` | Target FPS for dynamic resolution |
|
||||||
|
|
||||||
|
**Scalability Sliders:**
|
||||||
|
| Name | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| `ShadowQualitySlider` | `Slider` | 0=NONE, 1=LOW, 2=MED, 3=HIGH, 4=EPIC |
|
||||||
|
| `TextureQualitySlider` | `Slider` | 0–3 |
|
||||||
|
| `PostProcessQualitySlider` | `Slider` | 0–3 |
|
||||||
|
| `ViewDistanceQualitySlider` | `Slider` | 0–3 |
|
||||||
|
| `FoliageQualitySlider` | `Slider` | 0–3 |
|
||||||
|
| `AntiAliasingQualitySlider` | `Slider` | 0–3 |
|
||||||
|
|
||||||
|
**Post-Process Toggles:**
|
||||||
|
| Name | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| `MotionBlurToggle` | `CheckBox` | On/Off |
|
||||||
|
| `DepthOfFieldToggle` | `CheckBox` | On/Off |
|
||||||
|
| `VolumetricCloudsToggle` | `CheckBox` | On/Off |
|
||||||
|
| `HWRTToggle` | `CheckBox` | Hardware Ray Tracing (⚠ reload) — only visible on supported platforms |
|
||||||
|
|
||||||
|
**Texture:**
|
||||||
|
| Name | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| `TexturePoolSizeSlider` | `Slider` | 512–8192 MB |
|
||||||
|
|
||||||
|
**Actions:**
|
||||||
|
| Name | Type | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| `ApplyButton` | `Button` | Apply all pending changes |
|
||||||
|
| `RevertButton` | `Button` | Revert to last applied state |
|
||||||
|
| `ResetDefaultsButton` | `Button` | Reset all video settings to platform defaults |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Audio Tab Children
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
|------|------|-------------|
|
|------|------|-------------|
|
||||||
| `MasterVolumeSlider` | `Slider` | 0-100 |
|
| `MasterVolumeSlider` | `Slider` | 0–100 |
|
||||||
| `SFXVolumeSlider` | `Slider` | 0-100 |
|
| `SFXVolumeSlider` | `Slider` | 0–100 |
|
||||||
| `MusicVolumeSlider` | `Slider` | 0-100 |
|
| `MusicVolumeSlider` | `Slider` | 0–100 |
|
||||||
| `VoiceVolumeSlider` | `Slider` | 0-100 |
|
| `VoiceVolumeSlider` | `Slider` | 0–100 |
|
||||||
|
| `AmbientVolumeSlider` | `Slider` | 0–100 |
|
||||||
|
| `UIVolumeSlider` | `Slider` | 0–100 |
|
||||||
|
| `SpatialAudioToggle` | `CheckBox` | On/Off |
|
||||||
|
| `AudioQualityDropdown` | `ComboBoxString` | Low, Medium, High |
|
||||||
| `SubtitlesToggle` | `CheckBox` | On/Off |
|
| `SubtitlesToggle` | `CheckBox` | On/Off |
|
||||||
|
|
||||||
**Video Tab Children:**
|
### Gameplay Tab Children
|
||||||
|
|
||||||
| Name | Type | Description |
|
|
||||||
|------|------|-------------|
|
|
||||||
| `ResolutionDropdown` | `ComboBoxString` | Available resolutions |
|
|
||||||
| `WindowModeDropdown` | `ComboBoxString` | Fullscreen, Windowed, Borderless |
|
|
||||||
| `VSyncToggle` | `CheckBox` | On/Off |
|
|
||||||
| `QualityPresetDropdown` | `ComboBoxString` | Low, Medium, High, Epic |
|
|
||||||
| `BrightnessSlider` | `Slider` | 0.5-2.0 gamma |
|
|
||||||
|
|
||||||
**Gameplay Tab Children:**
|
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
|------|------|-------------|
|
|------|------|-------------|
|
||||||
| `InvertYAxisToggle` | `CheckBox` | On/Off |
|
| `InvertYAxisToggle` | `CheckBox` | On/Off |
|
||||||
| `SensitivitySlider` | `Slider` | Mouse sensitivity 0.1-5.0 |
|
| `SensitivityXSlider` | `Slider` | Mouse sensitivity X |
|
||||||
| `HoldDurationToggle` | `CheckBox` | Tap vs Hold for interactions |
|
| `SensitivityYSlider` | `Slider` | Mouse sensitivity Y |
|
||||||
| `AutoPickupToggle` | `CheckBox` | On/Off |
|
| `FOVSlider` | `Slider` | 70–120 degrees |
|
||||||
| `CrosshairToggle` | `CheckBox` | Show crosshair |
|
| `HeadBobToggle` | `CheckBox` | Camera head bob |
|
||||||
|
| `AutoPickupToggle` | `CheckBox` | Auto-pickup items |
|
||||||
|
| `CrosshairTypeDropdown` | `ComboBoxString` | Default, Dot, Cross, Off |
|
||||||
|
|
||||||
**Controls Tab Children:**
|
### Controls Tab Children
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
|------|------|-------------|
|
|------|------|-------------|
|
||||||
| `ActionMappingsList` | `ListView` | Key binding rows |
|
| `ActionMappingsList` | `ListView` | Key binding rows |
|
||||||
| `ResetDefaultsButton` | `Button` | Reset all bindings |
|
| `ResetDefaultsButton` | `Button` | Reset all bindings |
|
||||||
|
|
||||||
**Accessibility Tab Children:**
|
### Accessibility Tab Children
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
|------|------|-------------|
|
|------|------|-------------|
|
||||||
| `SubtitleSizeDropdown` | `ComboBoxString` | Small, Medium, Large |
|
| `SubtitleSizeDropdown` | `ComboBoxString` | Small, Medium, Large |
|
||||||
| `SubtitleBackgroundOpacity` | `Slider` | 0-100% |
|
| `SubtitleBackgroundOpacity` | `Slider` | 0–100% |
|
||||||
| `ColorblindModeDropdown` | `ComboBoxString` | None, Protanopia, Deuteranopia, Tritanopia |
|
| `ColorblindModeDropdown` | `ComboBoxString` | None, Protanopia, Deuteranopia, Tritanopia |
|
||||||
| `HighContrastUIToggle` | `CheckBox` | On/Off |
|
| `HighContrastUIToggle` | `CheckBox` | On/Off |
|
||||||
| `CameraShakeIntensity` | `Slider` | 0-100% |
|
| `CameraShakeIntensity` | `Slider` | 0–100% |
|
||||||
| `MotionBlurToggle` | `CheckBox` | On/Off |
|
| `ScreenShakeToggle` | `CheckBox` | Enable/disable screen shake |
|
||||||
|
| `ReducedMotionToggle` | `CheckBox` | Reduce UI animations |
|
||||||
|
| `LargeTextToggle` | `CheckBox` | Enlarge all UI text |
|
||||||
|
| `TextToSpeechToggle` | `CheckBox` | TTS for UI elements |
|
||||||
|
| `DisableFlashToggle` | `CheckBox` | Prevent strobe effects |
|
||||||
|
| `HapticsEnabledToggle` | `CheckBox` | Enable controller vibration |
|
||||||
|
| `HapticsIntensitySlider` | `Slider` | Vibration intensity 0–100% |
|
||||||
|
| `AdaptiveTriggersToggle` | `CheckBox` | PS5 DualSense adaptive triggers |
|
||||||
|
| `ControllerSpeakerToggle` | `CheckBox` | PS5 controller speaker |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Functions
|
### Functions
|
||||||
|
|
||||||
| Name | Inputs | Outputs | Description |
|
| Name | Inputs | Outputs | Description |
|
||||||
|------|--------|---------|-------------|
|
|------|--------|---------|-------------|
|
||||||
| `OnConstruct` | — | — | Populate dropdowns from system, load saved settings |
|
| `OnConstruct` | — | — | Populate dropdowns, load saved settings, query available upscalers |
|
||||||
| `SwitchTab` | Tab: `E_SettingsTab` | — | Show selected panel, hide others |
|
| `SwitchTab` | Tab: `E_SettingsTab` | — | Show selected panel, hide others |
|
||||||
|
| `OnQualityPresetChanged` | NewPreset: Name | — | Update all controls to reflect preset, show reload warning if needed |
|
||||||
|
| `OnPipelineSettingChanged` | SettingName: Name | — | Show reload banner if setting requires level reload |
|
||||||
| `OnSettingChanged` | SettingName: FName, Value: Generic | — | Write to SS_SettingsSystem |
|
| `OnSettingChanged` | SettingName: FName, Value: Generic | — | Write to SS_SettingsSystem |
|
||||||
| `SaveSettings` | — | — | SS_SettingsSystem.SaveToDisk |
|
| `SaveSettings` | — | — | SS_SettingsSystem.SaveToDisk |
|
||||||
| `ApplyVideoSettings` | — | — | Apply resolution, window mode, quality |
|
| `ApplyVideoSettings` | — | — | SS_SettingsSystem.ApplyGraphicsSettings → BPC_RenderPipelineManager |
|
||||||
| `ResetToDefaults` | — | — | Load default values, re-populate UI |
|
| `ResetToDefaults` | — | — | Load platform defaults, re-populate UI |
|
||||||
| `OnBack` | — | — | SaveSettings, SS_UIManager.PopMenu |
|
| `OnBack` | — | — | SaveSettings, SS_UIManager.PopMenu |
|
||||||
| `RebindKey` | ActionName: FName | — | Listen for next key press, map to action |
|
| `RebindKey` | ActionName: FName | — | Listen for next key press, map to action |
|
||||||
|
| `QueryAvailableUpscalers` | — | TArray<E_UpscalerMethod> | Calls BPC_RenderPipelineManager.GetAvailableUpscalers() |
|
||||||
|
| `ShowReloadWarning` | — | — | Display "⚠ Some settings will apply on next level load" banner |
|
||||||
|
| `HideReloadWarning` | — | — | Dismiss the reload warning banner |
|
||||||
|
| `UpdatePresetDescription` | Preset: Name | — | Show FPS target, resolution, GI method for preset |
|
||||||
|
| `OnHoverSetting` | SettingName: Name | — | Show tooltip explaining what this setting does |
|
||||||
|
| `RevertToApplied` | — | — | Revert UI controls to last-applied state |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Quality Preset Descriptions (shown in `PresetDescriptionText`)
|
||||||
|
|
||||||
|
| Preset | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| **Low** | Performance mode. Baked lighting, CSM shadows, LOD meshes. Target: 60 FPS on consoles, 120 FPS on PC. |
|
||||||
|
| **Medium** | Balanced. Lumen Low GI, VSM shadows, Nanite enabled, TSR upscaling. Target: 60 FPS. |
|
||||||
|
| **High** | Quality mode. Full Lumen GI, high-quality VSM, Nanite, DLSS/FSR Quality upscaling. Target: 60 FPS. |
|
||||||
|
| **Ultra** | Maximum fidelity. Lumen Ultra, full VSM, Nanite, DLSS/PSSR upscaling. Target: 60 FPS (30 on base consoles). |
|
||||||
|
| **Cinematic** | Cinematic quality. Lumen + HWRT, path tracing available, highest upscaling quality. Target: 30 FPS. |
|
||||||
|
| **Custom** | Manually configured settings. Performance impact varies. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Communications With
|
### Communications With
|
||||||
|
|
||||||
| Target System | Method | Why |
|
| Target System | Method | Why |
|
||||||
|--------------|--------|-----|
|
|--------------|--------|-----|
|
||||||
| [`SS_UIManager`](44_SS_UIManager.md) | Push/Pop | Menu lifecycle |
|
| [`SS_UIManager`](44_SS_UIManager.md) | Push/Pop | Menu lifecycle |
|
||||||
| `SS_SettingsSystem` | Function calls | Read/write settings |
|
| `SS_SettingsSystem` | Function calls | Read/write all settings |
|
||||||
|
| `BPC_RenderPipelineManager` (149) | Function calls | Query available upscalers, check reload requirements |
|
||||||
|
|
||||||
|
### Blueprint Flow — Video Tab
|
||||||
|
|
||||||
|
```
|
||||||
|
[OnConstruct]
|
||||||
|
├─ Populate QualityPresetDropdown
|
||||||
|
├─ Read SS_SettingsSystem.GetSettingFloat("QualityPreset") → set dropdown
|
||||||
|
├─ Call BPC_RenderPipelineManager.GetAvailableUpscalers() → populate upscaler dropdown
|
||||||
|
│ └─ Filter: only show upscalers supported on this platform
|
||||||
|
├─ Read all saved settings from SS_SettingsSystem → populate sliders/toggles
|
||||||
|
└─ Call UpdatePresetDescription based on active preset
|
||||||
|
|
||||||
|
[OnQualityPresetChanged(NewPreset)]
|
||||||
|
├─ Call BPC_RenderPipelineManager.RequiresReloadForPresetChange(NewPreset)
|
||||||
|
├─ If TRUE: ShowReloadWarning()
|
||||||
|
├─ If FALSE: HideReloadWarning()
|
||||||
|
├─ Set all sliders/toggles to match the preset defaults
|
||||||
|
├─ UpdatePresetDescription(NewPreset)
|
||||||
|
└─ SS_SettingsSystem.SetSettingDropdown("QualityPreset", NewPreset)
|
||||||
|
|
||||||
|
[OnPipelineSettingChanged(SettingName)]
|
||||||
|
├─ Check if setting is in "reload required" list:
|
||||||
|
│ [GlobalIllumination, ShadowMethod, ReflectionMethod, MeshStrategy, HWRT]
|
||||||
|
├─ If changed from current: ShowReloadWarning()
|
||||||
|
└─ Else: HideReloadWarning()
|
||||||
|
|
||||||
|
[ApplyVideoSettings]
|
||||||
|
├─ SS_SettingsSystem.ApplyGraphicsSettings()
|
||||||
|
├─ If reload warning visible AND game is in session:
|
||||||
|
│ └─ SS_SettingsSystem.SaveSettings()
|
||||||
|
│ └─ Show notification: "Settings applied. Render pipeline changes will take effect on next level load."
|
||||||
|
├─ Else if in Main Menu:
|
||||||
|
│ └─ Apply immediately (no reload needed for menu)
|
||||||
|
└─ HideReloadWarning()
|
||||||
|
```
|
||||||
|
|
||||||
### Reuse Notes
|
### Reuse Notes
|
||||||
- The tab panel pattern can be extended for mod settings or developer menus by adding new tab entries
|
- The tab panel pattern can be extended for mod settings or developer menus by adding new tab entries
|
||||||
- Split from original bundled `36_WBP_MenuWidgets.md` per Clean Slate refactoring plan
|
- All controls marked `⚠ reload` call `OnPipelineSettingChanged` to show the warning banner
|
||||||
|
- The Video tab queries `BPC_RenderPipelineManager` at construction to know which upscalers are available
|
||||||
|
- Preset selection acts as a "quick-config" — sets all sliders/toggles to preset defaults, then user can fine-tune
|
||||||
|
- When Custom preset is selected, all individual controls are enabled; other presets lock controls to their defaults
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# BPC_DamageReceptionSystem — Damage Reception System
|
# BPC_DamageReceptionSystem — Damage Reception System
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Weapons/BPC_DamageReceptionSystem.h` provides the complete damage pipeline: raw damage → resistance → armor → shield → health → hit reaction. **Attach directly to player/enemy pawns** (Add Component → `BPC_DamageReceptionSystem`). Call `ApplyDamage(RawDamage, Causer, DamageType, HitLocation, HitDirection)`. Set `StaggerThreshold`/`KnockdownThreshold` in Details panel. Do NOT create a BP child. See `docs/developer/cpp-integration-guide.md`.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Blueprint Spec — UE 5.5–5.7
|
## Blueprint Spec — UE 5.5–5.7
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# BPC_HitReactionSystem — Hit Reaction System
|
# BPC_HitReactionSystem — Hit Reaction System
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Stub** — `Source/PG_Framework/Public/Weapons/BPC_HitReactionSystem.h` provides the UCLASS shell, `PlayHitReaction()` stub, `FlinchThreshold`, and `RagdollThreshold`. **The C++ stub has NO gameplay logic.** **Create a BP child** and build ALL logic: hit reaction animation selection, stagger/knockdown/ragdoll trigger, directional response, camera trauma. Attach to player/enemy pawns. See `docs/developer/cpp-integration-guide.md`.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Blueprint Spec — UE 5.5–5.7
|
## Blueprint Spec — UE 5.5–5.7
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# BPC_ShieldDefenseSystem — Actor Component
|
# BPC_ShieldDefenseSystem — Actor Component
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Stub** — `Source/PG_Framework/Public/Weapons/BPC_ShieldDefenseSystem.h` provides the UCLASS shell, `ShieldHealth`, `MaxShieldHealth`, `BlockAngle`, `bShieldBroken`. **The C++ stub has NO gameplay logic.** **Create a BP child** and build ALL logic: shield raise/lower, block detection, damage reduction while blocking, break effect, stamina drain. Attach to player pawn. See `docs/developer/cpp-integration-guide.md`.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
**File:** [`Content/Framework/Weapons/BPC_ShieldDefenseSystem`](Content/Framework/Weapons/BPC_ShieldDefenseSystem.uasset)
|
**File:** [`Content/Framework/Weapons/BPC_ShieldDefenseSystem`](Content/Framework/Weapons/BPC_ShieldDefenseSystem.uasset)
|
||||||
**Parent Class:** `UActorComponent`
|
**Parent Class:** `UActorComponent`
|
||||||
**Dependencies:** [`BPC_DamageReceptionSystem`](BPC_DamageReceptionSystem.md), [`BPC_StaminaSystem`](../02-player/09_BPC_StaminaSystem.md)
|
**Dependencies:** [`BPC_DamageReceptionSystem`](BPC_DamageReceptionSystem.md), [`BPC_StaminaSystem`](../02-player/09_BPC_StaminaSystem.md)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
`ActorComponent`
|
`ActorComponent`
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
- [`BPC_RenderPipelineManager`](../12-settings/149_BPC_RenderPipelineManager.md) — **Delegates all CVar application** (GI, shadows, upscaling, Nanite, quality tiers)
|
||||||
|
- [`DA_RenderPipelineProfile`](../14-data-assets/DA_RenderPipelineProfile.md) — Reads render config per platform + quality tier
|
||||||
- [`BPC_LightingManager`](65_BPC_LightingManager.md) — Controls light quality
|
- [`BPC_LightingManager`](65_BPC_LightingManager.md) — Controls light quality
|
||||||
- [`BPC_AudioManager`](66_BPC_AudioManager.md) — Controls audio quality
|
- [`BPC_AudioManager`](66_BPC_AudioManager.md) — Controls audio quality
|
||||||
- [`BPC_VFXManager`](67_BPC_VFXManager.md) — Controls particle LOD
|
- [`BPC_VFXManager`](67_BPC_VFXManager.md) — Controls particle LOD
|
||||||
@@ -16,7 +18,7 @@
|
|||||||
- [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) — Frame time measurement
|
- [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) — Frame time measurement
|
||||||
|
|
||||||
### Purpose
|
### Purpose
|
||||||
Automatically adjusts graphics, audio, and gameplay quality settings based on real-time performance metrics (frame time, draw calls, memory usage). Provides a unified interface for all other systems to scale their quality without direct awareness of the hardware. Supports manual user override via Settings menu and adaptive automatic mode. Maintains a performance budget that is dynamically allocated across subsystems.
|
Automatically adjusts rendering, audio, and gameplay quality settings based on real-time performance metrics (frame time, draw calls, memory usage). **Delegates all render pipeline CVar changes to `BPC_RenderPipelineManager`** — this component focuses on monitoring performance and deciding WHEN to change quality tiers. The RenderPipelineManager handles the HOW (which CVars to execute, whether a reload is needed). Supports manual user override via Settings menu and adaptive automatic mode. Maintains a performance budget that is dynamically allocated across subsystems.
|
||||||
|
|
||||||
### Enums
|
### Enums
|
||||||
|
|
||||||
@@ -128,13 +130,15 @@ Automatically adjusts graphics, audio, and gameplay quality settings based on re
|
|||||||
└─► If CurrentQualityLevel == Level: return
|
└─► If CurrentQualityLevel == Level: return
|
||||||
└─► TargetQualityLevel = Level
|
└─► TargetQualityLevel = Level
|
||||||
└─► bIsScalingInProgress = true
|
└─► bIsScalingInProgress = true
|
||||||
└─► ApplyScalerSettings(Level)
|
└─► Delegate to BPC_RenderPipelineManager.ApplyQualityPreset(PresetName)
|
||||||
└─► For each subsystem:
|
→ RenderPipelineManager handles ALL CVar application
|
||||||
BPC_LightingManager: SetParticleLOD mapped to QualityLevel
|
→ Reads DA_RenderPipelineProfile for current platform
|
||||||
|
→ Returns whether reload is required
|
||||||
|
└─► For each non-render subsystem:
|
||||||
|
BPC_LightingManager: Reduce dynamic light count
|
||||||
BPC_AudioManager: Reduce active layers and spatial audio
|
BPC_AudioManager: Reduce active layers and spatial audio
|
||||||
BPC_VFXManager: SetParticleLOD mapped to QualityLevel
|
BPC_VFXManager: SetParticleLOD mapped to QualityLevel
|
||||||
BPC_AtmosphereController: Reduce preset complexity
|
BPC_AtmosphereController: Reduce preset complexity
|
||||||
└─► Apply UE console variables for resolution, shadows, textures, foliage
|
|
||||||
└─► bIsScalingInProgress = false
|
└─► bIsScalingInProgress = false
|
||||||
└─► CurrentQualityLevel = TargetQualityLevel
|
└─► CurrentQualityLevel = TargetQualityLevel
|
||||||
└─► OnQualityLevelChanged.Broadcast(CurrentQualityLevel)
|
└─► OnQualityLevelChanged.Broadcast(CurrentQualityLevel)
|
||||||
@@ -215,6 +219,7 @@ Automatically adjusts graphics, audio, and gameplay quality settings based on re
|
|||||||
|
|
||||||
| Target | Method | Why |
|
| Target | Method | Why |
|
||||||
|--------|--------|-----|
|
|--------|--------|-----|
|
||||||
|
| [`BPC_RenderPipelineManager`](../12-settings/149_BPC_RenderPipelineManager.md) | Direct call | **All render CVar application** — delegates quality tier to pipeline manager |
|
||||||
| [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) | Direct call | Frame time data source |
|
| [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) | Direct call | Frame time data source |
|
||||||
| [`SS_SaveManager`](../05-saveload/28_SS_SaveManager.md) | Direct call | Save/load settings |
|
| [`SS_SaveManager`](../05-saveload/28_SS_SaveManager.md) | Direct call | Save/load settings |
|
||||||
| [`BPC_LightingManager`](65_BPC_LightingManager.md) | Get from player | Light quality reduction |
|
| [`BPC_LightingManager`](65_BPC_LightingManager.md) | Get from player | Light quality reduction |
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
- **Requires:** [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) — Player stats source
|
- **Requires:** [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) — Player stats source
|
||||||
- **Requires:** [`BPC_NarrativeStateSystem`](../07-narrative/38_BPC_NarrativeStateSystem.md) — Story progression
|
- **Requires:** [`BPC_NarrativeStateSystem`](../07-narrative/38_BPC_NarrativeStateSystem.md) — Story progression
|
||||||
- **Required By:** [`WBP_NotificationToast`](../06-ui/WBP_NotificationToast.md) — UI notification
|
- **Required By:** [`WBP_NotificationToast`](../06-ui/WBP_NotificationToast.md) — UI notification
|
||||||
- **Required By:** [`BPC_PlatformServiceAbstraction`](../12-settings/BPC_PlatformServiceAbstraction.md) — Platform API submission
|
- **Required By:** [`BPC_PlatformServiceAbstraction`](../01-core/150_BPC_PlatformServiceAbstraction.md) — Platform API routing (Steam/PSN/Xbox/Nintendo)
|
||||||
- **Engine/Plugin Requirements:** GameplayTags
|
- **Engine/Plugin Requirements:** GameplayTags, Platform SDK plugins (Steamworks, PSN, Xbox GDK, Nintendo SDK — all optional)
|
||||||
|
|
||||||
### Purpose
|
### Purpose
|
||||||
Platform-agnostic achievement and trophy tracking system. Manages achievement definitions, progress tracking, unlock logic, and platform API submission. Supports hidden achievements, progress-based achievements, and cross-save achievement state persistence.
|
Platform-agnostic achievement and trophy tracking system. Manages achievement definitions, progress tracking, unlock logic, and platform API submission. Supports hidden achievements, progress-based achievements, and cross-save achievement state persistence.
|
||||||
@@ -118,7 +118,7 @@ Platform-agnostic achievement and trophy tracking system. Manages achievement de
|
|||||||
| [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) | Direct read | Stats for achievements |
|
| [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) | Direct read | Stats for achievements |
|
||||||
| [`BPC_NarrativeStateSystem`](../07-narrative/38_BPC_NarrativeStateSystem.md) | Direct read | Story achievements |
|
| [`BPC_NarrativeStateSystem`](../07-narrative/38_BPC_NarrativeStateSystem.md) | Direct read | Story achievements |
|
||||||
| [`WBP_NotificationToast`](../06-ui/WBP_NotificationToast.md) | Create widget | Toast display |
|
| [`WBP_NotificationToast`](../06-ui/WBP_NotificationToast.md) | Create widget | Toast display |
|
||||||
| [`BPC_PlatformServiceAbstraction`](../12-settings/BPC_PlatformServiceAbstraction.md) | Direct call | Platform submission |
|
| [`BPC_PlatformServiceAbstraction`](../01-core/150_BPC_PlatformServiceAbstraction.md) | Direct call | Platform SDK routing (Steam/PSN/Xbox/Nintendo) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
- [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) — Game instance reference
|
- [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) — Game instance reference
|
||||||
- [`BPC_PerformanceScaler`](../10-adaptive/69_BPC_PerformanceScaler.md) — Quality settings bridge
|
- [`BPC_RenderPipelineManager`](149_BPC_RenderPipelineManager.md) — Quality pipeline bridge (NEW)
|
||||||
- [`SS_SaveManager`](../05-saveload/28_SS_SaveManager.md) — Persist settings
|
- [`SS_SaveManager`](../05-saveload/28_SS_SaveManager.md) — Persist settings
|
||||||
- [`WBP_SettingsUI`](../06-ui/44_WBP_SettingsUI.md) — UI to read/write settings
|
- [`WBP_SettingsUI`](../06-ui/44_WBP_SettingsUI.md) — UI to read/write settings
|
||||||
|
|
||||||
@@ -116,8 +116,9 @@ Centralized settings manager that stores, applies, and persists all player-confi
|
|||||||
|
|
||||||
[ApplySettings]
|
[ApplySettings]
|
||||||
└─► ApplyGraphicsSettings():
|
└─► ApplyGraphicsSettings():
|
||||||
Get "ResolutionScale", "ShadowQuality", etc.
|
Get "QualityPreset", "ResolutionScale", "UpscalerMethod", etc.
|
||||||
Pass to BPC_PerformanceScaler.SetQualityLevel
|
Pass to BPC_RenderPipelineManager.ApplyQualityPreset(QualityPreset)
|
||||||
|
→ RenderPipelineManager handles ALL CVars, reload detection, platform selection
|
||||||
└─► ApplyAudioSettings():
|
└─► ApplyAudioSettings():
|
||||||
Get "MasterVolume", "SFXVolume", etc.
|
Get "MasterVolume", "SFXVolume", etc.
|
||||||
Pass to BPC_AudioManager volume modifiers
|
Pass to BPC_AudioManager volume modifiers
|
||||||
@@ -146,18 +147,31 @@ Centralized settings manager that stores, applies, and persists all player-confi
|
|||||||
|
|
||||||
[RegisterDefaultSettings]
|
[RegisterDefaultSettings]
|
||||||
└─► Graphics category:
|
└─► Graphics category:
|
||||||
ResolutionScale Float 1.0 [0.5–1.5]
|
QualityPreset Dropdown [Low, Medium, High, Ultra, Cinematic, Custom]
|
||||||
ShadowQuality Int 2 [0–3]
|
GlobalIllumination Dropdown [Lumen, Baked, SSGI, Off] ⚠ reload
|
||||||
|
ShadowMethod Dropdown [VSM, CSM, DFShadows, Off] ⚠ reload
|
||||||
|
ReflectionMethod Dropdown [Lumen, SSR, Captures, Off] ⚠ reload
|
||||||
|
MeshStrategy Dropdown [Nanite, LOD, Proxy] ⚠ reload
|
||||||
|
UpscalerMethod Dropdown [TSR, DLSS, FSR, PSSR, XeSS, NIS, TAAU, Off]
|
||||||
|
UpscalerQuality Dropdown [UltraPerf, Perf, Balanced, Quality, UltraQuality]
|
||||||
|
ResolutionScale Float 1.0 [0.25–2.0]
|
||||||
|
DynamicResolution Bool false
|
||||||
|
DynamicResTargetFPS Int 60 [30–120]
|
||||||
|
ShadowQuality Int 2 [0–4]
|
||||||
TextureQuality Int 2 [0–3]
|
TextureQuality Int 2 [0–3]
|
||||||
PostProcessQuality Int 2 [0–3]
|
PostProcessQuality Int 2 [0–3]
|
||||||
AntiAliasing Dropdown [TSR, TAA, FXAA, Off]
|
ViewDistanceQuality Int 2 [0–3]
|
||||||
|
FoliageQuality Int 2 [0–3]
|
||||||
|
AntiAliasingQuality Int 2 [0–3]
|
||||||
VSync Bool true
|
VSync Bool true
|
||||||
FrameRateLimit Int 60 [30–240]
|
FrameRateLimit Int 60 [30–240]
|
||||||
GlobalIllumination Dropdown [Lumen, SSGI, Off]
|
|
||||||
MotionBlur Bool true
|
MotionBlur Bool true
|
||||||
DepthOfField Bool true
|
DepthOfField Bool true
|
||||||
FoliageQuality Int 2 [0–3]
|
VolumetricClouds Bool true
|
||||||
ViewDistance Int 2 [0–3]
|
HW_RayTracing Bool false ⚠ reload
|
||||||
|
Brightness Float 1.0 [0.5–2.0]
|
||||||
|
HDR Bool false (if display supports)
|
||||||
|
TexturePoolSizeMB Int 2048 [512–8192]
|
||||||
└─► Audio category:
|
└─► Audio category:
|
||||||
MasterVolume Float 1.0 [0–1]
|
MasterVolume Float 1.0 [0–1]
|
||||||
SFXVolume Float 1.0 [0–1]
|
SFXVolume Float 1.0 [0–1]
|
||||||
@@ -205,7 +219,8 @@ Centralized settings manager that stores, applies, and persists all player-confi
|
|||||||
|
|
||||||
| Target | Method | Why |
|
| Target | Method | Why |
|
||||||
|--------|--------|-----|
|
|--------|--------|-----|
|
||||||
| [`BPC_PerformanceScaler`](../10-adaptive/69_BPC_PerformanceScaler.md) | Cast to player | Graphics settings |
|
| [`BPC_RenderPipelineManager`](149_BPC_RenderPipelineManager.md) | Direct call | Graphics pipeline settings |
|
||||||
|
| [`BPC_PerformanceScaler`](../10-adaptive/69_BPC_PerformanceScaler.md) | Direct call | Adaptive quality bridging |
|
||||||
| [`BPC_AudioManager`](../10-adaptive/66_BPC_AudioManager.md) | Cast to player | Audio settings |
|
| [`BPC_AudioManager`](../10-adaptive/66_BPC_AudioManager.md) | Cast to player | Audio settings |
|
||||||
| [`BPC_Movement`](../02-player/14_BPC_Movement.md) | Cast to player | Gameplay settings |
|
| [`BPC_Movement`](../02-player/14_BPC_Movement.md) | Cast to player | Gameplay settings |
|
||||||
| [`SS_SaveManager`](../05-saveload/28_SS_SaveManager.md) | Direct call | Persistence |
|
| [`SS_SaveManager`](../05-saveload/28_SS_SaveManager.md) | Direct call | Persistence |
|
||||||
|
|||||||
577
docs/blueprints/12-settings/148_BPC_HapticsController.md
Normal file
577
docs/blueprints/12-settings/148_BPC_HapticsController.md
Normal file
@@ -0,0 +1,577 @@
|
|||||||
|
# 148 — Haptics Controller (`BPC_HapticsController`)
|
||||||
|
|
||||||
|
> **Blueprint-Only Implementation** — UE 5.5–5.7 fully supports controller haptics/force feedback from Blueprints. No C++ required. This component wraps UE5's `Play Force Feedback`, `Set Haptics By Value`, and DualSense adaptive trigger APIs behind a GameplayTag-driven event system.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Abstraction layer for all controller haptic feedback and force feedback effects. Gameplay systems trigger haptics by GameplayTag (e.g., `Haptic.Damage.Heavy`) rather than calling raw UE5 haptic APIs. This component handles platform detection (Xbox rumble vs PS5 DualSense adaptive triggers vs generic PC gamepad), respects accessibility settings (`BPC_AccessibilitySettings.bHapticsEnabled`), and manages effect priority/conflict resolution.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- **Requires:** [`DA_HapticProfile`](docs/blueprints/14-data-assets/121_DA_HapticProfile.md) (effect definitions), [`BPC_AccessibilitySettings`](docs/blueprints/12-settings/104_BPC_AccessibilitySettings.md) (master toggle), [`BPC_StateManager`](docs/blueprints/16-state/130_BPC_StateManager.md) (heart rate for heartbeat haptic), [`BPC_PlatformServiceAbstraction`](docs/blueprints/01-core/150_BPC_PlatformServiceAbstraction.md) (platform detection — replaces own platform enum)
|
||||||
|
- **Required By:** `BPC_HealthSystem` (damage haptics), `BPC_FirearmSystem` (weapon fire kick), `BPC_MeleeSystem` (melee impact), `BPC_PhysicsDragSystem` (grab/release), `BPC_ScareEventSystem` (tension rumble), `BP_ItemPickup` (pickup pulse), `BPC_MovementStateSystem` (footstep rumble via GASP notifies)
|
||||||
|
- **Engine/Plugin Requirements:** Enhanced Input Plugin (controller detection), PlayStation 5 Controller Plugin (DualSense adaptive triggers — optional, graceful fallback)
|
||||||
|
|
||||||
|
## Class Info
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| **Parent Class** | `ActorComponent` |
|
||||||
|
| **Class Type** | Blueprint Component |
|
||||||
|
| **Asset Path** | `Content/Framework/Settings/BPC_HapticsController` |
|
||||||
|
| **Implements Interfaces** | None |
|
||||||
|
| **Attachment** | Player Controller (`PC_CoreController`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Enums
|
||||||
|
|
||||||
|
### `EHapticEvent`
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `None = 0` | No haptic effect |
|
||||||
|
| `Damage = 1` | Player takes damage (intensity scales with amount) |
|
||||||
|
| `HeavyDamage = 2` | Critical/major damage hit |
|
||||||
|
| `Heartbeat = 3` | Heartbeat pulse (tempo from BPC_StateManager heart rate) |
|
||||||
|
| `WeaponFire = 4` | Weapon fire kick |
|
||||||
|
| `WeaponReload = 5` | Reload action feedback |
|
||||||
|
| `MeleeImpact = 6` | Melee weapon hit/kill |
|
||||||
|
| `Footstep = 7` | Footstep surface-dependent rumble |
|
||||||
|
| `Explosion = 8` | Nearby explosion or environmental blast |
|
||||||
|
| `PickupItem = 9` | Item picked up |
|
||||||
|
| `DropItem = 10` | Item dropped/discarded |
|
||||||
|
| `GrabObject = 11` | Physics object grabbed |
|
||||||
|
| `ReleaseObject = 12` | Physics object released/thrown |
|
||||||
|
| `ScareEvent = 13` | Jump scare / tension event |
|
||||||
|
| `AmbientPulse = 14` | Low-level ambient tension rumble |
|
||||||
|
| `UI_Confirm = 15` | Menu confirm/select haptic click |
|
||||||
|
| `UI_Navigate = 16` | Menu navigation tick |
|
||||||
|
| `LowHealth = 17` | Health-critical warning pulse |
|
||||||
|
| `StaminaExhausted = 18` | Stamina depleted heavy pulse |
|
||||||
|
| `Death = 19` | Player death rumble |
|
||||||
|
|
||||||
|
### `EHapticMotor`
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `Left = 0` | Left (low-frequency) motor only |
|
||||||
|
| `Right = 1` | Right (high-frequency) motor only |
|
||||||
|
| `Both = 2` | Both motors simultaneously |
|
||||||
|
|
||||||
|
### `EControllerPlatform` *(deprecated — use EPlatformFamily from BPC_PlatformServiceAbstraction. This enum is mapped internally from the unified platform enum.)*
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `Unknown = 0` | Platform not yet detected |
|
||||||
|
| `PC_Generic = 1` | PC with generic gamepad (XInput) |
|
||||||
|
| `Xbox = 2` | Xbox Series X\|S / Xbox One controller |
|
||||||
|
| `PS5_DualSense = 3` | PlayStation 5 DualSense controller |
|
||||||
|
| `PS4_DualShock = 4` | PlayStation 4 DualShock 4 controller |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Structs
|
||||||
|
|
||||||
|
### `S_HapticRequest`
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `ProfileTag` | `FGameplayTag` | Which DA_HapticProfile to use |
|
||||||
|
| `EventType` | `EHapticEvent` | Event category |
|
||||||
|
| `IntensityMultiplier` | `Float` | Scale intensity (0.0–2.0, 1.0 = default) |
|
||||||
|
| `DurationOverride` | `Float` | Override duration (-1 = use profile default) |
|
||||||
|
| `Priority` | `Int32` | Higher interrupts lower (0–100) |
|
||||||
|
| `RequestTime` | `Float` | Game time when requested (for cooldown) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Variables
|
||||||
|
|
||||||
|
### Configuration (Instance Editable)
|
||||||
|
| Variable | Type | Default | Category | Description |
|
||||||
|
|----------|------|---------|----------|-------------|
|
||||||
|
| `HapticProfileMap` | `TMap<FGameplayTag, DA_HapticProfile>` | `Empty` | `Config` | All haptic profiles loaded at startup |
|
||||||
|
| `bHapticsEnabled` | `Bool` | `true` | `Config` | Master toggle (synced from BPC_AccessibilitySettings) |
|
||||||
|
| `bEnableDualSenseTriggers` | `Bool` | `true` | `Config` | Enable adaptive trigger effects on PS5 |
|
||||||
|
| `bEnableSpeakerAudio` | `Bool` | `true` | `Config` | Enable controller speaker audio on PS5 |
|
||||||
|
| `MinTimeBetweenEffects` | `Float` | `0.05` | `Config` | Minimum seconds between any two effects (prevents rumble spam) |
|
||||||
|
| `HapticIntensityScale` | `Float` | `1.0` | `Config` | Global intensity multiplier (0.0 = off, 1.0 = full) |
|
||||||
|
| `HeartbeatMinBPM` | `Float` | `40.0` | `Config` | Minimum BPM for heartbeat haptic |
|
||||||
|
| `HeartbeatMaxBPM` | `Float` | `180.0` | `Config` | Maximum BPM for heartbeat haptic |
|
||||||
|
|
||||||
|
### Internal (Private)
|
||||||
|
| Variable | Type | Default | Category | Description |
|
||||||
|
|----------|------|---------|----------|-------------|
|
||||||
|
| `CurrentPlatform` | `EControllerPlatform` | `Unknown` | `State` | Detected controller platform |
|
||||||
|
| `bIsInitialized` | `Bool` | `false` | `State` | Whether Initialize has completed |
|
||||||
|
| `CachedPlayerController` | `APlayerController` | `None` | `Cache` | Cached owner PlayerController reference |
|
||||||
|
| `ActiveHapticEffect` | `UForceFeedbackEffect` | `None` | `State` | Currently playing effect asset |
|
||||||
|
| `LastPlayTime` | `Float` | `0.0` | `State` | Game time of last played effect |
|
||||||
|
| `PendingRequest` | `S_HapticRequest` | `Empty` | `State` | Currently queued request |
|
||||||
|
| `bHeartbeatActive` | `Bool` | `false` | `State` | Whether heartbeat loop is active |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Functions
|
||||||
|
|
||||||
|
### Public Functions
|
||||||
|
|
||||||
|
#### `Initialize` → `void`
|
||||||
|
- **Description:** Detects controller platform, loads all `DA_HapticProfile` instances into `HapticProfileMap`, caches PlayerController.
|
||||||
|
- **Parameters:** None
|
||||||
|
- **Blueprint Authority:** Local Client Only
|
||||||
|
- **Flow:**
|
||||||
|
1. Get Owner → Cast to `APlayerController` → Store as `CachedPlayerController`
|
||||||
|
2. Detect platform: Check `UGameplayStatics::GetPlatformName()` + connected input devices
|
||||||
|
3. Set `CurrentPlatform` (Xbox, PS5_DualSense, PS4_DualShock, or PC_Generic)
|
||||||
|
4. Load all `DA_HapticProfile` assets from `Content/Framework/DataAssets/Haptics/` into `HapticProfileMap`
|
||||||
|
5. If `BPC_AccessibilitySettings` exists on owner → bind to `OnHapticsToggleChanged`
|
||||||
|
6. Bind to `BPC_StateManager.OnHeartRateChanged` for heartbeat haptic
|
||||||
|
7. Set `bIsInitialized = true`
|
||||||
|
8. Broadcast `OnHapticsControllerInitialized`
|
||||||
|
|
||||||
|
#### `PlayHapticByTag` → `void`
|
||||||
|
- **Description:** Play a haptic effect by its GameplayTag. Main entry point for all gameplay systems.
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `ProfileTag` | `FGameplayTag` | Tag matching a DA_HapticProfile |
|
||||||
|
| `IntensityMultiplier` | `Float` | Scale intensity (default 1.0) |
|
||||||
|
- **Blueprint Authority:** Local Client Only
|
||||||
|
- **Flow:**
|
||||||
|
1. Validate `bHapticsEnabled` and `bIsInitialized` — if disabled, return
|
||||||
|
2. Check `MinTimeBetweenEffects` cooldown — if too soon, queue or skip
|
||||||
|
3. Look up `DA_HapticProfile` from `HapticProfileMap` by `ProfileTag`
|
||||||
|
4. If not found: log warning, return
|
||||||
|
5. Build `S_HapticRequest` with profile data
|
||||||
|
6. Call `PlayHapticInternal()` with the request
|
||||||
|
7. Broadcast `OnHapticPlayed` with tag
|
||||||
|
|
||||||
|
#### `PlayHapticByEvent` → `void`
|
||||||
|
- **Description:** Play a haptic effect by event type. Convenience wrapper for systems that don't know the exact tag.
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `EventType` | `EHapticEvent` | Which event to trigger |
|
||||||
|
| `IntensityMultiplier` | `Float` | Scale intensity (default 1.0) |
|
||||||
|
- **Flow:** Converts `EHapticEvent` to default tag (e.g., `Damage` → `Haptic.Damage.Default`) and calls `PlayHapticByTag`.
|
||||||
|
|
||||||
|
#### `StopHaptic` → `void`
|
||||||
|
- **Description:** Stop all currently playing haptic effects.
|
||||||
|
- **Flow:**
|
||||||
|
1. If `CachedPlayerController` valid: call `Stop Force Feedback` node
|
||||||
|
2. Set `ActiveHapticEffect = None`
|
||||||
|
3. If `bHeartbeatActive`: call `StopHeartbeatHaptic()`
|
||||||
|
4. Broadcast `OnHapticStopped`
|
||||||
|
|
||||||
|
#### `PlayHeartbeatHaptic` → `void`
|
||||||
|
- **Description:** Start or update the continuous heartbeat pulse haptic at the given BPM.
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `BeatsPerMinute` | `Float` | Heart rate in BPM (from BPC_StateManager) |
|
||||||
|
- **Flow:**
|
||||||
|
1. Clamp BPM to `HeartbeatMinBPM` — `HeartbeatMaxBPM`
|
||||||
|
2. Calculate pulse interval: `Interval = 60.0 / BPM`
|
||||||
|
3. Set `bHeartbeatActive = true`
|
||||||
|
4. Start a looping timer at `Interval` seconds
|
||||||
|
5. Each tick: call `PlayHapticByTag(Haptic.Heartbeat)` with intensity from BPM
|
||||||
|
6. If BPM changes: update timer interval
|
||||||
|
|
||||||
|
#### `StopHeartbeatHaptic` → `void`
|
||||||
|
- **Description:** Stop the continuous heartbeat haptic loop.
|
||||||
|
- **Flow:** Clear heartbeat timer, set `bHeartbeatActive = false`.
|
||||||
|
|
||||||
|
#### `SetHapticsEnabled` → `void`
|
||||||
|
- **Description:** Enable or disable all haptic output. Called by accessibility toggle.
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `bEnabled` | `Bool` | New state |
|
||||||
|
- **Flow:**
|
||||||
|
1. Set `bHapticsEnabled = bEnabled`
|
||||||
|
2. If disabled: call `StopHaptic()`
|
||||||
|
3. Broadcast `OnHapticsEnabledChanged`
|
||||||
|
|
||||||
|
#### `SetControllerPlatform` → `void`
|
||||||
|
- **Description:** Force a specific controller platform (for testing or hot-swap).
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `Platform` | `EControllerPlatform` | Target platform |
|
||||||
|
|
||||||
|
#### `GetControllerPlatform` → `EControllerPlatform`
|
||||||
|
- **Description:** Returns the detected controller platform. Read-only.
|
||||||
|
|
||||||
|
#### `IsDualSenseConnected` → `Bool`
|
||||||
|
- **Description:** Returns true if a PS5 DualSense controller is detected.
|
||||||
|
- **Flow:** Returns `CurrentPlatform == PS5_DualSense`
|
||||||
|
|
||||||
|
#### `SetDualSenseTriggerEffect` → `void`
|
||||||
|
- **Description:** Set adaptive trigger resistance on a specific DualSense trigger. No-op on non-DualSense platforms.
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `TriggerSide` | `Name` | "Left" or "Right" |
|
||||||
|
| `EffectType` | `Name` | "Resistance", "Vibration", "WeaponFire", "BowDraw", etc. |
|
||||||
|
| `StartPosition` | `Int32` | Trigger position where effect begins (0–9) |
|
||||||
|
| `Strength` | `Int32` | Effect strength (0–8) |
|
||||||
|
- **Blueprint Authority:** Local Client Only
|
||||||
|
- **Flow:**
|
||||||
|
1. If `CurrentPlatform != PS5_DualSense`: return (graceful no-op)
|
||||||
|
2. If `bEnableDualSenseTriggers == false`: return
|
||||||
|
3. Call platform-specific DualSense trigger API via `PlayerController`
|
||||||
|
4. Log effect for debugging
|
||||||
|
|
||||||
|
### Protected / Private Functions
|
||||||
|
|
||||||
|
#### `PlayHapticInternal` → `void`
|
||||||
|
- **Description:** Core haptic playback logic. Resolves platform, selects the right effect asset, handles priority conflicts.
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `Request` | `S_HapticRequest` | Full haptic request |
|
||||||
|
- **Flow:**
|
||||||
|
1. Check priority: if `Request.Priority < PendingRequest.Priority` and `PendingRequest` is active → skip
|
||||||
|
2. If `Request.Priority >= PendingRequest.Priority`: interrupt current effect
|
||||||
|
3. Resolve platform: select `UForceFeedbackEffect` from `DA_HapticProfile` for `CurrentPlatform`
|
||||||
|
4. If no platform-specific asset: fall back to generic PC effect
|
||||||
|
5. Apply `IntensityMultiplier` to `HapticIntensityScale`
|
||||||
|
6. Call `Play Force Feedback` node on `CachedPlayerController`:
|
||||||
|
- Input: `ForceFeedbackEffect`, `IntensityMultiplier`, `bLooping=false`, `bIgnoreTimeDilation=true`
|
||||||
|
7. Store as `ActiveHapticEffect`
|
||||||
|
8. Set `LastPlayTime = GameTimeInSeconds`
|
||||||
|
9. Broadcast `OnHapticPlayed`
|
||||||
|
|
||||||
|
#### `DetectControllerPlatform` → `void`
|
||||||
|
- **Description:** Queries the input system to determine which controller is connected.
|
||||||
|
- **Flow:**
|
||||||
|
1. Check `UGameplayStatics::GetPlatformName()`
|
||||||
|
2. If platform contains "PS5" → `PS5_DualSense`
|
||||||
|
3. If platform contains "PS4" → `PS4_DualShock`
|
||||||
|
4. If platform contains "Xbox" or "XboxOne" or "XSX" → `Xbox`
|
||||||
|
5. Otherwise: check connected devices → if gamepad found → `PC_Generic`, else → `Unknown`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Event Dispatchers
|
||||||
|
|
||||||
|
| Dispatcher | Parameters | Bind Access | Description |
|
||||||
|
|------------|-----------|-------------|-------------|
|
||||||
|
| `OnHapticsControllerInitialized` | — | `Public` | Fired after Initialize completes |
|
||||||
|
| `OnHapticPlayed` | `FGameplayTag ProfileTag`, `EHapticEvent EventType`, `Float Intensity` | `Public` | Fired when any haptic effect starts |
|
||||||
|
| `OnHapticStopped` | — | `Public` | Fired when haptics stop (manual or effect ends) |
|
||||||
|
| `OnHapticsEnabledChanged` | `Bool bEnabled` | `Public` | Fired when master toggle changes |
|
||||||
|
| `OnControllerPlatformChanged` | `EControllerPlatform NewPlatform` | `Public` | Fired on controller hot-swap |
|
||||||
|
| `OnDualSenseTriggerActivated` | `Name TriggerSide`, `Name EffectType` | `Public` | Fired when adaptive trigger effect applied (PS5 only) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Overridden Events / Custom Events
|
||||||
|
|
||||||
|
### Event: `BeginPlay`
|
||||||
|
- **Description:** Startup. Calls `Initialize()`.
|
||||||
|
- **Flow:**
|
||||||
|
1. Call `Initialize()`
|
||||||
|
2. If `CachedPlayerController` is null: log error, return
|
||||||
|
3. Register for controller connection/disconnection events
|
||||||
|
|
||||||
|
### Event: `EndPlay`
|
||||||
|
- **Description:** Cleanup on component destruction.
|
||||||
|
- **Flow:**
|
||||||
|
1. Call `StopHaptic()` (stop all effects)
|
||||||
|
2. Call `StopHeartbeatHaptic()`
|
||||||
|
3. Unbind from all dispatchers
|
||||||
|
4. Clear cached references
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Blueprint Graph Logic Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[BeginPlay] --> B[Initialize]
|
||||||
|
B --> C{Detect Controller Platform}
|
||||||
|
C --> D[Set CurrentPlatform]
|
||||||
|
D --> E[Load DA_HapticProfile Map]
|
||||||
|
E --> F[Bind to AccessibilitySettings.OnHapticsToggleChanged]
|
||||||
|
F --> G[Bind to StateManager.OnHeartRateChanged]
|
||||||
|
G --> H[Broadcast OnInitialized]
|
||||||
|
|
||||||
|
I[PlayHapticByTag] --> J{bHapticsEnabled?}
|
||||||
|
J -->|No| K[Return]
|
||||||
|
J -->|Yes| L{Cooldown check}
|
||||||
|
L -->|TooSoon| M[Queue or skip]
|
||||||
|
L -->|OK| N[Lookup DA_HapticProfile]
|
||||||
|
N --> O{Found?}
|
||||||
|
O -->|No| P[Log Warning]
|
||||||
|
O -->|Yes| Q[Build S_HapticRequest]
|
||||||
|
Q --> R[PlayHapticInternal]
|
||||||
|
R --> S{Platform == PS5?}
|
||||||
|
S -->|Yes| T[Play Force Feedback PS5 Profile]
|
||||||
|
S -->|No| U[Play Force Feedback Generic]
|
||||||
|
T --> V[Broadcast OnHapticPlayed]
|
||||||
|
U --> V
|
||||||
|
|
||||||
|
W[PlayHeartbeatHaptic] --> X[Clamp BPM]
|
||||||
|
X --> Y[Calculate Interval = 60/BPM]
|
||||||
|
Y --> Z[Start Looping Timer]
|
||||||
|
Z --> AA[Each Tick: PlayHapticByTag Haptic.Heartbeat]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Communication Matrix
|
||||||
|
|
||||||
|
| Who Talks | How | What Is Sent |
|
||||||
|
|-----------|-----|-------------|
|
||||||
|
| `BPC_HealthSystem` | `Function Call` | `BPC_HapticsController::PlayHapticByTag(Haptic.Damage.Heavy, Intensity)` on damage taken |
|
||||||
|
| `BPC_FirearmSystem` | `Function Call` | `BPC_HapticsController::PlayHapticByTag(Haptic.WeaponFire.Pistol)` on fire |
|
||||||
|
| `BPC_MeleeSystem` | `Function Call` | `BPC_HapticsController::PlayHapticByTag(Haptic.MeleeImpact)` on hit |
|
||||||
|
| `BPC_PhysicsDragSystem` | `Function Call` | `BPC_HapticsController::PlayHapticByTag(Haptic.Grab)` / `Haptic.Release` |
|
||||||
|
| `BPC_ScareEventSystem` | `Function Call` | `BPC_HapticsController::PlayHapticByTag(Haptic.ScareEvent)` on scare trigger |
|
||||||
|
| `BPC_ReloadSystem` | `Function Call` | `BPC_HapticsController::PlayHapticByTag(Haptic.WeaponReload)` on reload complete |
|
||||||
|
| `BP_ItemPickup` | `Function Call` | `BPC_HapticsController::PlayHapticByTag(Haptic.PickupItem)` on collect |
|
||||||
|
| `BPC_MovementStateSystem` | `Function Call` | `BPC_HapticsController::PlayHapticByTag(Haptic.Footstep. + SurfaceTag)` via GASP notify |
|
||||||
|
| `BPC_StateManager` | `Dispatcher` | `OnHeartRateChanged(BPM)` → `BPC_HapticsController::PlayHeartbeatHaptic(BPM)` |
|
||||||
|
| `BPC_AccessibilitySettings` | `Dispatcher` | `OnHapticsToggleChanged(bEnabled)` → `BPC_HapticsController::SetHapticsEnabled()` |
|
||||||
|
| `BPC_DeathHandlingSystem` | `Function Call` | `BPC_HapticsController::PlayHapticByTag(Haptic.Death)` on death |
|
||||||
|
| `BPC_DamageReceptionSystem` | `Function Call` | `BPC_HapticsController::PlayHapticByTag(Haptic.Explosion)` on area damage |
|
||||||
|
| `BPC_StaminaSystem` | `Function Call` | `BPC_HapticsController::PlayHapticByTag(Haptic.StaminaExhausted)` on empty |
|
||||||
|
| `BPC_HealthSystem` (low) | `Function Call` | `BPC_HapticsController::PlayHapticByTag(Haptic.LowHealth)` when <25% HP |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Validation / Testing Checklist
|
||||||
|
|
||||||
|
- [ ] `Initialize` correctly detects Xbox controller: `CurrentPlatform = Xbox`
|
||||||
|
- [ ] `Initialize` correctly detects PS5 DualSense: `CurrentPlatform = PS5_DualSense`
|
||||||
|
- [ ] `PlayHapticByTag` with valid tag plays force feedback on controller
|
||||||
|
- [ ] `PlayHapticByTag` with invalid tag logs warning but does not crash
|
||||||
|
- [ ] `StopHaptic` immediately stops all controller vibration
|
||||||
|
- [ ] `bHapticsEnabled = false` causes all `PlayHapticByTag` calls to be skipped
|
||||||
|
- [ ] Heartbeat haptic loops at correct interval (60 BPM → 1 pulse/second)
|
||||||
|
- [ ] Heartbeat BPM changes dynamically when `PlayHeartbeatHaptic(120)` called
|
||||||
|
- [ ] DualSense trigger resistance activates on PS5 (R2 stiffens when aiming)
|
||||||
|
- [ ] Non-DualSense platforms gracefully skip trigger effects (no crash, no log spam)
|
||||||
|
- [ ] Priority conflict: higher priority effect interrupts lower (damage overrides footstep)
|
||||||
|
- [ ] `MinTimeBetweenEffects` prevents rapid-fire rumble spam
|
||||||
|
- [ ] Edge case: `PlayHapticByTag` before `Initialize` logs warning and returns
|
||||||
|
- [ ] Edge case: Controller disconnected mid-effect — `StopHaptic` called safely
|
||||||
|
- [ ] Edge case: Multiple rapid `PlayHapticByTag` calls — only highest priority plays
|
||||||
|
- [ ] Edge case: Platform hot-swap (Xbox → PS5) fires `OnControllerPlatformChanged`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Reuse Notes
|
||||||
|
|
||||||
|
- Attach this component to the **Player Controller** (not the Pawn). Haptics are per-controller, per-player.
|
||||||
|
- All gameplay systems trigger haptics via GameplayTag — never hardcode `Play Force Feedback` nodes.
|
||||||
|
- The `DA_HapticProfile` Data Asset stores platform-specific `UForceFeedbackEffect` curves. Designers author these in the Content Browser without touching Blueprints.
|
||||||
|
- For multiplayer: haptics are **local client only** — never replicated. The server doesn't need to know about controller vibration.
|
||||||
|
- Heartbeat haptic is the only continuous/looping effect. All others are one-shot.
|
||||||
|
- DualSense adaptive triggers require the **PS5 Controller Plugin** enabled. Framework gracefully degrades on other platforms.
|
||||||
|
- The `Haptic.` GameplayTag namespace is documented in `DT_Tags_Player.csv` and `DA_GameTagRegistry`.
|
||||||
|
- Accessibility: `bHapticsEnabled` syncs with `BPC_AccessibilitySettings` so players can disable all vibration from settings.
|
||||||
|
- For platform profiles: create one `DA_HapticProfile` instance per event per platform, then reference all three in the profile map. `BPC_HapticsController` selects the right one at runtime.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Manual Implementation Guide
|
||||||
|
|
||||||
|
> **This section is for a human implementer building the Blueprint manually in UE5.**
|
||||||
|
> Follow these steps in order. Each function is broken down into specific UE5 Blueprint nodes.
|
||||||
|
|
||||||
|
### 11.1 Class Setup
|
||||||
|
|
||||||
|
1. Create a new Blueprint Class:
|
||||||
|
- Parent Class: `ActorComponent`
|
||||||
|
- Name: `BPC_HapticsController`
|
||||||
|
- Path: `Content/Framework/Settings/`
|
||||||
|
2. Add all variables from Section 3 to the Class Defaults.
|
||||||
|
- Configuration variables: set `Instance Editable` ✓
|
||||||
|
- Internal variables: set to `Private` (no expose)
|
||||||
|
3. Add the Event Dispatchers from Section 5.
|
||||||
|
|
||||||
|
### 11.2 Variable Initialization (BeginPlay)
|
||||||
|
|
||||||
|
```
|
||||||
|
Event BeginPlay
|
||||||
|
├─ Get Owner → Cast to PlayerController → Store as CachedPlayerController
|
||||||
|
│ └─ If NOT valid: Print String "BPC_HapticsController: Owner is not a PlayerController!" → Return
|
||||||
|
├─ Call DetectControllerPlatform → Set CurrentPlatform
|
||||||
|
├─ Load Asset Registry → Get All Assets of Class (DA_HapticProfile)
|
||||||
|
│ └─ ForEach: Add to HapticProfileMap [ProfileTag → Asset]
|
||||||
|
├─ Get Owner → Get Component by Class (BPC_AccessibilitySettings)
|
||||||
|
│ └─ If valid: Bind Event OnHapticsToggleChanged → SetHapticsEnabled
|
||||||
|
│ └─ If valid: Read initial bHapticsEnabled value
|
||||||
|
├─ Get Owner Pawn → Get Component by Class (BPC_StateManager)
|
||||||
|
│ └─ If valid: Bind Event OnHeartRateChanged → PlayHeartbeatHaptic
|
||||||
|
├─ Set bIsInitialized = true
|
||||||
|
└─ Call OnHapticsControllerInitialized
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.3 Function Implementations
|
||||||
|
|
||||||
|
#### `PlayHapticByTag`
|
||||||
|
|
||||||
|
**Input Pins:** `ProfileTag` (GameplayTag), `IntensityMultiplier` (Float)
|
||||||
|
**Output Pins:** None
|
||||||
|
|
||||||
|
**Node-by-Node Logic:**
|
||||||
|
```
|
||||||
|
[Function: PlayHapticByTag]
|
||||||
|
Step 1: Branch on bHapticsEnabled → False: Return
|
||||||
|
Step 2: Get Game Time in Seconds → Subtract LastPlayTime → Compare to MinTimeBetweenEffects
|
||||||
|
Branch → Too soon: Return (or queue if needed)
|
||||||
|
Step 3: HapticProfileMap → Find (ProfileTag) → Store as FoundProfile
|
||||||
|
Step 4: Branch on FoundProfile valid?
|
||||||
|
True →
|
||||||
|
Step 4a: Make S_HapticRequest:
|
||||||
|
- ProfileTag = ProfileTag
|
||||||
|
- EventType = FoundProfile.EventType
|
||||||
|
- IntensityMultiplier = IntensityMultiplier
|
||||||
|
- DurationOverride = -1 (use profile default)
|
||||||
|
- Priority = FoundProfile.Priority
|
||||||
|
Step 4b: Call PlayHapticInternal(S_HapticRequest)
|
||||||
|
Step 4c: Call OnHapticPlayed(ProfileTag, FoundProfile.EventType, IntensityMultiplier)
|
||||||
|
False →
|
||||||
|
Step 4d: Print String Warning: "No haptic profile found for tag: {ProfileTag}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nodes Used:** `Branch`, `FindGameplayTag`, `Map Find`, `Make Struct (S_HapticRequest)`, `Call Function`, `Print String`
|
||||||
|
|
||||||
|
#### `PlayHapticInternal`
|
||||||
|
|
||||||
|
**Input Pins:** `Request` (S_HapticRequest)
|
||||||
|
**Output Pins:** None
|
||||||
|
|
||||||
|
**Node-by-Node Logic:**
|
||||||
|
```
|
||||||
|
[Function: PlayHapticInternal]
|
||||||
|
Step 1: If ActiveHapticEffect is valid → Call StopHaptic (interrupt current)
|
||||||
|
Step 2: Break S_HapticRequest → get ProfileTag
|
||||||
|
Step 3: Look up DA_HapticProfile from HapticProfileMap
|
||||||
|
Step 4: Switch on CurrentPlatform:
|
||||||
|
Case PS5_DualSense: Get PS5_ForceFeedbackCurve from profile
|
||||||
|
Case Xbox: Get Xbox_ForceFeedbackCurve from profile
|
||||||
|
Default: Get Generic_ForceFeedbackCurve from profile
|
||||||
|
Step 5: Branch on selected curve valid?
|
||||||
|
True →
|
||||||
|
Step 5a: CachedPlayerController → Play Force Feedback
|
||||||
|
- Force Feedback Effect: selected curve asset
|
||||||
|
- Intensity Multiplier: Request.IntensityMultiplier * HapticIntensityScale
|
||||||
|
- bLooping: false
|
||||||
|
- bIgnore Time Dilation: true
|
||||||
|
Step 5b: Set ActiveHapticEffect = selected curve
|
||||||
|
False →
|
||||||
|
Step 5c: Print String Warning: "No ForceFeedbackEffect for platform {CurrentPlatform}"
|
||||||
|
Step 6: Set LastPlayTime = Get Game Time in Seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nodes Used:** `Switch on EControllerPlatform`, `Break S_HapticRequest`, `Map Find`, `Play Force Feedback (PlayerController)`, `Branch`
|
||||||
|
|
||||||
|
#### `PlayHeartbeatHaptic`
|
||||||
|
|
||||||
|
**Input Pins:** `BeatsPerMinute` (Float)
|
||||||
|
**Output Pins:** None
|
||||||
|
|
||||||
|
**Node-by-Node Logic:**
|
||||||
|
```
|
||||||
|
[Function: PlayHeartbeatHaptic]
|
||||||
|
Step 1: Clamp (BeatsPerMinute, HeartbeatMinBPM, HeartbeatMaxBPM) → Store as ClampedBPM
|
||||||
|
Step 2: Divide 60.0 / ClampedBPM → Store as PulseInterval
|
||||||
|
Step 3: If bHeartbeatActive:
|
||||||
|
True → Clear Timer by Handle (HeartbeatTimerHandle)
|
||||||
|
Step 4: Set bHeartbeatActive = true
|
||||||
|
Step 5: Set Timer by Event:
|
||||||
|
- Event: Custom Event (HeartbeatPulse)
|
||||||
|
- Time: PulseInterval
|
||||||
|
- Looping: true
|
||||||
|
- Store handle as HeartbeatTimerHandle
|
||||||
|
[Custom Event: HeartbeatPulse]
|
||||||
|
→ Call PlayHapticByTag(Haptic.Heartbeat, 1.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nodes Used:** `Clamp (float)`, `Divide`, `Set Timer by Event`, `Clear Timer by Handle`
|
||||||
|
|
||||||
|
#### `SetHapticsEnabled`
|
||||||
|
|
||||||
|
**Input Pins:** `bEnabled` (Bool)
|
||||||
|
**Output Pins:** None
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: SetHapticsEnabled]
|
||||||
|
Step 1: Set bHapticsEnabled = bEnabled
|
||||||
|
Step 2: Branch:
|
||||||
|
False → Call StopHaptic
|
||||||
|
Step 3: Call OnHapticsEnabledChanged(bEnabled)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `DetectControllerPlatform`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: DetectControllerPlatform]
|
||||||
|
Step 1: Get Platform Name → Store as PlatformStr
|
||||||
|
Step 2: String Contains (PlatformStr, "PS5") → True: Set CurrentPlatform = PS5_DualSense → Return
|
||||||
|
Step 3: String Contains (PlatformStr, "PS4") → True: Set CurrentPlatform = PS4_DualShock → Return
|
||||||
|
Step 4: String Contains (PlatformStr, "Xbox") → True: Set CurrentPlatform = Xbox → Return
|
||||||
|
Step 5: Get Input Device Type → Switch on Type:
|
||||||
|
Gamepad → Set CurrentPlatform = PC_Generic
|
||||||
|
Default → Set CurrentPlatform = Unknown
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nodes Used:** `Get Platform Name`, `Contains (string)`, `Switch on String`, `Get Input Device Type`
|
||||||
|
|
||||||
|
### 11.4 Event Dispatcher Bindings (Inbound Listeners)
|
||||||
|
|
||||||
|
| Bind to Dispatcher | Custom Event to Create | What it Does |
|
||||||
|
|--------------------------------------|------------------------|--------------|
|
||||||
|
| `BPC_StateManager.OnHeartRateChanged` | `OnHeartRateChanged_Handler` | `PlayHeartbeatHaptic(CurrentHeartRate)` |
|
||||||
|
| `BPC_AccessibilitySettings.OnHapticsToggleChanged` | `OnHapticsToggle_Handler` | `SetHapticsEnabled(bEnabled)` |
|
||||||
|
|
||||||
|
### 11.5 Multiplayer Networking
|
||||||
|
|
||||||
|
- This component is **local client only**. No replication needed.
|
||||||
|
- Haptics play only on the local player's controller.
|
||||||
|
- No `HasAuthority()` gates needed — haptics are cosmetic.
|
||||||
|
- For listen server hosts: `IsLocalPlayerController()` check before playing effects.
|
||||||
|
|
||||||
|
### 11.6 Quick Node Reference
|
||||||
|
|
||||||
|
| Node | Where to Find | Used For |
|
||||||
|
|------|---------------|----------|
|
||||||
|
| `Play Force Feedback` | Right-click → "Play Force Feedback" | Playing rumble effect on controller |
|
||||||
|
| `Stop Force Feedback` | Right-click → "Stop Force Feedback" | Stopping all active vibration |
|
||||||
|
| `Set Timer by Event` | Right-click → "Set Timer by Event" | Looping heartbeat pulse |
|
||||||
|
| `Get Platform Name` | Right-click → "Get Platform Name" | Detecting Xbox/PS5/PC |
|
||||||
|
| `Clamp (float)` | Right-click → "Clamp" | Clamping BPM range |
|
||||||
|
| `Make Struct` | Right-click → "Make S_HapticRequest" | Building haptic request |
|
||||||
|
| `Map Find` | Right-click → "Find" | Looking up profile by tag |
|
||||||
|
| `Get Game Time in Seconds` | Right-click → "Get Game Time" | Cooldown tracking |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Blueprint Build Checklist
|
||||||
|
|
||||||
|
- [ ] Create Blueprint class: `BPC_HapticsController` (parent: `ActorComponent`)
|
||||||
|
- [ ] Add all variables from Section 3 with correct types and defaults
|
||||||
|
- [ ] Create `EHapticEvent`, `EHapticMotor`, `EControllerPlatform` enums (in Content Browser)
|
||||||
|
- [ ] Create `S_HapticRequest` struct (in Content Browser)
|
||||||
|
- [ ] Build `BeginPlay` event → `Initialize` chain
|
||||||
|
- [ ] Implement `DetectControllerPlatform` function
|
||||||
|
- [ ] Implement `PlayHapticInternal` with Platform Switch
|
||||||
|
- [ ] Implement `PlayHapticByTag` (public entry point)
|
||||||
|
- [ ] Implement `PlayHapticByEvent` (convenience wrapper)
|
||||||
|
- [ ] Implement `StopHaptic` / `StopHeartbeatHaptic`
|
||||||
|
- [ ] Implement `PlayHeartbeatHaptic` with looping timer
|
||||||
|
- [ ] Implement `SetHapticsEnabled` / `SetDualSenseTriggerEffect`
|
||||||
|
- [ ] Create all 6 event dispatchers
|
||||||
|
- [ ] Bind to `BPC_StateManager.OnHeartRateChanged`
|
||||||
|
- [ ] Bind to `BPC_AccessibilitySettings.OnHapticsToggleChanged`
|
||||||
|
- [ ] Create at least one `DA_HapticProfile` instance for `Haptic.Damage.Default`
|
||||||
|
- [ ] Test: PlayHapticByTag triggers vibration on Xbox controller
|
||||||
|
- [ ] Test: PlayHapticByTag triggers vibration on PS5 DualSense
|
||||||
|
- [ ] Test: bHapticsEnabled=false blocks all effects
|
||||||
|
- [ ] Test: Heartbeat BPM changes pitch/speed of pulse
|
||||||
|
- [ ] Test: Rapid-fire calls respect MinTimeBetweenEffects
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Blueprint Spec: Haptics Controller. Conforms to TEMPLATE.md v2.0 — part of the UE5 Modular Game Framework, SETTINGS layer.*
|
||||||
434
docs/blueprints/12-settings/149_BPC_RenderPipelineManager.md
Normal file
434
docs/blueprints/12-settings/149_BPC_RenderPipelineManager.md
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
# 149 — Render Pipeline Manager (`BPC_RenderPipelineManager`)
|
||||||
|
|
||||||
|
> **Blueprint-Only Implementation** — UE 5.5–5.7 supports all render pipeline configuration from Blueprints via `Execute Console Command` and `UGameUserSettings` API calls. This component wraps the complexity of per-platform render pipeline selection, upscaler configuration, and pre-level-load CVar application behind a simple `ApplyQualityPreset(PresetName)` interface.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Central authority for render pipeline configuration. Reads `DA_RenderPipelineProfile` Data Assets to determine the appropriate rendering method (Lumen vs Baked Lightmass), shadow system (VSM vs CSM), upscaler (DLSS/FSR/TSR/PSSR), and mesh strategy (Nanite vs LOD) for the current platform and quality preset. Applies settings via UE5 console variables, coordinates with `BPC_PerformanceScaler` for runtime quality tier adjustments, and broadcasts pipeline changes so dependent systems (Planar Capture, Audio, UI) can adapt.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- **Requires:** [`DA_RenderPipelineProfile`](../14-data-assets/DA_RenderPipelineProfile.md) (render configs), [`BPC_PlatformServiceAbstraction`](../01-core/150_BPC_PlatformServiceAbstraction.md) (central platform detection), [`SS_SettingsSystem`](105_SS_SettingsSystem.md) (saved quality preferences), [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) (game phase for pre-load apply)
|
||||||
|
- **Required By:** [`BPC_PerformanceScaler`](../10-adaptive/91_BPC_PerformanceScaler.md) (delegates CVar application), [`SS_PlanarCaptureManager`](../17-capture/138_SS_PlanarCaptureManager.md) (capture quality cap on pipeline change), [`WBP_SettingsMenu`](../06-ui/57_WBP_SettingsMenu.md) (Video tab quality controls)
|
||||||
|
- **Engine/Plugin Requirements:** DLSS Plugin (optional), FSR Plugin (optional), XeSS Plugin (optional), GameplayTags, Enhanced Input
|
||||||
|
|
||||||
|
## Class Info
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| **Parent Class** | `ActorComponent` |
|
||||||
|
| **Class Type** | Blueprint Component |
|
||||||
|
| **Asset Path** | `Content/Framework/Settings/BPC_RenderPipelineManager` |
|
||||||
|
| **Implements Interfaces** | None |
|
||||||
|
| **Attachment** | Player Controller (`PC_CoreController`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Enums
|
||||||
|
|
||||||
|
*See [`DA_RenderPipelineProfile`](../14-data-assets/DA_RenderPipelineProfile.md) for: `ERenderPipelineMethod`, `EShadowMethod`, `EReflectionMethod`, `EUpscalerMethod`, `EMeshStrategy`.*
|
||||||
|
*See [`BPC_PlatformServiceAbstraction`](../01-core/150_BPC_PlatformServiceAbstraction.md) for: `EPlatformFamily` (unified — all systems use this).*
|
||||||
|
|
||||||
|
### `ERenderPipelineChangeType`
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `NonDestructive = 0` | Change is safe at runtime (resolution, texture quality, FPS cap) |
|
||||||
|
| `RequiresLevelReload = 1` | Change requires reloading the current level (GI method, Nanite, shadow method) |
|
||||||
|
| `RequiresEngineRestart = 2` | Change requires full engine restart (HWRT toggle on some platforms) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Structs
|
||||||
|
|
||||||
|
### `SActivePipelineState`
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `Platform` | `EPlatformFamily` | Detected platform (from BPC_PlatformServiceAbstraction) |
|
||||||
|
| `ActivePreset` | `FName` | Currently applied quality preset name |
|
||||||
|
| `PendingPreset` | `FName` | Queued preset awaiting reload |
|
||||||
|
| `ActivePipelineProfile` | `DA_RenderPipelineProfile` | Currently loaded profile Data Asset |
|
||||||
|
| `bPipelineApplied` | `bool` | Whether full pipeline has been applied |
|
||||||
|
| `bPendingReloadRequired` | `bool` | Whether a level reload is needed to apply changes |
|
||||||
|
| `AppliedConfig` | `SRenderPipelineConfig` | Currently active render config values |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Variables
|
||||||
|
|
||||||
|
### Configuration (Instance Editable)
|
||||||
|
| Variable | Type | Default | Category | Description |
|
||||||
|
|----------|------|---------|----------|-------------|
|
||||||
|
| `PlatformProfileMap` | `TMap<EPlatformFamily, DA_RenderPipelineProfile>` | `Empty` | `Config` | One Data Asset per platform family |
|
||||||
|
| `DefaultQualityPreset` | `FName` | `Medium` | `Config` | Preset to use on first launch |
|
||||||
|
| `bApplyPipelineOnBeginPlay` | `bool` | `true` | `Config` | Automatically apply pipeline on BeginPlay |
|
||||||
|
| `bAutoDetectPlatform` | `bool` | `true` | `Config` | Auto-detect platform at startup |
|
||||||
|
|
||||||
|
### Internal (Private)
|
||||||
|
| Variable | Type | Default | Category | Description |
|
||||||
|
|----------|------|---------|----------|-------------|
|
||||||
|
| `ActivePipeline` | `DA_RenderPipelineProfile` | `None` | `State` | Currently active profile |
|
||||||
|
| `ActiveState` | `SActivePipelineState` | — | `State` | Full current pipeline state |
|
||||||
|
| `DetectedPlatform` | `EPlatformFamily` | `PC_High` | `State` | Auto-detected platform |
|
||||||
|
| `bIsInitialized` | `bool` | `false` | `State` | Whether Initialize completed |
|
||||||
|
| `CachedPlayerController` | `APlayerController` | `None` | `Cache` | Owner PC reference |
|
||||||
|
| `bReloadNeededOnNextLevel` | `bool` | `false` | `State` | Reload flag for next level load |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Functions
|
||||||
|
|
||||||
|
### Public Functions
|
||||||
|
|
||||||
|
#### `Initialize` → `void`
|
||||||
|
- **Description:** Detects platform, loads the appropriate `DA_RenderPipelineProfile`, reads saved quality from `SS_SettingsSystem`, and applies initial pipeline settings.
|
||||||
|
- **Flow:**
|
||||||
|
1. Get Owner → Cast to PlayerController → cache
|
||||||
|
2. Bind to `GI_GameFramework.GetSubsystem(BPC_PlatformServiceAbstraction).OnPlatformReady`
|
||||||
|
3. In handler: read `PlatformService.GetPlatformFamily()` → set `DetectedPlatform`
|
||||||
|
3. Lookup `DA_RenderPipelineProfile` from `PlatformProfileMap` by `DetectedPlatform`
|
||||||
|
4. If not found: log error, fallback to `PC_High` profile
|
||||||
|
5. Read `SS_SettingsSystem.GetSettingFloat("QualityPreset")` → resolve preset name
|
||||||
|
6. If no saved setting: use `DefaultQualityPreset`
|
||||||
|
7. Call `ApplyQualityPreset(PresetName)`
|
||||||
|
8. Bind to `SS_SettingsSystem.OnSettingChanged` → listen for quality changes
|
||||||
|
9. Bind to `GI_GameFramework.OnGamePhaseChanged` → apply pending reloads on level change
|
||||||
|
10. Set `bIsInitialized = true`
|
||||||
|
11. Broadcast `OnPipelineManagerInitialized`
|
||||||
|
|
||||||
|
#### `ApplyQualityPreset` → `ERenderPipelineChangeType`
|
||||||
|
- **Description:** Apply a quality preset by name. Returns whether a level reload is needed.
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `PresetName` | `FName` | Quality preset to apply ("Low", "Medium", "High", "Ultra", "Cinematic") |
|
||||||
|
| `bForceApply` | `bool` | Force re-apply even if already active |
|
||||||
|
- **Flow:**
|
||||||
|
1. Validate `bIsInitialized` and `ActivePipeline` is valid
|
||||||
|
2. Lookup `SRenderPipelineConfig` from `ActivePipeline.QualityPresets[PresetName]`
|
||||||
|
3. If not found: log error, return
|
||||||
|
4. Compare with `ActiveState.AppliedConfig`:
|
||||||
|
- If identical and not `bForceApply`: return `NonDestructive`
|
||||||
|
5. Determine change type:
|
||||||
|
- If `GIMethod`, `ShadowMethod`, or `MeshStrategy` changed → `RequiresLevelReload`
|
||||||
|
- Otherwise → `NonDestructive`
|
||||||
|
6. If `RequiresLevelReload` and game is mid-session:
|
||||||
|
- Store as `PendingPreset`, set `bReloadNeededOnNextLevel = true`
|
||||||
|
- Show UI notification "Settings will apply on next level load"
|
||||||
|
- Broadcast `OnPipelineReloadRequired`
|
||||||
|
- Return `RequiresLevelReload`
|
||||||
|
7. If safe to apply now: call `ApplyRenderConfig(PresetName, Config)`
|
||||||
|
8. Save to `SS_SettingsSystem.SetSetting("QualityPreset", PresetName)`
|
||||||
|
9. Broadcast `OnRenderPipelineChanged(PresetName, ChangeType)`
|
||||||
|
10. Return change type
|
||||||
|
|
||||||
|
#### `ApplyRenderConfig` → `void`
|
||||||
|
- **Description:** Executes console commands to apply a render configuration immediately.
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `PresetName` | `FName` | Preset being applied |
|
||||||
|
| `Config` | `SRenderPipelineConfig` | Configuration to apply |
|
||||||
|
- **Flow:**
|
||||||
|
1. Execute console commands for GI method:
|
||||||
|
```
|
||||||
|
ExecuteConsoleCommand("r.DynamicGlobalIlluminationMethod {GIMethod}")
|
||||||
|
ExecuteConsoleCommand("r.Lumen.DiffuseIndirect.Allow {LumenAllowed}")
|
||||||
|
```
|
||||||
|
2. Execute shadow commands:
|
||||||
|
```
|
||||||
|
ExecuteConsoleCommand("r.Shadow.Virtual.Enable {VSMEnabled}")
|
||||||
|
ExecuteConsoleCommand("sg.ShadowQuality {ShadowQuality}")
|
||||||
|
```
|
||||||
|
3. Execute upscaling commands:
|
||||||
|
```
|
||||||
|
ExecuteConsoleCommand("r.ScreenPercentage {ResolutionScale*100}")
|
||||||
|
ExecuteConsoleCommand("r.TemporalAA.Upsampling {TSREnabled}")
|
||||||
|
// DLSS/FSR plugin-specific commands
|
||||||
|
```
|
||||||
|
4. Execute mesh/Nanite commands:
|
||||||
|
```
|
||||||
|
ExecuteConsoleCommand("r.Nanite {NaniteEnabled}")
|
||||||
|
ExecuteConsoleCommand("r.StaticMeshLODDistanceScale {LODScale}")
|
||||||
|
```
|
||||||
|
5. Execute scalability groups:
|
||||||
|
```
|
||||||
|
ExecuteConsoleCommand("sg.TextureQuality {TextureQuality}")
|
||||||
|
ExecuteConsoleCommand("sg.PostProcessQuality {PostProcessQuality}")
|
||||||
|
ExecuteConsoleCommand("sg.ViewDistanceQuality {ViewDistanceQuality}")
|
||||||
|
ExecuteConsoleCommand("sg.FoliageQuality {FoliageQuality}")
|
||||||
|
ExecuteConsoleCommand("sg.AntiAliasingQuality {AntiAliasingQuality}")
|
||||||
|
```
|
||||||
|
6. Execute post-process toggles:
|
||||||
|
```
|
||||||
|
ExecuteConsoleCommand("r.MotionBlurQuality {MotionBlur}")
|
||||||
|
ExecuteConsoleCommand("r.DepthOfFieldQuality {DoF}")
|
||||||
|
```
|
||||||
|
7. Execute platform-specific overrides from `SPlatformRenderDefaults.ConsoleVariables`
|
||||||
|
8. Set `ActiveState.AppliedConfig = Config`
|
||||||
|
9. Set `ActiveState.ActivePreset = PresetName`
|
||||||
|
10. Set `bReloadNeededOnNextLevel = false`
|
||||||
|
|
||||||
|
#### `ApplyPendingReload` → `void`
|
||||||
|
- **Description:** Called on level transition or game restart. Applies queued pipeline changes.
|
||||||
|
- **Flow:**
|
||||||
|
1. If `bReloadNeededOnNextLevel == false`: return
|
||||||
|
2. Get `SRenderPipelineConfig` for `ActiveState.PendingPreset`
|
||||||
|
3. Call `ApplyRenderConfig(PendingPreset, Config)`
|
||||||
|
4. Broadcast `OnRenderPipelineChanged(PendingPreset, RequiresLevelReload)`
|
||||||
|
|
||||||
|
#### `DetectPlatform` → `EPlatformFamily` *(deprecated — use BPC_PlatformServiceAbstraction.GetPlatformFamily() instead)*
|
||||||
|
- **Description:** Auto-detect the current platform and GPU capability.
|
||||||
|
- **Flow:**
|
||||||
|
1. Check `UGameplayStatics::GetPlatformName()`:
|
||||||
|
- "PS5" → detect if Pro → `PS5_Pro` or `PS5`
|
||||||
|
- "PS4" → `PS4`
|
||||||
|
- "XboxOne" → `Xbox_One`
|
||||||
|
- "XSX" or "XboxSeries" → `Xbox_Series`
|
||||||
|
- "Switch" → `Switch`
|
||||||
|
- "Win64" → check GPU:
|
||||||
|
```cpp
|
||||||
|
IPlatformFile& PlatformFile = FPlatformFileManager::GetPlatformFile();
|
||||||
|
// Check GPU name via GRHIAdapterName
|
||||||
|
// RTX 2000+ / RX 6000+ → PC_High
|
||||||
|
// GTX 900–1600 / iGPU → PC_Low
|
||||||
|
```
|
||||||
|
2. Store result in `DetectedPlatform`
|
||||||
|
3. Return platform
|
||||||
|
|
||||||
|
#### `SetUpscalerMethod` → `void`
|
||||||
|
- **Description:** Override the upscaler independently of quality preset.
|
||||||
|
- **Parameters:** `Method` (EUpscalerMethod), `Quality` (int32)
|
||||||
|
- **Flow:** Updates only the upscaler CVars without touching other settings.
|
||||||
|
|
||||||
|
#### `GetActiveUpscaler` → `EUpscalerMethod`
|
||||||
|
- **Description:** Returns the currently active upscaler. Read-only.
|
||||||
|
|
||||||
|
#### `GetActivePipelineConfig` → `SRenderPipelineConfig`
|
||||||
|
- **Description:** Returns a copy of the currently applied render config. Read-only.
|
||||||
|
|
||||||
|
#### `IsNaniteEnabled` → `bool`
|
||||||
|
- **Description:** Quick check if Nanite is currently active.
|
||||||
|
|
||||||
|
#### `IsLumenEnabled` → `bool`
|
||||||
|
- **Description:** Quick check if Lumen GI is currently active.
|
||||||
|
|
||||||
|
#### `RequiresReloadForPresetChange` → `bool`
|
||||||
|
- **Description:** Check if switching from current preset to a new one would require a level reload.
|
||||||
|
- **Parameters:** `NewPresetName` (FName)
|
||||||
|
|
||||||
|
#### `GetAvailableUpscalers` → `TArray<EUpscalerMethod>`
|
||||||
|
- **Description:** Returns which upscalers are available (based on installed plugins + platform).
|
||||||
|
|
||||||
|
#### `SetDynamicResolutionTarget` → `void`
|
||||||
|
- **Description:** Enable/disable dynamic resolution scaling.
|
||||||
|
- **Parameters:** `TargetFrameTimeMs` (float), `bEnabled` (bool)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Event Dispatchers
|
||||||
|
|
||||||
|
| Dispatcher | Parameters | Bind Access | Description |
|
||||||
|
|------------|-----------|-------------|-------------|
|
||||||
|
| `OnPipelineManagerInitialized` | — | `Public` | Fired after Initialize completes |
|
||||||
|
| `OnRenderPipelineChanged` | `FName PresetName`, `ERenderPipelineChangeType ChangeType` | `Public` | Fired when pipeline configuration changes |
|
||||||
|
| `OnPipelineReloadRequired` | `FName PendingPreset` | `Public` | Fired when a change requires level reload — UI shows warning |
|
||||||
|
| `OnPipelineApplied` | `FName PresetName` | `Public` | Fired after all CVars have been executed |
|
||||||
|
| `OnUpscalerChanged` | `EUpscalerMethod NewMethod`, `int32 Quality` | `Public` | Fired when upscaler is changed |
|
||||||
|
| `OnPlatformDetected` | `EPlatformFamily Platform` | `Public` | Fired after platform detection |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Overridden Events
|
||||||
|
|
||||||
|
### Event: `BeginPlay`
|
||||||
|
- **Description:** Startup. Calls `Initialize()` then conditionally applies pipeline.
|
||||||
|
- **Flow:**
|
||||||
|
1. Call `Initialize()`
|
||||||
|
2. If `bApplyPipelineOnBeginPlay`: apply the default/saved preset
|
||||||
|
3. If game phase is `MainMenu` (not in-game): pipeline can be applied immediately (no reload warning needed)
|
||||||
|
|
||||||
|
### Event: `OnGamePhaseChanged` (bound to `GI_GameFramework`)
|
||||||
|
- **Description:** When transitioning from MainMenu to InGame or between levels, apply any pending pipeline changes.
|
||||||
|
- **Flow:**
|
||||||
|
1. If `bReloadNeededOnNextLevel`: call `ApplyPendingReload()`
|
||||||
|
2. Notify `SS_PlanarCaptureManager` of new pipeline state
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Blueprint Graph Logic Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[BeginPlay / GI_GameFramework.OnGamePhaseChanged] --> B[Initialize]
|
||||||
|
B --> C[DetectPlatform]
|
||||||
|
C --> D[Load DA_RenderPipelineProfile for platform]
|
||||||
|
D --> E[Read saved QualityPreset from SS_SettingsSystem]
|
||||||
|
E --> F[ApplyQualityPreset PresetName]
|
||||||
|
|
||||||
|
G[User changes quality in settings] --> H[WBP_SettingsMenu → SS_SettingsSystem.SetSetting]
|
||||||
|
H --> I[SS_SettingsSystem.OnSettingChanged → BPC_RenderPipelineManager]
|
||||||
|
I --> J[ApplyQualityPreset NewPresetName]
|
||||||
|
J --> K{NonDestructive?}
|
||||||
|
K -->|Yes| L[ApplyRenderConfig immediately]
|
||||||
|
K -->|No - Needs Reload| M[Store as PendingPreset]
|
||||||
|
M --> N[Show UI Warning: 'Settings apply on next level']
|
||||||
|
M --> O[Broadcast OnPipelineReloadRequired]
|
||||||
|
L --> P[Broadcast OnRenderPipelineChanged]
|
||||||
|
|
||||||
|
Q[Level Transition / GamePhase change] --> R{Reload pending?}
|
||||||
|
R -->|Yes| S[ApplyPendingReload]
|
||||||
|
S --> T[ApplyRenderConfig with pending preset]
|
||||||
|
T --> P
|
||||||
|
|
||||||
|
P --> U[Notify SS_PlanarCaptureManager]
|
||||||
|
U --> V[PlanarCapture: adjust GlobalQualityCap]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Communication Matrix
|
||||||
|
|
||||||
|
| Who Talks | How | What Is Sent |
|
||||||
|
|-----------|-----|-------------|
|
||||||
|
| `WBP_SettingsMenu` | `Indirect (SS_SettingsSystem)` | User selects quality preset → `SetSetting("QualityPreset", "High")` |
|
||||||
|
| `SS_SettingsSystem` | `Dispatcher` | `OnSettingChanged("QualityPreset")` → `BPC_RenderPipelineManager.ApplyQualityPreset()` |
|
||||||
|
| `BPC_PerformanceScaler` | `Function Call` | `BPC_RenderPipelineManager.ApplyQualityPreset()` for adaptive scaling |
|
||||||
|
| `BPC_RenderPipelineManager` | `Dispatcher` | `OnRenderPipelineChanged(PresetName)` → `SS_PlanarCaptureManager.AdjustGlobalQualityCap()` |
|
||||||
|
| `BPC_RenderPipelineManager` | `Dispatcher` | `OnPipelineReloadRequired(PendingPreset)` → `WBP_SettingsMenu.ShowReloadWarning()` |
|
||||||
|
| `SS_PlanarCaptureManager` | `Function Call` | `BPC_RenderPipelineManager.IsLumenEnabled()` — adjusts capture Lumen budget |
|
||||||
|
| `WBP_NotificationToast` | `Dispatcher` | `OnPipelineReloadRequired` → show "Settings will apply on next area" toast |
|
||||||
|
| `BPC_AudioAtmosphereController` | `Dispatcher` | `OnRenderPipelineChanged` → adjust audio quality budget |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Integration with Planar Capture System
|
||||||
|
|
||||||
|
When `BPC_RenderPipelineManager` applies a render pipeline change:
|
||||||
|
|
||||||
|
1. If Lumen is **disabled** (baked lightmass mode):
|
||||||
|
- `SS_PlanarCaptureManager` auto-caps `GlobalQualityCap` to `Medium` (no Hero tier Lumen captures)
|
||||||
|
- All `BPC_PlanarCapture` instances set `bEnableLumen = false` on their quality profiles
|
||||||
|
- Capture frame budget is increased (no Lumen overhead → can run more captures at higher FPS)
|
||||||
|
|
||||||
|
2. If Nanite is **disabled** (traditional LOD mode):
|
||||||
|
- Captures render faster (no Nanite rasterization overhead in capture pass)
|
||||||
|
- `SS_PlanarCaptureManager` can increase `GlobalQualityCap` by one tier
|
||||||
|
|
||||||
|
3. If upscaler is **active** (DLSS/FSR/TSR):
|
||||||
|
- Main view renders at lower internal resolution
|
||||||
|
- Planar captures render at configured quality tier resolution (independent of upscaler)
|
||||||
|
|
||||||
|
4. `BPC_PlanarCapture.ApplyQualityTier()` checks `BPC_RenderPipelineManager.IsLumenEnabled()`:
|
||||||
|
```
|
||||||
|
If !IsLumenEnabled() AND profile.bEnableLumen:
|
||||||
|
Force bEnableLumen = false // Prevent capture from trying to enable Lumen
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Validation / Testing Checklist
|
||||||
|
|
||||||
|
- [ ] `DetectPlatform` correctly identifies PS5, PS4, Xbox Series, Xbox One, PC_High, PC_Low
|
||||||
|
- [ ] `ApplyQualityPreset("Low")` sets GI to None/Baked, switches to CSM, disables Nanite
|
||||||
|
- [ ] `ApplyQualityPreset("High")` sets GI to Lumen, switches to VSM, enables Nanite
|
||||||
|
- [ ] Switching from High→Low mid-game shows "requires level reload" warning
|
||||||
|
- [ ] Switching from High→Low in Main Menu applies immediately (no warning needed)
|
||||||
|
- [ ] Switching Low→High mid-game: PendingPreset stored, applies on next level load
|
||||||
|
- [ ] Planar capture budget adjusts: lower cap when Lumen is on, higher when off
|
||||||
|
- [ ] DLSS is available on PC_High with NVIDIA GPU — shows in `GetAvailableUpscalers()`
|
||||||
|
- [ ] FSR is available on all platforms — always in available list
|
||||||
|
- [ ] PSSR only available when `DetectedPlatform == PS5_Pro`
|
||||||
|
- [ ] NIS is available on Switch — fallback on non-NVIDIA PC
|
||||||
|
- [ ] `SetUpscalerMethod` independently changes upscaler without affecting other settings
|
||||||
|
- [ ] `GetActivePipelineConfig()` returns current state accurately after apply
|
||||||
|
- [ ] Edge case: `PlatformProfileMap` missing platform → logs error, falls back to `PC_High`
|
||||||
|
- [ ] Edge case: Invalid preset name → logs warning, no CVars changed
|
||||||
|
- [ ] Edge case: Console variable execution fails → logs error per failed CVar, continues
|
||||||
|
- [ ] Edge case: Hot-swap GPU (eGPU) → `DetectPlatform` called again, pipeline may change
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Manual Implementation Guide
|
||||||
|
|
||||||
|
### 11.1 Class Setup
|
||||||
|
1. Create Blueprint Class: parent `ActorComponent`, name `BPC_RenderPipelineManager`
|
||||||
|
2. Path: `Content/Framework/Settings/`
|
||||||
|
3. Add all variables, enums, structs, and event dispatchers from this spec
|
||||||
|
|
||||||
|
### 11.2 Key UE5 Nodes
|
||||||
|
| Node | Where to Find | Used For |
|
||||||
|
|------|---------------|----------|
|
||||||
|
| `Execute Console Command` | Right-click → "Execute Console Command" | Applying all CVar changes |
|
||||||
|
| `Get Platform Name` | Right-click → "Get Platform Name" | Platform detection |
|
||||||
|
| `Get Game User Settings` | Right-click → "Get Game User Settings" | Accessing scalability API |
|
||||||
|
| `Set Overall Scalability Level` | On GameUserSettings | Bulk quality tier application |
|
||||||
|
| `Get Game Instance` → `Get Subsystem(SS_SettingsSystem)` | Subsystem access | Reading/writing quality setting |
|
||||||
|
| `Get Game Instance` → `Get Subsystem(SS_PlanarCaptureManager)` | Subsystem access | Adjust capture budget |
|
||||||
|
| `Switch on EPlatformFamily` | Right-click → "Switch" | Platform-specific logic |
|
||||||
|
| `Make SRenderPipelineConfig` | Right-click → "Make Struct" | Building config structs |
|
||||||
|
|
||||||
|
### 11.3 Node-by-Node: ApplyRenderConfig
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: ApplyRenderConfig(PresetName, Config)]
|
||||||
|
Step 1: Get Owner → Cast to PlayerController → cache
|
||||||
|
Step 2: Break SRenderPipelineConfig → get all fields
|
||||||
|
Step 3: Switch on Config.GIMethod:
|
||||||
|
Lumen_GI → ExecuteConsoleCommand("r.DynamicGlobalIlluminationMethod 1")
|
||||||
|
ExecuteConsoleCommand("r.Lumen.Reflections.Allow 1")
|
||||||
|
Baked_Lightmass → ExecuteConsoleCommand("r.DynamicGlobalIlluminationMethod 0")
|
||||||
|
ExecuteConsoleCommand("r.Lumen.Reflections.Allow 0")
|
||||||
|
SSGI → ExecuteConsoleCommand("r.DynamicGlobalIlluminationMethod 2")
|
||||||
|
None → ExecuteConsoleCommand("r.DynamicGlobalIlluminationMethod 0")
|
||||||
|
Step 4: ExecuteConsoleCommand("r.Shadow.Virtual.Enable {VSM?1:0}")
|
||||||
|
Step 5: ExecuteConsoleCommand("sg.ShadowQuality {ShadowQuality}")
|
||||||
|
Step 6: ExecuteConsoleCommand("r.ScreenPercentage {ResolutionScale*100}")
|
||||||
|
Step 7: Switch on Config.Upscaler:
|
||||||
|
TSR → ExecuteConsoleCommand("r.TemporalAA.Upsampling 1")
|
||||||
|
DLSS → ExecuteConsoleCommand("r.NGX.DLSS.Enable 1")
|
||||||
|
ExecuteConsoleCommand("r.NGX.DLSS.Quality {UpscalerQuality}")
|
||||||
|
FSR → ExecuteConsoleCommand("r.FidelityFX.FSR2.Enabled 1")
|
||||||
|
NIS → ExecuteConsoleCommand("r.NIS.Enable 1")
|
||||||
|
Step 8: ExecuteConsoleCommand("r.Nanite {NaniteEnabled?1:0}")
|
||||||
|
Step 9: Execute Console Command "sg.TextureQuality {TextureQuality}"
|
||||||
|
Execute Console Command "sg.PostProcessQuality {PostProcessQuality}"
|
||||||
|
Execute Console Command "sg.ViewDistanceQuality {ViewDistanceQuality}"
|
||||||
|
Execute Console Command "sg.FoliageQuality {FoliageQuality}"
|
||||||
|
Step 10: ExecuteConsoleCommand("r.MotionBlurQuality {MotionBlur?2:0}")
|
||||||
|
Step 11: ExecuteConsoleCommand("r.DepthOfFieldQuality {DoF?2:0}")
|
||||||
|
Step 12: Set ActiveState.AppliedConfig = Config
|
||||||
|
Step 13: Set ActiveState.ActivePreset = PresetName
|
||||||
|
Step 14: Set bReloadNeededOnNextLevel = false
|
||||||
|
Step 15: Broadcast OnPipelineApplied(PresetName)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.4 Networking
|
||||||
|
- **Local client only.** Render pipeline is per-client hardware. No replication.
|
||||||
|
- On listen servers: the server-host player runs their own render pipeline; other clients run theirs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Blueprint Build Checklist
|
||||||
|
|
||||||
|
- [ ] Create Blueprint class: `BPC_RenderPipelineManager` (parent: `ActorComponent`)
|
||||||
|
- [ ] Add enums: `ERenderPipelineMethod`, `EShadowMethod`, `EReflectionMethod`, `EUpscalerMethod`, `EMeshStrategy`, `EPlatformFamily`, `ERenderPipelineChangeType`
|
||||||
|
- [ ] Add structs: `SRenderPipelineConfig`, `SPlatformRenderDefaults`, `SActivePipelineState`
|
||||||
|
- [ ] Add all variables from Section 3
|
||||||
|
- [ ] Build `BeginPlay` → `Initialize` chain
|
||||||
|
- [ ] Implement `DetectPlatform` with Switch on Platform Name
|
||||||
|
- [ ] Implement `ApplyQualityPreset` with reload detection logic
|
||||||
|
- [ ] Implement `ApplyRenderConfig` with full CVar execution
|
||||||
|
- [ ] Implement `ApplyPendingReload` bound to `GI_GameFramework.OnGamePhaseChanged`
|
||||||
|
- [ ] Implement `SetUpscalerMethod` / `GetAvailableUpscalers` / `IsLumenEnabled` / `IsNaniteEnabled`
|
||||||
|
- [ ] Create all 6 event dispatchers
|
||||||
|
- [ ] Bind to `SS_SettingsSystem.OnSettingChanged` (QualityPreset key)
|
||||||
|
- [ ] Bind to `GI_GameFramework.OnGamePhaseChanged`
|
||||||
|
- [ ] Create at least one `DA_RenderPipelineProfile` instance per target platform
|
||||||
|
- [ ] Test: Low preset disables Lumen, Nanite, switches to CSM
|
||||||
|
- [ ] Test: High preset enables Lumen, Nanite, VSM
|
||||||
|
- [ ] Test: Mid-session pipeline switch shows reload warning
|
||||||
|
- [ ] Test: Main Menu pipeline switch applies immediately
|
||||||
|
- [ ] Test: Planar capture quality cap adjusts on pipeline change
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Blueprint Spec: Render Pipeline Manager. Conforms to TEMPLATE.md v2.0 — part of the UE5 Modular Game Framework, SETTINGS layer.*
|
||||||
@@ -51,7 +51,8 @@ The Modular Game Framework uses `UDataAsset` (and subclass `UPrimaryDataAsset` w
|
|||||||
| 11 | `DA_ScareEvent` | Adaptive | `BPC_ScareEventSystem` |
|
| 11 | `DA_ScareEvent` | Adaptive | `BPC_ScareEventSystem` |
|
||||||
| 12 | `DA_RoomMutation` | Adaptive | `BPC_AdaptiveEnvironmentDirector` |
|
| 12 | `DA_RoomMutation` | Adaptive | `BPC_AdaptiveEnvironmentDirector` |
|
||||||
| 13 | `DA_BehaviourVariant` | AI | `BPC_BehaviourVariantSelector` |
|
| 13 | `DA_BehaviourVariant` | AI | `BPC_BehaviourVariantSelector` |
|
||||||
| 14 | `DA_HapticProfile` | Settings | `BPC_HapticsController` |
|
| 14 | `DA_HapticProfile` | Settings | `BPC_HapticsController` (148) |
|
||||||
|
| 15 | `DA_RenderPipelineProfile` | Settings | `BPC_RenderPipelineManager` (149) |
|
||||||
| 15 | `DA_AdaptationRule` | Adaptive | `BPC_DifficultyManager` |
|
| 15 | `DA_AdaptationRule` | Adaptive | `BPC_DifficultyManager` |
|
||||||
| 16 | `DA_EquipmentConfig` | Inventory | `BPC_EquipmentSlotSystem` |
|
| 16 | `DA_EquipmentConfig` | Inventory | `BPC_EquipmentSlotSystem` |
|
||||||
| 17 | `DA_PuzzleData` | Interaction | `BP_PuzzleDeviceActor` |
|
| 17 | `DA_PuzzleData` | Interaction | `BP_PuzzleDeviceActor` |
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# DA_EquipmentConfig — Data Asset
|
# DA_EquipmentConfig — Data Asset
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Stub** — `Source/PG_Framework/Public/Inventory/DA_EquipmentConfig.h` provides `FDamageTypeResistance` struct (DamageType GameplayTag + Resistance float), `Durability`, `Weight`, and `GetResistance()` query. **Create Data Asset instances** (Right-click → Miscellaneous → Data Asset → `DA_EquipmentConfig`), one per equipment piece. Fill in damage resistances per type. See `docs/developer/cpp-integration-guide.md`.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
**Parent Class:** `UDataAsset`
|
**Parent Class:** `UDataAsset`
|
||||||
**Dependencies:** [`BPC_EquipmentSlotSystem`](../04-inventory/BPC_EquipmentSlotSystem.md)
|
**Dependencies:** [`BPC_EquipmentSlotSystem`](../04-inventory/BPC_EquipmentSlotSystem.md)
|
||||||
**Purpose:** Defines equipment slot configurations — which item categories map to which slots, socket names, and auto-equip rules.
|
**Purpose:** Defines equipment slot configurations — which item categories map to which slots, socket names, and auto-equip rules.
|
||||||
|
|||||||
@@ -1,52 +1,178 @@
|
|||||||
# DA_HapticProfile — Data Asset
|
# DA_HapticProfile — Data Asset
|
||||||
|
|
||||||
**Parent Class:** `UDataAsset`
|
**Parent Class:** `UPrimaryDataAsset`
|
||||||
**Dependencies:** [`BPC_HapticsController`](../12-settings/)
|
**Dependencies:** [`BPC_HapticsController`](../12-settings/148_BPC_HapticsController.md)
|
||||||
**Purpose:** Defines haptic feedback profiles for controller vibration and force feedback effects tied to gameplay events.
|
**Purpose:** Defines haptic feedback profiles for controller vibration and force feedback effects tied to gameplay events. Each profile maps a GameplayTag to a platform-specific `UForceFeedbackEffect` waveform asset with configurable intensity, duration, motor mask, and priority.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Enums Used
|
||||||
|
|
||||||
|
| Enum | Defined In | Values |
|
||||||
|
|------|-----------|--------|
|
||||||
|
| `EHapticEvent` | `BPC_HapticsController` (148) | Damage, HeavyDamage, Heartbeat, WeaponFire, WeaponReload, MeleeImpact, Footstep, Explosion, PickupItem, DropItem, GrabObject, ReleaseObject, ScareEvent, AmbientPulse, UI_Confirm, UI_Navigate, LowHealth, StaminaExhausted, Death |
|
||||||
|
| `EHapticMotor` | `BPC_HapticsController` (148) | Left, Right, Both |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Variables / Structure
|
## Variables / Structure
|
||||||
|
|
||||||
|
### Core Fields
|
||||||
|
|
||||||
| Field | Type | Description |
|
| Field | Type | Description |
|
||||||
|-------|------|-------------|
|
|-------|------|-------------|
|
||||||
| `ProfileTag` | `FGameplayTag` | Unique haptic profile identifier |
|
| `ProfileTag` | `FGameplayTag` | Unique haptic profile identifier (e.g., `Haptic.Damage.Heavy`) |
|
||||||
| `EventType` | `EHapticEvent` | Damage, Heartbeat, Explosion, Footstep, WeaponFire, AmbientPulse |
|
| `EventType` | `EHapticEvent` | Event category this profile handles |
|
||||||
| `IntensityCurve` | `UCurveFloat*` | Vibration intensity over time |
|
| `ForceFeedbackEffect_Generic` | `UForceFeedbackEffect*` | Waveform curve asset for PC/Xbox generic controllers |
|
||||||
| `Duration` | `float` | Total haptic effect duration (seconds) |
|
| `ForceFeedbackEffect_PS5` | `UForceFeedbackEffect*` | Waveform curve asset for PS5 DualSense (higher fidelity) |
|
||||||
| `MotorMask` | `EHapticMotor` | Left, Right, Both |
|
| `ForceFeedbackEffect_Xbox` | `UForceFeedbackEffect*` | Waveform curve asset for Xbox controllers (optional override) |
|
||||||
| `Priority` | `int32` | Higher priority overrides lower during conflicts |
|
| `IntensityCurve` | `UCurveFloat*` | Optional: vibration intensity over time (0.0–1.0) |
|
||||||
| `bCanInterrupt` | `bool` | Can this effect be interrupted by higher priority |
|
| `Duration` | `float` | Total haptic effect duration in seconds |
|
||||||
| `PlatformMinIntensity` | `float` | Minimum intensity per platform capability |
|
| `MotorMask` | `EHapticMotor` | Left motor, Right motor, or Both |
|
||||||
|
| `Priority` | `int32` | Higher priority overrides lower during conflicts (0 = lowest, 100 = highest) |
|
||||||
|
| `bCanInterrupt` | `bool` | Can this effect be interrupted by a higher-priority effect? |
|
||||||
|
| `bIgnoreTimeDilation` | `bool` | Should this play at real-time regardless of game speed? |
|
||||||
|
|
||||||
|
### Platform-Specific Fields
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `PlatformMinIntensity` | `float` | Minimum intensity threshold before effect is felt (0.0–1.0) |
|
||||||
|
| `DualSense_TriggerSide` | `FName` | PS5 adaptive trigger side ("Left", "Right", "None") |
|
||||||
|
| `DualSense_TriggerEffect` | `FName` | PS5 trigger effect type ("Resistance", "Vibration", "WeaponFire", "BowDraw", "None") |
|
||||||
|
| `DualSense_TriggerStartPosition` | `int32` | PS5 trigger position where effect begins (0–9) |
|
||||||
|
| `DualSense_TriggerStrength` | `int32` | PS5 trigger effect strength (0–8) |
|
||||||
|
|
||||||
|
### Heartbeat-Specific Fields
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `bIsHeartbeatProfile` | `bool` | Whether this profile is used for heartbeat pulse effects |
|
||||||
|
| `TargetBPMRange_Min` | `float` | Minimum BPM this profile handles |
|
||||||
|
| `TargetBPMRange_Max` | `float` | Maximum BPM this profile handles |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Gameplay Tags
|
## Gameplay Tags
|
||||||
- Namespace: `Haptic.<Event>` (e.g., `Haptic.Damage.Heavy`, `Haptic.Heartbeat`)
|
|
||||||
|
- Namespace: `Haptic.<Category>.<Subcategory>`
|
||||||
|
- Examples: `Haptic.Damage.Heavy`, `Haptic.Heartbeat.Normal`, `Haptic.WeaponFire.Pistol`
|
||||||
|
- All tags must be registered in `DT_Tags_Player.csv` for validation at startup
|
||||||
|
|
||||||
|
### Full Tag Hierarchy
|
||||||
|
|
||||||
|
```
|
||||||
|
Haptic.
|
||||||
|
├── Damage.Light
|
||||||
|
├── Damage.Heavy
|
||||||
|
├── Damage.Critical
|
||||||
|
├── Heartbeat.Normal
|
||||||
|
├── Heartbeat.Fast
|
||||||
|
├── Heartbeat.Panic
|
||||||
|
├── WeaponFire.Pistol
|
||||||
|
├── WeaponFire.Shotgun
|
||||||
|
├── WeaponReload
|
||||||
|
├── MeleeImpact.Crowbar
|
||||||
|
├── MeleeImpact.Default
|
||||||
|
├── Footstep.Tile
|
||||||
|
├── Footstep.Wood
|
||||||
|
├── Footstep.Concrete
|
||||||
|
├── Footstep.Metal
|
||||||
|
├── Footstep.Gravel
|
||||||
|
├── Explosion
|
||||||
|
├── Pickup.Item
|
||||||
|
├── Pickup.Weapon
|
||||||
|
├── Grab
|
||||||
|
├── Release.Throw
|
||||||
|
├── Scare.JumpScare
|
||||||
|
├── Scare.TensionRise
|
||||||
|
├── Ambient.Void
|
||||||
|
├── Ambient.Default
|
||||||
|
├── LowHealth
|
||||||
|
├── StaminaExhausted
|
||||||
|
├── Death
|
||||||
|
├── UI.Confirm
|
||||||
|
└── UI.Navigate
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Validation Rules
|
## Validation Rules
|
||||||
- `ProfileTag` must be unique
|
|
||||||
- `Duration` must be > 0
|
- `ProfileTag` must be unique across all profiles
|
||||||
- `IntensityCurve` must be valid
|
- `Duration` must be > 0.0
|
||||||
|
- At least one `ForceFeedbackEffect_*` must be assigned (Generic falls back for all platforms)
|
||||||
|
- `Priority` must be 0–100
|
||||||
|
- If `bIsHeartbeatProfile` is true, `TargetBPMRange_Min` and `TargetBPMRange_Max` must be set
|
||||||
|
- If `DualSense_TriggerSide != "None"`, trigger effect fields must be valid
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Example Data
|
## Example Data
|
||||||
|
|
||||||
|
### Damage Profile — Heavy Hit
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| ProfileTag | `Haptic.Damage.Critical` |
|
| `ProfileTag` | `Haptic.Damage.Heavy` |
|
||||||
| EventType | Damage |
|
| `EventType` | `HeavyDamage` |
|
||||||
| Duration | 0.5 |
|
| `Duration` | `0.4` |
|
||||||
| MotorMask | Both |
|
| `MotorMask` | `Both` |
|
||||||
| Priority | 10 |
|
| `Priority` | `80` |
|
||||||
|
| `bCanInterrupt` | `true` |
|
||||||
|
| `bIgnoreTimeDilation` | `true` |
|
||||||
|
| `ForceFeedbackEffect_Generic` | `FFE_Damage_Heavy` |
|
||||||
|
| `ForceFeedbackEffect_PS5` | `FFE_Damage_Heavy_PS5` |
|
||||||
|
|
||||||
|
### Heartbeat Profile — Normal
|
||||||
|
| Field | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| `ProfileTag` | `Haptic.Heartbeat.Normal` |
|
||||||
|
| `EventType` | `Heartbeat` |
|
||||||
|
| `Duration` | `0.1` |
|
||||||
|
| `MotorMask` | `Left` |
|
||||||
|
| `Priority` | `30` |
|
||||||
|
| `bCanInterrupt` | `true` |
|
||||||
|
| `bIsHeartbeatProfile` | `true` |
|
||||||
|
| `TargetBPMRange_Min` | `60.0` |
|
||||||
|
| `TargetBPMRange_Max` | `90.0` |
|
||||||
|
| `ForceFeedbackEffect_Generic` | `FFE_Heartbeat` |
|
||||||
|
|
||||||
|
### Weapon Fire — Pistol
|
||||||
|
| Field | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| `ProfileTag` | `Haptic.WeaponFire.Pistol` |
|
||||||
|
| `EventType` | `WeaponFire` |
|
||||||
|
| `Duration` | `0.08` |
|
||||||
|
| `MotorMask` | `Right` |
|
||||||
|
| `Priority` | `50` |
|
||||||
|
| `DualSense_TriggerSide` | `Right` |
|
||||||
|
| `DualSense_TriggerEffect` | `WeaponFire` |
|
||||||
|
| `DualSense_TriggerStartPosition` | `4` |
|
||||||
|
| `DualSense_TriggerStrength` | `6` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Consumed By
|
## Consumed By
|
||||||
- [`BPC_HapticsController`](../12-settings/)
|
- [`BPC_HapticsController`](../12-settings/148_BPC_HapticsController.md) — loads all profiles into `HapticProfileMap` at initialization
|
||||||
|
|
||||||
|
## Referenced By
|
||||||
|
- `BPC_HealthSystem` (08) — damage haptics
|
||||||
|
- `BPC_FirearmSystem` (74) — weapon fire haptics
|
||||||
|
- `BPC_MeleeSystem` (76) — melee impact haptics
|
||||||
|
- `BPC_ScareEventSystem` (101) — scare event haptics
|
||||||
|
- `BPC_MovementStateSystem` (11) — footstep haptics
|
||||||
|
- `BPC_ReloadSystem` (78) — reload haptics
|
||||||
|
- `BPC_PhysicsDragSystem` (22) — grab/release haptics
|
||||||
|
- `BP_ItemPickup` (25) — pickup haptics
|
||||||
|
- `BPC_DeathHandlingSystem` (39) — death haptics
|
||||||
|
- `BPC_StaminaSystem` (09) — stamina exhausted haptics
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Reuse Notes
|
## Reuse Notes
|
||||||
- Haptic profiles are platform-agnostic; translation handled by PlatformServiceAbstraction
|
- Haptic profiles are platform-agnostic in design; `BPC_HapticsController` selects the correct `UForceFeedbackEffect` asset at runtime based on `CurrentPlatform`.
|
||||||
|
- Designers create FFE waveform curves in the Content Browser — no Blueprint changes needed for tuning.
|
||||||
|
- For heartbeat profiles: create 3 instances (Normal 60-90 BPM, Fast 90-140 BPM, Panic 140-180 BPM). `BPC_HapticsController` auto-selects based on current BPM.
|
||||||
|
- For footstep profiles: create one per surface type. `BPC_MovementStateSystem` selects based on physical surface trace result.
|
||||||
|
- Priority guidelines: 0–20 (ambient/footsteps), 30–50 (weapons/reload/pickups), 60–80 (damage), 90–100 (scares/death).
|
||||||
|
- The `Haptic.` namespace should be registered in `DT_Tags_Player.csv` so `DA_GameTagRegistry` validates them at startup.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Primary Data Asset that defines all Enhanced Input bindings across three platfor
|
|||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
- **Requires:** None (self-contained Data Asset)
|
- **Requires:** None (self-contained Data Asset)
|
||||||
- **Required By:** [`SS_EnhancedInputManager`](docs/blueprints/15-input/128_SS_EnhancedInputManager.md) (loads and caches this), [`SS_SettingsSystem`](docs/blueprints/12-settings/105_SS_SettingsSystem.md) (references for key rebinding)
|
- **Required By:** [`SS_EnhancedInputManager`](docs/blueprints/15-input/128_SS_EnhancedInputManager.md) (loads and caches this), [`SS_SettingsSystem`](docs/blueprints/12-settings/105_SS_SettingsSystem.md) (references for key rebinding), [`BPC_HapticsController`](docs/blueprints/12-settings/148_BPC_HapticsController.md) (reads `bEnableControllerRumble` for haptics master toggle)
|
||||||
- **Engine/Plugin Requirements:** Enhanced Input Plugin, `UInputMappingContext`, `UInputAction`
|
- **Engine/Plugin Requirements:** Enhanced Input Plugin, `UInputMappingContext`, `UInputAction`
|
||||||
|
|
||||||
## Class Info
|
## Class Info
|
||||||
@@ -68,7 +68,7 @@ Primary Data Asset that defines all Enhanced Input bindings across three platfor
|
|||||||
| `PlatformProfiles` | `TArray<S_PlatformProfile>` | `Empty` | `Profiles` | One profile per platform (3 total) |
|
| `PlatformProfiles` | `TArray<S_PlatformProfile>` | `Empty` | `Profiles` | One profile per platform (3 total) |
|
||||||
| `ContextDefinitions` | `TArray<S_ContextDefinition>` | `Empty` | `Contexts` | IMC asset references and defaults |
|
| `ContextDefinitions` | `TArray<S_ContextDefinition>` | `Empty` | `Contexts` | IMC asset references and defaults |
|
||||||
| `GlobalDeadZone` | `Float` | `0.15` | `Global` | Fallback analog stick dead zone |
|
| `GlobalDeadZone` | `Float` | `0.15` | `Global` | Fallback analog stick dead zone |
|
||||||
| `bEnableControllerRumble` | `Bool` | `true` | `Global` | Master toggle for force feedback |
|
| `bEnableControllerRumble` | `Bool` | `true` | `Global` | Master toggle for force feedback (read by BPC_HapticsController) |
|
||||||
| `bSwapSticksForLeftHanded` | `Bool` | `false` | `Accessibility` | Swap left/right stick for accessibility |
|
| `bSwapSticksForLeftHanded` | `Bool` | `false` | `Accessibility` | Swap left/right stick for accessibility |
|
||||||
| `AxisInvertSettings` | `TMap<FName, Bool>` | `Empty` | `Accessibility` | Per-action axis inversion (e.g., "IA_Look" = InvertY) |
|
| `AxisInvertSettings` | `TMap<FName, Bool>` | `Empty` | `Accessibility` | Per-action axis inversion (e.g., "IA_Look" = InvertY) |
|
||||||
|
|
||||||
|
|||||||
298
docs/blueprints/14-data-assets/DA_RenderPipelineProfile.md
Normal file
298
docs/blueprints/14-data-assets/DA_RenderPipelineProfile.md
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
# DA_RenderPipelineProfile — Data Asset
|
||||||
|
|
||||||
|
**Parent Class:** `UPrimaryDataAsset`
|
||||||
|
**Dependencies:** [`BPC_RenderPipelineManager`](../12-settings/149_BPC_RenderPipelineManager.md)
|
||||||
|
**Purpose:** Defines a complete rendering pipeline configuration for a specific quality tier on a specific platform family. Each profile maps quality presets (Low/Medium/High/Ultra/Cinematic) to concrete UE5 render settings: global illumination method, shadow method, reflection method, anti-aliasing, upscaling, Nanite/LOD strategy, and post-process presets. Designers configure these per-platform so PS4 gets baked light + CSM shadows while PS5 gets Lumen + Virtual Shadow Maps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design Rationale
|
||||||
|
|
||||||
|
UE5's rendering pipeline is **not runtime-switchable** for certain features (Lumen ON/OFF, Nanite ON/OFF). These must be configured before the engine initializes or before a level loads. This Data Asset serves as the single source of truth for the render pipeline configuration. `BPC_RenderPipelineManager` reads the appropriate profile based on the player's quality selection and platform, then applies console variables through UE5's `IConsoleManager` or `UGameUserSettings` scalability API.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Enums
|
||||||
|
|
||||||
|
### `ERenderPipelineMethod`
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `Lumen_GI = 0` | Lumen Global Illumination + Reflection |
|
||||||
|
| `Baked_Lightmass = 1` | Static baked lightmaps + reflection captures |
|
||||||
|
| `SSGI = 2` | Screen-space global illumination (mid-range fallback) |
|
||||||
|
| `None = 3` | No GI — unlit or emissive-only |
|
||||||
|
|
||||||
|
### `EShadowMethod`
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `VirtualShadowMaps = 0` | UE5 Virtual Shadow Maps (Nanite-compatible) |
|
||||||
|
| `CascadedShadowMaps = 1` | Traditional CSM (low-end compatible) |
|
||||||
|
| `DistanceFieldShadows = 2` | Ray-traced distance field shadows |
|
||||||
|
| `None = 3` | No dynamic shadows |
|
||||||
|
|
||||||
|
### `EReflectionMethod`
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `Lumen_Reflections = 0` | Lumen reflection system |
|
||||||
|
| `ScreenSpace = 1` | Screen-space reflections (SSR) |
|
||||||
|
| `ReflectionCaptures = 2` | Static sphere/box reflection captures (baked) |
|
||||||
|
| `None = 3` | No reflections |
|
||||||
|
|
||||||
|
### `EUpscalerMethod`
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `TSR = 0` | Temporal Super Resolution (UE5 built-in) |
|
||||||
|
| `DLSS = 1` | NVIDIA DLSS 3 (requires plugin + RTX GPU) |
|
||||||
|
| `FSR = 2` | AMD FidelityFX Super Resolution 2/3 |
|
||||||
|
| `PSSR = 3` | PlayStation Spectral Super Resolution (PS5 Pro only) |
|
||||||
|
| `XeSS = 4` | Intel Xe Super Sampling |
|
||||||
|
| `NIS = 5` | NVIDIA Image Scaling (vendor-agnostic) |
|
||||||
|
| `None = 6` | Native resolution — no upscaling |
|
||||||
|
| `TAAU = 7` | Temporal Anti-Aliasing Upsample (legacy) |
|
||||||
|
|
||||||
|
### `EMeshStrategy`
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `Nanite = 0` | Nanite virtualized geometry (UE5 only) |
|
||||||
|
| `Traditional_LOD = 1` | Standard static mesh LODs |
|
||||||
|
| `ProxyGeometry = 2` | HLOD/proxy geometry for distant objects |
|
||||||
|
|
||||||
|
### `EPlatformFamily`
|
||||||
|
| Value | Description | Default Render Method |
|
||||||
|
|-------|-------------|----------------------|
|
||||||
|
| `PC_High = 0` | PC with RTX 2000+ / RX 6000+ | Lumen + Nanite |
|
||||||
|
| `PC_Low = 1` | PC with GTX 900–1600 / iGPU | Baked + CSM |
|
||||||
|
| `PS5 = 2` | PlayStation 5 | Lumen + Nanite |
|
||||||
|
| `PS5_Pro = 3` | PlayStation 5 Pro (PSSR support) | Lumen + Nanite + PSSR |
|
||||||
|
| `PS4 = 4` | PlayStation 4 / PS4 Pro | Baked + CSM + TAAU |
|
||||||
|
| `Xbox_Series = 5` | Xbox Series X\|S | Lumen + Nanite |
|
||||||
|
| `Xbox_One = 6` | Xbox One / One X | Baked + CSM + TAAU |
|
||||||
|
| `Switch_2 = 7` | Nintendo Switch 2 (when available) | Baked + CSM + FSR |
|
||||||
|
| `Switch = 8` | Nintendo Switch | Baked + ProxyGeometry + NIS |
|
||||||
|
| `SteamDeck = 9` | Steam Deck / handheld PC | Baked/SSGI + CSM + FSR |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Structs
|
||||||
|
|
||||||
|
### `SRenderPipelineConfig`
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `QualityPresetName` | `FName` | Identifier ("Low", "Medium", "High", "Ultra", "Cinematic") |
|
||||||
|
| `GIMethod` | `ERenderPipelineMethod` | Global illumination method |
|
||||||
|
| `ShadowMethod` | `EShadowMethod` | Shadow rendering method |
|
||||||
|
| `ReflectionMethod` | `EReflectionMethod` | Reflection rendering method |
|
||||||
|
| `Upscaler` | `EUpscalerMethod` | Upscaling method |
|
||||||
|
| `UpscalerQuality` | `int32` | Upscaler quality tier (0=Performance, 1=Balanced, 2=Quality, 3=UltraQuality) |
|
||||||
|
| `MeshStrategy` | `EMeshStrategy` | Geometry rendering strategy |
|
||||||
|
| `ResolutionScale` | `float` | Screen percentage (0.5–1.0) |
|
||||||
|
| `DynamicResolutionTarget` | `float` | Target ms for dynamic resolution (0=disabled) |
|
||||||
|
| `ShadowQuality` | `int32` | Shadow resolution/distance tier (0=NONE, 1=LOW, 2=MED, 3=HIGH, 4=EPIC) |
|
||||||
|
| `TextureQuality` | `int32` | Texture streaming pool tier (0–3) |
|
||||||
|
| `PostProcessQuality` | `int32` | Post-process complexity (0–3) |
|
||||||
|
| `ViewDistanceQuality` | `int32` | View distance tier (0–3) |
|
||||||
|
| `FoliageQuality` | `int32` | Foliage density tier (0–3) |
|
||||||
|
| `AntiAliasingQuality` | `int32` | AA quality tier (0–3) |
|
||||||
|
| `bEnableMotionBlur` | `bool` | Motion blur toggle |
|
||||||
|
| `bEnableDepthOfField` | `bool` | Depth of field toggle |
|
||||||
|
| `bEnableVolumetricClouds` | `bool` | Volumetric cloud rendering |
|
||||||
|
| `bEnableVirtualTextures` | `bool` | Runtime virtual textures |
|
||||||
|
| `bEnableHWRT` | `bool` | Hardware ray tracing (requires RT-capable GPU) |
|
||||||
|
| `bRequiresLevelReload` | `bool` | TRUE if this setting requires a level reload to take effect |
|
||||||
|
| `TargetFPS` | `int32` | Frame rate target (0=unlimited) |
|
||||||
|
| `bEnableVSync` | `bool` | Vertical sync |
|
||||||
|
| `Description` | `FText` | Human-readable description for settings UI |
|
||||||
|
|
||||||
|
### `SPlatformRenderDefaults`
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `Platform` | `EPlatformFamily` | Target platform |
|
||||||
|
| `DefaultQualityPreset` | `FName` | Preset to use on first launch ("Medium" for most) |
|
||||||
|
| `SupportedPipelines` | `TArray<ERenderPipelineMethod>` | Which GI methods this platform supports |
|
||||||
|
| `SupportedUpscalers` | `TArray<EUpscalerMethod>` | Which upscalers this platform supports |
|
||||||
|
| `MaxResolutionScale` | `float` | Maximum resolution scale (e.g., 1.0 for PS5, 0.7 for Switch) |
|
||||||
|
| `MaxFPS` | `int32` | Maximum recommended FPS target |
|
||||||
|
| `VRAMBudget_MB` | `int32` | Typical VRAM budget for this platform |
|
||||||
|
| `bSupportsNanite` | `bool` | Platform supports Nanite |
|
||||||
|
| `bSupportsLumen` | `bool` | Platform supports Lumen |
|
||||||
|
| `bSupportsHWRT` | `bool` | Platform supports hardware ray tracing |
|
||||||
|
| `ConsoleVariables` | `TMap<FString, FString>` | Additional platform-specific CVar overrides |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Variables
|
||||||
|
|
||||||
|
### Configuration (Instance Editable)
|
||||||
|
|
||||||
|
| Variable | Type | Default | Category | Description |
|
||||||
|
|----------|------|---------|----------|-------------|
|
||||||
|
| `PlatformFamily` | `EPlatformFamily` | `PC_High` | `Config` | Which platform this profile targets |
|
||||||
|
| `PlatformDefaults` | `SPlatformRenderDefaults` | — | `Config` | Platform-specific capabilities and defaults |
|
||||||
|
| `QualityPresets` | `TMap<FName, SRenderPipelineConfig>` | `Empty` | `Config` | Maps quality preset names to render configs |
|
||||||
|
| `bAllowDynamicPresetSwitch` | `bool` | `true` | `Config` | Whether quality can change without level reload |
|
||||||
|
| `bAutoDetectPlatform` | `bool` | `true` | `Config` | Auto-detect platform at startup |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Default Preset Tables (Per-Platform)
|
||||||
|
|
||||||
|
### PS5 (EPlatformFamily::PS5) — Target: 60 FPS
|
||||||
|
|
||||||
|
| Preset | Resolution % | GI | Shadows | Reflections | Upscaler | Mesh | FPS |
|
||||||
|
|--------|------------|-----|---------|-------------|----------|------|-----|
|
||||||
|
| Low | 70% | Baked | CSM | Captures | TSR Perf | LOD | 60 |
|
||||||
|
| Medium | 85% | Lumen Low | VSM Low | Lumen Low | TSR Balanced | Nanite | 60 |
|
||||||
|
| High | 100% | Lumen High | VSM High | Lumen High | TSR Quality | Nanite | 60 |
|
||||||
|
| Ultra | 100% | Lumen Ultra | VSM Ultra | Lumen Ultra | PSSR | Nanite | 60 |
|
||||||
|
| Cinematic | 100% | Lumen + HWRT | VSM + RT | Lumen + RT | PSSR | Nanite | 30 |
|
||||||
|
|
||||||
|
### PS4 (EPlatformFamily::PS4) — Target: 30 FPS
|
||||||
|
|
||||||
|
| Preset | Resolution % | GI | Shadows | Reflections | Upscaler | Mesh | FPS |
|
||||||
|
|--------|------------|-----|---------|-------------|----------|------|-----|
|
||||||
|
| Low | 60% | None | CSM Low | None | TAAU | Proxy | 30 |
|
||||||
|
| Medium | 75% | Baked | CSM Medium | Captures | TAAU | LOD | 30 |
|
||||||
|
| High | 90% | Baked | CSM High | Captures + SSR | TAAU | LOD | 30 |
|
||||||
|
|
||||||
|
### PC_High (RTX 3080+) — Target: 120 FPS
|
||||||
|
|
||||||
|
| Preset | Resolution % | GI | Shadows | Reflections | Upscaler | Mesh | FPS |
|
||||||
|
|--------|------------|-----|---------|-------------|----------|------|-----|
|
||||||
|
| Low | 70% | SSGI | CSM | SSR | DLSS Perf | LOD | 120 |
|
||||||
|
| Medium | 85% | Lumen Low | VSM | Lumen Low | DLSS Balanced | Nanite | 120 |
|
||||||
|
| High | 100% | Lumen High | VSM | Lumen High | DLSS Quality | Nanite | 120 |
|
||||||
|
| Ultra | 100% | Lumen Ultra | VSM Ultra | Lumen Ultra | DLSS Quality | Nanite | 120 |
|
||||||
|
| Cinematic | 120% | Lumen + HWRT | VSM + RT | Lumen + RT | DLSS UltraQ | Nanite | 60 |
|
||||||
|
|
||||||
|
### PC_Low (GTX 1060 / integrated) — Target: 30 FPS
|
||||||
|
|
||||||
|
| Preset | Resolution % | GI | Shadows | Reflections | Upscaler | Mesh | FPS |
|
||||||
|
|--------|------------|-----|---------|-------------|----------|------|-----|
|
||||||
|
| Low | 50% | None | CSM Low | None | FSR Perf | Proxy | 30 |
|
||||||
|
| Medium | 65% | Baked | CSM Medium | Captures | FSR Balanced | LOD | 30 |
|
||||||
|
| High | 80% | SSGI | CSM High | SSR | FSR Quality | LOD | 30 |
|
||||||
|
|
||||||
|
### Switch (EPlatformFamily::Switch) — Target: 30 FPS
|
||||||
|
|
||||||
|
| Preset | Resolution % | GI | Shadows | Reflections | Upscaler | Mesh | FPS |
|
||||||
|
|--------|------------|-----|---------|-------------|----------|------|-----|
|
||||||
|
| Low | 40% | None | CSM Low | None | NIS | Proxy | 30 |
|
||||||
|
| Medium | 55% | Baked | CSM Med | Captures | NIS | LOD | 30 |
|
||||||
|
| High | 70% | Baked | CSM High | Captures | FSR | LOD | 30 |
|
||||||
|
|
||||||
|
### Xbox_Series (EPlatformFamily::Xbox_Series) — Target: 60 FPS
|
||||||
|
|
||||||
|
| Preset | Resolution % | GI | Shadows | Reflections | Upscaler | Mesh | FPS |
|
||||||
|
|--------|------------|-----|---------|-------------|----------|------|-----|
|
||||||
|
| Low | 70% | Baked | CSM | Captures | FSR Perf | LOD | 60 |
|
||||||
|
| Medium | 85% | Lumen Low | VSM Low | Lumen Low | FSR Balanced | Nanite | 60 |
|
||||||
|
| High | 100% | Lumen High | VSM High | Lumen High | FSR Quality | Nanite | 60 |
|
||||||
|
| Ultra | 100% | Lumen Ultra | VSM Ultra | Lumen Ultra | FSR Quality | Nanite | 60 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Functions
|
||||||
|
|
||||||
|
*This Data Asset has no Blueprint functions. All data retrieval is performed by `BPC_RenderPipelineManager` reading the struct tables directly.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Console Variable Reference (per setting)
|
||||||
|
|
||||||
|
When `BPC_RenderPipelineManager` applies a `SRenderPipelineConfig`, it sets these UE5 console variables:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Global Illumination]
|
||||||
|
r.DynamicGlobalIlluminationMethod <0=None, 1=Lumen, 2=SSGI, 3=Plugin>
|
||||||
|
r.Lumen.Reflections.Allow <0/1>
|
||||||
|
r.Lumen.DiffuseIndirect.Allow <0/1>
|
||||||
|
r.Lumen.TranslucencyVolume <0/1>
|
||||||
|
|
||||||
|
[Shadows]
|
||||||
|
r.Shadow.Virtual.Enable <0/1>
|
||||||
|
r.Shadow.CSM.MaxCascades <0-4>
|
||||||
|
sg.ShadowQuality <0-4>
|
||||||
|
|
||||||
|
[Reflections]
|
||||||
|
r.ReflectionMethod <0=None, 1=Lumen, 2=SSR>
|
||||||
|
r.SSR.Quality <0-4>
|
||||||
|
|
||||||
|
[Upscaling]
|
||||||
|
r.TemporalAA.Upsampling <0/1>
|
||||||
|
r.TSR.History.ScreenPercentage <50-200>
|
||||||
|
r.NGX.DLSS.Enable <0/1> (DLSS plugin)
|
||||||
|
r.FidelityFX.FSR2.Enabled <0/1> (FSR plugin)
|
||||||
|
r.FidelityFX.FI.Enabled <0/1>
|
||||||
|
|
||||||
|
[Mesh / Nanite]
|
||||||
|
r.Nanite <0/1>
|
||||||
|
r.Nanite.MaxPixelsPerEdge <1-4>
|
||||||
|
r.StaticMeshLODDistanceScale <0.5-3.0>
|
||||||
|
|
||||||
|
[Post Process]
|
||||||
|
sg.PostProcessQuality <0-3>
|
||||||
|
r.MotionBlurQuality <0-4>
|
||||||
|
r.DepthOfFieldQuality <0-4>
|
||||||
|
|
||||||
|
[Textures]
|
||||||
|
sg.TextureQuality <0-3>
|
||||||
|
r.Streaming.PoolSize <MB value>
|
||||||
|
|
||||||
|
[View Distance]
|
||||||
|
sg.ViewDistanceQuality <0-3>
|
||||||
|
r.ViewDistanceScale <0.5-3.0>
|
||||||
|
foliage.LODDistanceScale <0.5-3.0>
|
||||||
|
|
||||||
|
[Volumetrics]
|
||||||
|
r.VolumetricCloud <0/1>
|
||||||
|
r.VolumetricFog <0/1>
|
||||||
|
|
||||||
|
[Hardware RT]
|
||||||
|
r.RayTracing <0/1>
|
||||||
|
r.RayTracing.Shadows <0/1>
|
||||||
|
r.RayTracing.Reflections <0/1>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Integration with Planar Capture System
|
||||||
|
|
||||||
|
The Planar Capture System (`BPC_PlanarCapture`, system 136) uses `SceneCapture2D` components that capture the world from a separate camera. These captures respect the **world's current render state**, meaning:
|
||||||
|
|
||||||
|
| Main Pipeline Setting | Effect on Planar Capture |
|
||||||
|
|----------------------|-------------------------|
|
||||||
|
| Lumen GI ON | Capture shows Lumen-lit scene (if `bEnableLumen` on capture profile) |
|
||||||
|
| Baked Lightmass | Capture shows baked lighting — SceneCapture2D picks this up naturally |
|
||||||
|
| Nanite ON | Captures render Nanite geometry (significant cost — bump down capture quality tier) |
|
||||||
|
| Traditional LOD | Captures use standard LOD — lower cost |
|
||||||
|
| VSM ON | Capture benefits from VSM (if capture profile enables shadows) |
|
||||||
|
| CSM | Capture uses CSM — lower cost but lower quality |
|
||||||
|
|
||||||
|
**Key rule:** When the main pipeline switches to **Baked Lightmass**, the Planar Capture System's quality tier profiles should also use `bEnableLumen = false` to prevent the capture from trying to enable Lumen on a world that doesn't use it. `BPC_RenderPipelineManager` broadcasts `OnRenderPipelineChanged` which `SS_PlanarCaptureManager` binds to, adjusting its `GlobalQualityCap` accordingly.
|
||||||
|
|
||||||
|
See [`BPC_RenderPipelineManager`](../12-settings/149_BPC_RenderPipelineManager.md) for the full integration spec.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Validation Rules
|
||||||
|
|
||||||
|
- At least one quality preset must be defined
|
||||||
|
- All preset names must be unique
|
||||||
|
- `bRequiresLevelReload` must be TRUE for profile changes that modify `GIMethod`, `ShadowMethod`, or `MeshStrategy` from the current value
|
||||||
|
- Platform family must not be `Unknown` (use auto-detection as fallback)
|
||||||
|
- ResolutionScale must be 0.25–2.0
|
||||||
|
- Upscaler must be in `SupportedUpscalers` for the target platform
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Consumed By
|
||||||
|
- [`BPC_RenderPipelineManager`](../12-settings/149_BPC_RenderPipelineManager.md) — reads profiles at init and on quality change
|
||||||
|
- [`BPC_PerformanceScaler`](../10-adaptive/91_BPC_PerformanceScaler.md) — delegates quality-tier CVar application
|
||||||
|
- [`SS_PlanarCaptureManager`](../17-capture/138_SS_PlanarCaptureManager.md) — adjusts capture quality cap on pipeline change
|
||||||
|
|
||||||
|
## 10. Reuse Notes
|
||||||
|
- Platform profiles are Data Asset instances — designers create `DA_RPP_PS5`, `DA_RPP_PS4`, `DA_RPP_PC_High`, `DA_RPP_PC_Low`, etc.
|
||||||
|
- The `bRequiresLevelReload` flag is critical — UI must display a "requires restart" warning when the player changes a pipeline-affecting setting (GI method, shadow method, Nanite toggle).
|
||||||
|
- Upscaler plugins (DLSS, FSR, XeSS) must be enabled in `Project Settings → Plugins`. If a plugin is missing, the system falls back to the next available upscaler.
|
||||||
|
- For console certification: each platform must have a `DA_RenderPipelineProfile` instance that meets Sony/Microsoft/Nintendo TRC requirements (minimum FPS target, resolution floor).
|
||||||
|
- The `ConsoleVariables` override map on `SPlatformRenderDefaults` allows per-platform tweaks without modifying the quality preset structs.
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
# 128 — Enhanced Input Manager (`SS_EnhancedInputManager`)
|
# 128 — Enhanced Input Manager (`SS_EnhancedInputManager`)
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Input/SS_EnhancedInputManager.h` provides the complete context stack management: Push/Pop context with priority ordering, input mode coordination, key rebinding, and action value queries. **Auto-created** by UE's subsystem system — no BP child needed. Access from any BP: `Get Game Instance → Get Subsystem(SS_EnhancedInputManager)`. You still need to create 22 IA_* + 5 IMC_* assets in the editor. See `docs/developer/cpp-integration-guide.md` for usage patterns.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
Centralized Game Instance Subsystem that manages all UE5 Enhanced Input operations: Input Mapping Context push/pop with priority stack, platform-specific binding profiles, key rebinding, input mode coordination with [`SS_UIManager`](docs/blueprints/06-ui/44_SS_UIManager.md), and read-only input state queries for gameplay systems.
|
Centralized Game Instance Subsystem that manages all UE5 Enhanced Input operations: Input Mapping Context push/pop with priority stack, platform-specific binding profiles, key rebinding, input mode coordination with [`SS_UIManager`](docs/blueprints/06-ui/44_SS_UIManager.md), and read-only input state queries for gameplay systems.
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
- **Requires:** [`DA_InputMappingProfile`](docs/blueprints/14-data-assets/129_DA_InputMappingProfile.md) (platform profiles), [`GI_GameFramework`](docs/blueprints/01-core/04_GI_GameFramework.md) (subsystem access)
|
- **Requires:** [`DA_InputMappingProfile`](docs/blueprints/14-data-assets/129_DA_InputMappingProfile.md) (platform profiles), [`GI_GameFramework`](docs/blueprints/01-core/04_GI_GameFramework.md) (subsystem access), [`BPC_PlatformServiceAbstraction`](docs/blueprints/01-core/150_BPC_PlatformServiceAbstraction.md) (central platform detection — replaces own platform enum)
|
||||||
- **Required By:** All gameplay systems that read input (`BPC_InteractionDetector`, `BPC_FirearmSystem`, `BPC_MovementStateSystem`, `BPC_CutsceneBridge`, `BPC_HidingSystem`, `BPC_ActiveItemSystem`, etc.)
|
- **Required By:** All gameplay systems that read input (`BPC_InteractionDetector`, `BPC_FirearmSystem`, `BPC_MovementStateSystem`, `BPC_CutsceneBridge`, `BPC_HidingSystem`, `BPC_ActiveItemSystem`, etc.)
|
||||||
- **Engine/Plugin Requirements:** Enhanced Input Plugin (UE5 built-in), `UEnhancedInputLocalPlayerSubsystem`
|
- **Engine/Plugin Requirements:** Enhanced Input Plugin (UE5 built-in), `UEnhancedInputLocalPlayerSubsystem`
|
||||||
|
|
||||||
@@ -29,12 +33,12 @@ Centralized Game Instance Subsystem that manages all UE5 Enhanced Input operatio
|
|||||||
| `Inspection = 3` | 3D item inspection (IMC_Inspection, Priority 20) |
|
| `Inspection = 3` | 3D item inspection (IMC_Inspection, Priority 20) |
|
||||||
| `UI = 4` | Full-screen menus/pause (IMC_UI, Priority 100) |
|
| `UI = 4` | Full-screen menus/pause (IMC_UI, Priority 100) |
|
||||||
|
|
||||||
### `E_InputPlatform`
|
### `E_InputPlatform` *(deprecated — use EPlatformFamily from BPC_PlatformServiceAbstraction. This enum is kept for DA_InputMappingProfile compatibility but all runtime selection uses the unified enum)*
|
||||||
| Value | Description |
|
| Value | Description |
|
||||||
|-------|-------------|
|
|-------|-------------|
|
||||||
| `PC_KeyboardMouse = 0` | Keyboard + Mouse |
|
| `PC_KeyboardMouse = 0` | Keyboard + Mouse (maps to `PC_Win64`, `PC_Linux`, `Mac`) |
|
||||||
| `Xbox = 1` | Xbox Series X\|S / Xbox One |
|
| `Xbox = 1` | Xbox Series X\|S / Xbox One (maps to `Xbox_Series` or `Xbox_One`) |
|
||||||
| `PS5_DualSense = 2` | PlayStation 5 DualSense |
|
| `PS5_DualSense = 2` | PlayStation 5 DualSense (maps to `PS5` or `PS5_Pro`) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -285,6 +289,8 @@ flowchart TD
|
|||||||
| `BPC_CutsceneBridge` | `Function Call` | `SS_EnhancedInputManager::PushContext/PopContext` |
|
| `BPC_CutsceneBridge` | `Function Call` | `SS_EnhancedInputManager::PushContext/PopContext` |
|
||||||
| `BPC_HidingSystem` | `Function Call` | `SS_EnhancedInputManager::PushContext(Hiding) / PopContext(Hiding)` |
|
| `BPC_HidingSystem` | `Function Call` | `SS_EnhancedInputManager::PushContext(Hiding) / PopContext(Hiding)` |
|
||||||
| `BPC_ActiveItemSystem` | `Function Call` | `SS_EnhancedInputManager::IsActionPressed("IA_QuickSlot1")` |
|
| `BPC_ActiveItemSystem` | `Function Call` | `SS_EnhancedInputManager::IsActionPressed("IA_QuickSlot1")` |
|
||||||
|
| `BPC_HapticsController` (148) | `Function Call` | `SS_EnhancedInputManager::GetControllerPlatform()` — for platform detection |
|
||||||
|
| `BPC_PlatformServiceAbstraction` (150) | `Function Call / Bind OnPlatformReady` | `GetDefaultInputProfile()` — selects correct DA_InputMappingProfile for detected platform |
|
||||||
| `WBP_PauseMenu` | `Function Call` | `SS_EnhancedInputManager::PushContext(UI) / PopContext(UI)` |
|
| `WBP_PauseMenu` | `Function Call` | `SS_EnhancedInputManager::PushContext(UI) / PopContext(UI)` |
|
||||||
| `WBP_InventoryMenu` | `Function Call` | `SS_EnhancedInputManager::PushContext(WristwatchUI)` |
|
| `WBP_InventoryMenu` | `Function Call` | `SS_EnhancedInputManager::PushContext(WristwatchUI)` |
|
||||||
| `BP_PuzzleDeviceActor` | `Function Call` | `SS_EnhancedInputManager::PushContext(Inspection)` |
|
| `BP_PuzzleDeviceActor` | `Function Call` | `SS_EnhancedInputManager::PushContext(Inspection)` |
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# 130 — Central State Authority (`BPC_StateManager`)
|
# 130 — Central State Authority (`BPC_StateManager`)
|
||||||
|
|
||||||
|
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Player/BPC_StateManager.h` provides the complete state authority: `IsActionPermitted()` hot-path query, `RequestStateChange()`, `ForceStateChange()`/`RestorePreviousState()` force stack, heart rate BPM smoothing with tier detection, gating rule evaluation. **Attach directly to player pawn** (component slot 0). Set `GatingTable` → `DA_StateGatingTable` in Details panel. Do NOT create a BP child. See `docs/developer/cpp-integration-guide.md` for usage patterns.
|
||||||
|
>
|
||||||
|
> ---
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
Single source of truth for "what can the player do right now?" Every system queries `IsActionPermitted(Tag)` instead of checking other systems' states directly. Manages exclusive action states, upper-body overlay states, action action gating, vital signs (heart rate), and the force-stack pattern for nested overrides (death, cutscenes, void space). Communicates with GASP exclusively through overlay state variables — never touches motion matching internals.
|
Single source of truth for "what can the player do right now?" Every system queries `IsActionPermitted(Tag)` instead of checking other systems' states directly. Manages exclusive action states, upper-body overlay states, action action gating, vital signs (heart rate), and the force-stack pattern for nested overrides (death, cutscenes, void space). Communicates with GASP exclusively through overlay state variables — never touches motion matching internals.
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user