Compare commits

..

32 Commits

Author SHA1 Message Date
Lefteris Notas
57ea21073a feat: Add .gitattributes and .gitignore for Unreal project asset management 2026-05-22 19:28:44 +03:00
Lefteris Notas
15d6e88780 feat: Implement BPC_PlatformServiceAbstraction for unified platform detection and SDK routing
- Added BPC_PlatformServiceAbstraction to centralize platform detection and SDK routing for achievements, cloud saves, and overlays.
- Updated dependencies across various systems to utilize the new platform service for consistent platform handling.
- Deprecated old platform enums in favor of a unified EPlatformFamily enum.
- Enhanced documentation for affected systems to reflect changes in platform handling and dependencies.
2026-05-22 18:22:42 +03:00
Lefteris Notas
dc9c1a6b98 Add DA_RenderPipelineProfile and Platform Render Profiles documentation
- Introduced DA_RenderPipelineProfile data asset to define rendering pipeline configurations for various platforms and quality tiers.
- Documented enums, structs, variables, and default preset tables for render settings.
- Created a comprehensive developer guide for setting up platform-specific render profiles, including file structure, profile creation, and integration with upscalers.
- Included validation rules, console variable references, and testing matrices for ensuring compliance with platform requirements.
2026-05-22 18:10:24 +03:00
Lefteris Notas
14441c000c Add haptics example documentation for Project Void controller feedback
- Introduced comprehensive guide for setting up controller haptics and force feedback.
- Detailed directory structure for haptic profiles and creation steps for DA_HapticProfile instances.
- Included platform-specific configurations for Xbox and PS5 DualSense adaptive triggers.
- Outlined wiring of BPC_HapticsController to various gameplay systems and events.
- Provided accessibility integration options and testing checklist for haptic functionality.
2026-05-22 17:16:34 +03:00
Lefteris Notas
7c2e8df6b1 docs: update README and UE5_Modular_Game_Framework.md to reflect changes in Blueprint systems and C++ class counts; add details for Planar Capture System 2026-05-22 16:29:32 +03:00
Lefteris Notas
5c08c929b5 refactor: rename ASS_PlanarCaptureManager to USS_PlanarCaptureManager for consistency; update documentation across multiple files 2026-05-22 16:23:20 +03:00
Lefteris Notas
321287253b refactor: remove unnecessary Super::Tick call in USS_PlanarCaptureManager::Tick method 2026-05-22 16:10:22 +03:00
Lefteris Notas
9fd679fd5b refactor: update show flag settings in BPC_PlanarCapture for UE5.7 compatibility; enhance SS_PlanarCaptureManager with FTickableGameObject interface 2026-05-22 16:02:01 +03:00
Lefteris Notas
d16c661022 refactor: rename ASS_PlanarCaptureManager to USS_PlanarCaptureManager for consistency 2026-05-22 15:52:13 +03:00
Lefteris Notas
0a2d08b2ad Add Planar Capture System implementation checklist and developer reference
- Created a comprehensive implementation checklist for the Planar Capture System (Systems 136-147) detailing tasks across multiple phases including C++ core, material foundation, Blueprint actors, data assets, integration, and performance testing.
- Added a developer reference document outlining the architecture, data flow, state machine, budget enforcement, render target pooling, horror features, integration points, multiplayer networking, performance characteristics, debugging methods, and build order for the capture systems.
- Introduced examples of capture surface usage in the Project Void horror game, including specific implementations for mirrors, monitors, portals, and fake windows, along with a checklist for integration tasks.
2026-05-22 15:36:08 +03:00
Lefteris Notas
6b6c702dd7 docs: Add initial README.md with comprehensive overview of UE5 Modular Game Framework and its features 2026-05-21 22:43:05 +03:00
Lefteris Notas
318d0d4317 docs: Add comprehensive summary and documentation for "Project Void" game prototype, detailing all assets and systems 2026-05-21 22:33:20 +03:00
Lefteris Notas
040db37720 Add UI Overrides and Weapons Index documentation for Project Void
- Created ui-overrides.md detailing game-specific Widget Blueprint overrides, including purpose, widget index, visual styling, and accessibility requirements.
- Established weapons-index.md outlining all held weapon actors, including their components, logic, and comparisons for gameplay mechanics.
2026-05-21 22:27:57 +03:00
Lefteris Notas
c515920eea refactor: remove clean slate refactor checklist and update documentation structure 2026-05-21 21:32:30 +03:00
Lefteris Notas
5b7d652505 docs: Remove outdated audit request and blueprint summary documentation 2026-05-21 21:31:51 +03:00
Lefteris Notas
bcbfcdf167 docs: Update C++ integration guide and blueprint specifications for various systems, adding detailed setup instructions and clarifying implementation statuses 2026-05-21 21:29:03 +03:00
Lefteris Notas
0852386168 docs: Add detailed item examples for Keycard, MedKit, and Pistol, including step-by-step setup instructions 2026-05-21 20:56:25 +03:00
Lefteris Notas
44aca98885 docs: Update item pickup setup instructions and add game examples for Blueprint walkthroughs 2026-05-21 20:45:44 +03:00
Lefteris Notas
7e876e4f0c docs: Update BP_ItemPickup setup instructions and correct C++ references 2026-05-21 20:08:40 +03:00
Lefteris Notas
ccd1872e59 Update documentation for player, inventory, and weapon systems
- Added C++ status updates for player systems (02-player-systems.md) indicating the implementation status of systems 08-11 and their relation to BPC_StateManager.
- Enhanced inventory systems documentation (04-inventory-systems.md) with C++ status for BPC_InventorySystem and DA_ItemData, clarifying their implementation details.
- Updated weapons systems documentation (08-weapons-systems.md) to reflect the C++ implementation status of BPC_DamageReceptionSystem and stubs for hit reaction and shield defense systems.
2026-05-21 18:41:43 +03:00
Lefteris Notas
8bb162eda2 Add Project Prototype Guide for PG_Framework Testing Setup
- Introduced a comprehensive guide for creating a minimal playable prototype in UE5.
- Detailed steps for project creation, C++ module integration, gameplay tags setup, and player pawn assembly.
- Included instructions for input system setup, state management wiring, and test level setup.
- Provided a verification checklist and troubleshooting section to assist developers.
- Documented next steps for expanding the prototype with additional systems.
2026-05-21 18:03:15 +03:00
Lefteris Notas
cd0ebf2233 refactor: Update API macro from FRAMEWORK_API to PG_FRAMEWORK_API across multiple headers 2026-05-21 17:15:25 +03:00
Lefteris Notas
6132571f8d fix: Update C++ path references to PG_Framework in documentation 2026-05-21 17:13:27 +03:00
Lefteris Notas
4ae2137179 refactor: Remove Framework module build and implementation files 2026-05-21 14:50:21 +03:00
Lefteris Notas
f986343325 Add core gameplay systems and data assets for player mechanics
- Implemented DA_EquipmentConfig for managing equipment resistances, durability, and weight.
- Created DA_ItemData to serve as a base item data asset with various item types and properties.
- Introduced BPC_HealthSystem for managing player health and death events.
- Added BPC_MovementStateSystem to handle player movement modes with event delegation.
- Developed BPC_StaminaSystem to track player stamina and exhaustion states.
- Established BPC_StateManager as a central authority for managing player action states and gating.
- Created BPC_StressSystem to monitor and respond to player stress levels.
- Implemented PC_CoreController and PS_CorePlayerState for player controller and state management.
- Developed SS_SaveManager for save/load functionality with slot management and serialization.
- Introduced DA_StateGatingTable for defining action gating rules based on gameplay tags.
- Added BPC_DamageReceptionSystem to process incoming damage and apply resistance calculations.
- Implemented BPC_HitReactionSystem for managing hit reactions based on damage received.
- Created BPC_ShieldDefenseSystem to manage shield health and blocking mechanics.
- Added PG_FrameworkEditor.Target.cs for editor build configuration.
2026-05-21 14:38:30 +03:00
Lefteris Notas
a145ae9373 refactor: Mark classes as Blueprintable for enhanced usability in Blueprints 2026-05-21 14:03:13 +03:00
Lefteris Notas
d982a6367d refactor: Simplify GetTagDisplayName by removing unnecessary checks and directly returning tag name 2026-05-21 13:43:08 +03:00
Lefteris Notas
108173b87b refactor: Simplify tag display name retrieval and enhance null checks in utility functions 2026-05-21 13:36:53 +03:00
Lefteris Notas
d100a097f5 refactor: Update log category names for consistency across game framework components 2026-05-21 13:30:59 +03:00
Lefteris Notas
3da9fd7493 refactor: Remove unused player controller and state class declarations in GM_CoreGameMode
docs: Update C++ Integration Guide for Blueprint child setup instructions
2026-05-21 13:22:43 +03:00
Lefteris Notas
9ee0a65630 feat: Implement core gameplay systems and data assets for health, stamina, stress, movement, hit reactions, and shield defense 2026-05-21 13:16:43 +03:00
Lefteris Notas
0b1128d209 Refactor code structure for improved readability and maintainability 2026-05-20 22:52:32 +03:00
155 changed files with 21637 additions and 1383 deletions

16
.gitattributes vendored Normal file
View 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
View 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

View File

@@ -2,5 +2,18 @@
"$schema": "https://app.kilo.ai/config.json",
"indexing": {
"enabled": true
},
"mcp": {
"context7": {
"type": "local",
"command": [
"npx",
"-y",
"@upstash/context7-mcp"
],
"environment": {
"DEFAULT_MINIMUM_TOKENS": "{{DEFAULT_MINIMUM_TOKENS}}"
}
}
}
}

View File

@@ -68,6 +68,22 @@ Content/
S_ActionPermissionResult.uasset # Permission result struct
DA_StateGatingTable.uasset # All gating rules (37 action rules)
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/
blueprints/
@@ -83,13 +99,14 @@ docs/
07-narrative/ # Narrative, Dialogue, Objective & Choice (11 files, 58-68)
08-weapons/ # Weapon, Equipment & Damage (11 files, 69-79)
09-ai/ # AI, Perception & Encounters (9 files, 80-88)
10-adaptive/ # Adaptive Environment, Atmosphere & Scare (15 files, 89-101, 132-133)
11-meta/ # Achievements, Progression & Meta (2 files, 102-103)
12-settings/ # Settings, Accessibility & Platform (2 files, 104-105)
13-polish/ # Tutorial, Loading, Credits, Debug (9 files, 106-114)
14-data-assets/ # Data Asset definitions (16 files, 115-127, 129, 134-135)
15-input/ # Enhanced Input System (1 file, 128)
16-state/ # State Management (2 files, 130-131)
10-adaptive/ # Adaptive Environment, Atmosphere & Scare (15 files, 89-101, 132-133)
11-meta/ # Achievements, Progression & Meta (2 files, 102-103)
12-settings/ # Settings, Accessibility & Platform (2 files, 104-105)
13-polish/ # Tutorial, Loading, Credits, Debug (9 files, 106-114)
14-data-assets/ # Data Asset definitions (16 files, 115-127, 129, 134-135)
15-input/ # Enhanced Input System (1 file, 128)
16-state/ # State Management (2 files, 130-131)
17-capture/ # Planar Capture System (12 files, 136-147)
developer/ # Developer Reference Docs (NEW)
INDEX.md # Master developer docs index (12 docs)
architecture-overview.md # Framework-wide architecture walkthrough
@@ -106,16 +123,17 @@ docs/
09-ai-systems.md # AI, perception & encounters explained
10-adaptive-systems.md # Adaptive environment & atmosphere explained
11-16-systems.md # Meta, Settings, Polish, Data Assets, Input, State explained
architecture/ # Architecture & Design Documents (8 files)
bpc-statemanager.md # BPC_StateManager full spec + Chooser Table Audit
metasounds-audio-system.md # MetaSounds audio architecture (mix buses, room zones, settings)
animation-catalog.md # Animation requirements for all 135 systems
sound-catalog.md # Sound/audio requirements for all 135 systems
enhanced-input-system.md # Enhanced Input System architecture
hud-overview.md # HUD system architecture — 14 widgets, wiring, integration points
multiplayer-networking.md # Multiplayer networking architecture — authority model, RPCs, prediction
blueprint-limitations-workarounds.md # UE5 C++-only functions + 100% Blueprint workarounds (NEW)
CLEAN_SLATE_PLAN.md # Clean slate refactoring plan
architecture/ # Architecture & Design Documents (9 files)
bpc-statemanager.md # BPC_StateManager full spec + Chooser Table Audit
metasounds-audio-system.md # MetaSounds audio architecture (mix buses, room zones, settings)
animation-catalog.md # Animation requirements for all 135 systems
sound-catalog.md # Sound/audio requirements for all 135 systems
enhanced-input-system.md # Enhanced Input System architecture
hud-overview.md # HUD system architecture — 14 widgets, wiring, integration points
multiplayer-networking.md # Multiplayer networking architecture — authority model, RPCs, prediction
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
reports/ # Condensed audit reports
blueprint_condensed_summary.md # ASK agent audit of all 129 files
checklists/ # Implementation checklists
@@ -123,7 +141,7 @@ docs/
enhanced-input-system.md
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
| 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).
## 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 2:** Interaction (03-interaction, 8 systems)
- **Phase 3:** Inventory (04-inventory, 11 systems)
@@ -175,11 +193,14 @@ docs/
- **Phase 9:** Adaptive & Atmosphere (10-adaptive, 15 systems)
- **Phase 10:** Data Assets (14-data-assets, 16 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 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 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
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 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)
- **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
View 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.55.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()`. ClientServer RPCs for requests, ServerClient `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 movementaction 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.55.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.*

View File

@@ -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");
}
}

View 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" } );
}
}

View 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
}
}

View 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" );

View File

@@ -0,0 +1,7 @@
// // Copyright Ngonart OU. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Capture/PlanarCaptureCommon.h"

View 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);
}

View 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);
}
}

View 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);
}

View File

@@ -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.

View 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);
}

View File

@@ -4,7 +4,7 @@
#include "Core/DA_GameTagRegistry.h"
#include "GameplayTagsManager.h"
#include "Engine/DataTable.h"
#include "GameplayTagTableRow.h"
UDA_GameTagRegistry::UDA_GameTagRegistry()
{
@@ -39,16 +39,7 @@ FText UDA_GameTagRegistry::GetTagDisplayName(const FGameplayTag& Tag) const
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::FromString(DisplayName);
return FText::FromName(Tag.GetTagName());
}
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();
TSharedPtr<FGameplayTagNode> TagNode = TagManager.FindTagNode(TagName);
FGameplayTag OutTag;
if (TagManager.RequestGameplayTag(TagName, OutTag))
FGameplayTag OutTag = TagManager.RequestGameplayTag(TagName, false);
if (OutTag.IsValid())
{
return OutTag;
}

View File

@@ -8,6 +8,7 @@
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
#include "GameplayTagsManager.h"
#include "GameplayTagAssetInterface.h"
// ============================================================================
// Subsystem Access
@@ -15,7 +16,18 @@
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,
@@ -173,9 +185,9 @@ FGameplayTag UFL_GameUtilities::MakeTagFromString(const FString& TagString, bool
}
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
FGameplayTag OutTag;
if (TagManager.RequestGameplayTag(FName(*TagString), OutTag))
FGameplayTag OutTag = TagManager.RequestGameplayTag(FName(*TagString), false);
if (OutTag.IsValid())
{
return OutTag;
}

View File

@@ -9,7 +9,7 @@
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"
DEFINE_LOG_CATEGORY_STATIC(LogGameMode, Log, All);
DEFINE_LOG_CATEGORY_STATIC(LogFrameworkGameMode, Log, All);
AGM_CoreGameMode::AGM_CoreGameMode()
{
@@ -29,10 +29,10 @@ void AGM_CoreGameMode::InitGame(const FString& MapName, const FString& Options,
CachedFramework = Cast<UGI_GameFramework>(GetGameInstance());
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()
@@ -45,7 +45,7 @@ void AGM_CoreGameMode::BeginPlay()
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())
{
UE_LOG(LogGameMode, Warning, TEXT("GM_CoreGameMode::TransitionToChapter — Invalid chapter tag"));
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::TransitionToChapter — Invalid chapter tag"));
return;
}
// Prevent transitions during 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());
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;
@@ -111,7 +111,7 @@ void AGM_CoreGameMode::OnChapterLevelLoaded(FGameplayTag ChapterTag)
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)
{
UE_LOG(LogGameMode, Warning, TEXT("GM_CoreGameMode::HandlePlayerDead — Invalid controller"));
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::HandlePlayerDead — Invalid controller"));
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.
bPauseAllowed = false;
@@ -138,7 +138,7 @@ void AGM_CoreGameMode::HandlePlayerDead(AController* DeadController)
// TODO: Decision logic — AltDeathSpace vs checkpoint respawn.
// 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())
{
UE_LOG(LogGameMode, Warning, TEXT("GM_CoreGameMode::TriggerEnding — Invalid ending tag"));
UE_LOG(LogFrameworkGameMode, Warning, TEXT("GM_CoreGameMode::TriggerEnding — Invalid ending tag"));
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);

View File

@@ -5,7 +5,7 @@
#include "Core/GI_GameFramework.h"
#include "Net/UnrealNetwork.h"
DEFINE_LOG_CATEGORY_STATIC(LogGameState, Log, All);
DEFINE_LOG_CATEGORY_STATIC(LogFrameworkGameState, Log, All);
AGS_CoreGameState::AGS_CoreGameState()
{
@@ -40,11 +40,11 @@ void AGS_CoreGameState::BeginPlay()
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
{
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;
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).
OnChapterChanged.Broadcast(ChapterTag);
@@ -106,7 +106,7 @@ void AGS_CoreGameState::SetNarrativePhase(FGameplayTag 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);
}
@@ -120,7 +120,7 @@ void AGS_CoreGameState::SetEncounterActive(bool 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);
}
@@ -139,7 +139,7 @@ void AGS_CoreGameState::AddObjective(FGameplayTag 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();
}
@@ -148,7 +148,7 @@ void AGS_CoreGameState::RemoveObjective(FGameplayTag ObjectiveTag)
{
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();
}
}
@@ -158,7 +158,7 @@ void AGS_CoreGameState::ClearAllObjectives()
if (ActiveObjectiveTags.Num() > 0)
{
ActiveObjectiveTags.Empty();
UE_LOG(LogGameState, Log, TEXT("GS_CoreGameState::ClearAllObjectives"));
UE_LOG(LogFrameworkGameState, Log, TEXT("GS_CoreGameState::ClearAllObjectives"));
OnObjectiveTagsChanged.Broadcast();
}
}

View File

@@ -10,7 +10,7 @@
#include "Engine/LocalPlayer.h"
#include "Kismet/GameplayStatics.h"
DEFINE_LOG_CATEGORY_STATIC(LogInput, Log, All);
DEFINE_LOG_CATEGORY_STATIC(LogFrameworkInput, Log, All);
USS_EnhancedInputManager::USS_EnhancedInputManager()
{
@@ -24,7 +24,7 @@ void USS_EnhancedInputManager::Initialize(FSubsystemCollectionBase& 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).
// 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()
{
UE_LOG(LogInput, Log, TEXT("SS_EnhancedInputManager::Deinitialize"));
UE_LOG(LogFrameworkInput, Log, TEXT("SS_EnhancedInputManager::Deinitialize"));
ClearAllContexts();
Super::Deinitialize();
}
@@ -57,7 +57,7 @@ void USS_EnhancedInputManager::PushContext(UInputMappingContext* Context,
{
if (!Context)
{
UE_LOG(LogInput, Warning, TEXT("PushContext — Null context"));
UE_LOG(LogFrameworkInput, Warning, TEXT("PushContext — Null context"));
return;
}
@@ -76,7 +76,7 @@ void USS_EnhancedInputManager::PushContext(UInputMappingContext* Context,
Entry.ContextTag = ContextTag;
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));
RebuildContextStack();
@@ -98,7 +98,7 @@ void USS_EnhancedInputManager::PopContext(UInputMappingContext* Context)
EInputContextPriority Prio = ContextStack[i].Priority;
ContextStack.RemoveAt(i);
UE_LOG(LogInput, Log, TEXT("PopContext — '%s'"), *Popped.GetTagName().ToString());
UE_LOG(LogFrameworkInput, Log, TEXT("PopContext — '%s'"), *Popped.GetTagName().ToString());
RebuildContextStack();
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)
@@ -120,13 +120,13 @@ void USS_EnhancedInputManager::PopContextByTag(FGameplayTag 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);
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());
}
@@ -145,7 +145,7 @@ void USS_EnhancedInputManager::ClearAllContexts()
}
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
@@ -187,7 +187,7 @@ void USS_EnhancedInputManager::SetInputMode(bool bUIMode, bool bShowCursor, bool
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!PC)
{
UE_LOG(LogInput, Warning, TEXT("SetInputMode — No PlayerController found"));
UE_LOG(LogFrameworkInput, Warning, TEXT("SetInputMode — No PlayerController found"));
return;
}
@@ -207,7 +207,7 @@ void USS_EnhancedInputManager::SetInputMode(bool bUIMode, bool bShowCursor, bool
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"),
bShowCursor ? TEXT("Visible") : TEXT("Hidden"));
@@ -222,7 +222,7 @@ void USS_EnhancedInputManager::RebindKey(UInputAction* Action, FKey NewKey, bool
{
if (!Action)
{
UE_LOG(LogInput, Warning, TEXT("RebindKey — Null action"));
UE_LOG(LogFrameworkInput, Warning, TEXT("RebindKey — Null action"));
return;
}
@@ -241,7 +241,7 @@ void USS_EnhancedInputManager::RebindKey(UInputAction* Action, FKey NewKey, bool
Options.bIgnoreAllPressedKeysUntilRelease = true;
// 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());
OnKeyRebound.Broadcast(FGameplayTag(), NewKey); // Tag from mapping profile.
@@ -253,8 +253,9 @@ void USS_EnhancedInputManager::ResetAllBindings()
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
if (Subsystem)
{
Subsystem->ResetPlayerMappedKeys();
UE_LOG(LogInput, Log, TEXT("ResetAllBindings — All keys reset to defaults"));
// UE 5.7+: ResetPlayerMappedKeys was removed. Use UEnhancedInputUserSettings or iterate context entries.
// Subsystem->RequestRebuildPlayerMappedKeys();
UE_LOG(LogFrameworkInput, Log, TEXT("ResetAllBindings — All keys reset to defaults"));
}
}
@@ -331,7 +332,7 @@ void USS_EnhancedInputManager::RebuildContextStack()
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
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;
}
@@ -341,8 +342,14 @@ void USS_EnhancedInputManager::RebuildContextStack()
return static_cast<int32>(A.Priority) < static_cast<int32>(B.Priority);
});
// Clear all and re-add in priority order.
Subsystem->RemoveAllMappingContexts();
// UE 5.7+: RemoveAllMappingContexts was removed. Remove individually.
for (const FInputContextEntry& Existing : ContextStack)
{
if (Existing.Context)
{
Subsystem->RemoveMappingContext(Existing.Context);
}
}
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

View 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;
}

View File

@@ -36,8 +36,8 @@ void UDA_ItemData::PostLoad()
if (ItemTag.IsValid())
{
UGameplayTagsManager& TagManager = UGameplayTagsManager::Get();
FGameplayTag CheckTag;
if (!TagManager.RequestGameplayTag(ItemTag.GetTagName(), CheckTag))
FGameplayTag CheckTag = TagManager.RequestGameplayTag(ItemTag.GetTagName(), false);
if (!CheckTag.IsValid())
{
UE_LOG(LogItemData, Warning, TEXT("DA_ItemData::PostLoad — '%s': ItemTag '%s' is not registered in the tag table!"),
*GetName(), *ItemTag.GetTagName().ToString());

View File

@@ -0,0 +1,6 @@
#include "Player/BPC_HealthSystem.h"
UBPC_HealthSystem::UBPC_HealthSystem()
{
PrimaryComponentTick.bCanEverTick = false;
}

View File

@@ -0,0 +1,6 @@
#include "Player/BPC_MovementStateSystem.h"
UBPC_MovementStateSystem::UBPC_MovementStateSystem()
{
PrimaryComponentTick.bCanEverTick = true;
}

View File

@@ -0,0 +1,6 @@
#include "Player/BPC_StaminaSystem.h"
UBPC_StaminaSystem::UBPC_StaminaSystem()
{
PrimaryComponentTick.bCanEverTick = true;
}

View File

@@ -2,10 +2,17 @@
// UE5 Modular Game Framework — BPC_StateManager Implementation
#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"
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()
{
PrimaryComponentTick.bCanEverTick = true;
@@ -285,6 +292,3 @@ EHeartRateTier UBPC_StateManager::GetHeartRateTier(float BPM)
if (BPM < 160.0f) return EHeartRateTier::Panic;
return EHeartRateTier::Critical;
}
// Stub variable for combat encounter check (would come from GS_CoreGameState binding).
namespace { bool bEncounterActive = false; }

View File

@@ -0,0 +1,6 @@
#include "Player/BPC_StressSystem.h"
UBPC_StressSystem::UBPC_StressSystem()
{
PrimaryComponentTick.bCanEverTick = true;
}

View File

@@ -0,0 +1,5 @@
#include "Player/PC_CoreController.h"
APC_CoreController::APC_CoreController()
{
}

View File

@@ -0,0 +1,5 @@
#include "Player/PS_CorePlayerState.h"
APS_CorePlayerState::APS_CorePlayerState()
{
}

View File

@@ -327,7 +327,8 @@ bool USS_SaveManager::SaveToFile(int32 SlotIndex, const TArray<uint8>& Data, con
FMemoryWriter Writer(FileData);
// 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.
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.
FMemoryReader Reader(FileData);
Reader << OutMeta;
FSaveSlotInfo::StaticStruct()->SerializeItem(Reader, &OutMeta, nullptr);
// Remaining bytes are game state data.
int32 HeaderSize = Reader.Tell();

View 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;
}

View File

@@ -2,8 +2,11 @@
// UE5 Modular Game Framework — BPC_DamageReceptionSystem Implementation
#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()
{
@@ -67,7 +70,7 @@ float UBPC_DamageReceptionSystem::ApplyDamage(float RawDamage, AActor* DamageCau
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);
return FinalDamage;
@@ -119,12 +122,12 @@ void UBPC_DamageReceptionSystem::EvaluateHitReaction(float FinalDamage, AActor*
{
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);
}
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);
}

View File

@@ -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.
}

View File

@@ -0,0 +1,6 @@
#include "Weapons/BPC_ShieldDefenseSystem.h"
UBPC_ShieldDefenseSystem::UBPC_ShieldDefenseSystem()
{
PrimaryComponentTick.bCanEverTick = false;
}

View 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;
};

View 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();
};

View 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);
};

View 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;
};

View 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);
};

View File

@@ -20,7 +20,7 @@
* bypass them entirely for performance and correctness.
*/
UCLASS(BlueprintType, Blueprintable, meta = (DisplayName = "DA_GameTagRegistry"))
class FRAMEWORK_API UDA_GameTagRegistry : public UPrimaryDataAsset
class PG_FRAMEWORK_API UDA_GameTagRegistry : public UPrimaryDataAsset
{
GENERATED_BODY()

View File

@@ -21,7 +21,7 @@ class APC_CoreController;
* here we get clean UFUNCTION(BlueprintCallable) with fast native paths.
*/
UCLASS()
class FRAMEWORK_API UFL_GameUtilities : public UBlueprintFunctionLibrary
class PG_FRAMEWORK_API UFL_GameUtilities : public UBlueprintFunctionLibrary
{
GENERATED_BODY()

View File

@@ -68,8 +68,8 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFrameworkInitFailed, const FStrin
* In C++, this replaces the Blueprint "Get Game Instance → Cast → Get Subsystem"
* pattern with clean template access via GetService<T>().
*/
UCLASS()
class FRAMEWORK_API UGI_GameFramework : public UGameInstance
UCLASS(Blueprintable)
class PG_FRAMEWORK_API UGI_GameFramework : public UGameInstance
{
GENERATED_BODY()

View File

@@ -14,8 +14,6 @@
// Forward declarations
class UGI_GameFramework;
class AGS_CoreGameState;
class APC_CoreController;
class APS_CorePlayerState;
// Delegates
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.
*
* 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()
class FRAMEWORK_API AGM_CoreGameMode : public AGameModeBase
UCLASS(Blueprintable)
class PG_FRAMEWORK_API AGM_CoreGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
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
// ========================================================================

View File

@@ -29,8 +29,8 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayTimeUpdated, float, ElapsedSe
* OnRep_ handlers that mirror broadcast dispatchers for both network
* clients and local single-player.
*/
UCLASS()
class FRAMEWORK_API AGS_CoreGameState : public AGameStateBase
UCLASS(Blueprintable)
class PG_FRAMEWORK_API AGS_CoreGameState : public AGameStateBase
{
GENERATED_BODY()

View File

@@ -20,13 +20,13 @@ class UDA_InteractionData;
// I_Interactable — World objects the player can interact with
// ============================================================================
UINTERFACE(MinimalAPI, BlueprintType, meta = (CannotImplementInterfaceInBlueprint))
UINTERFACE(MinimalAPI, BlueprintType)
class UInteractable : public UInterface
{
GENERATED_BODY()
};
class FRAMEWORK_API IInteractable
class PG_FRAMEWORK_API IInteractable
{
GENERATED_BODY()
@@ -66,7 +66,7 @@ class UInspectable : public UInterface
GENERATED_BODY()
};
class FRAMEWORK_API IInspectable
class PG_FRAMEWORK_API IInspectable
{
GENERATED_BODY()
@@ -97,7 +97,7 @@ class UDamageable : public UInterface
GENERATED_BODY()
};
class FRAMEWORK_API IDamageable
class PG_FRAMEWORK_API IDamageable
{
GENERATED_BODY()
@@ -138,7 +138,7 @@ class UHoldable : public UInterface
GENERATED_BODY()
};
class FRAMEWORK_API IHoldable
class PG_FRAMEWORK_API IHoldable
{
GENERATED_BODY()
@@ -169,7 +169,7 @@ class ULockable : public UInterface
GENERATED_BODY()
};
class FRAMEWORK_API ILockable
class PG_FRAMEWORK_API ILockable
{
GENERATED_BODY()
@@ -200,7 +200,7 @@ class UUsableItem : public UInterface
GENERATED_BODY()
};
class FRAMEWORK_API IUsableItem
class PG_FRAMEWORK_API IUsableItem
{
GENERATED_BODY()
@@ -229,7 +229,7 @@ class UPersistable : public UInterface
GENERATED_BODY()
};
class FRAMEWORK_API IPersistable
class PG_FRAMEWORK_API IPersistable
{
GENERATED_BODY()
@@ -261,7 +261,7 @@ class UToggleable : public UInterface
GENERATED_BODY()
};
class FRAMEWORK_API IToggleable
class PG_FRAMEWORK_API IToggleable
{
GENERATED_BODY()
@@ -292,7 +292,7 @@ class UAdjustable : public UInterface
GENERATED_BODY()
};
class FRAMEWORK_API IAdjustable
class PG_FRAMEWORK_API IAdjustable
{
GENERATED_BODY()

View File

@@ -34,7 +34,7 @@ enum class EInputContextPriority : uint8
* Mapping context entry in the stack.
*/
USTRUCT(BlueprintType)
struct FRAMEWORK_API FInputContextEntry
struct PG_FRAMEWORK_API FInputContextEntry
{
GENERATED_BODY()
@@ -70,7 +70,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnKeyRebound, FGameplayTag, Action
* "Get Enhanced Input Local Player Subsystem" node chains.
*/
UCLASS()
class FRAMEWORK_API USS_EnhancedInputManager : public UGameInstanceSubsystem
class PG_FRAMEWORK_API USS_EnhancedInputManager : public UGameInstanceSubsystem
{
GENERATED_BODY()

View File

@@ -16,7 +16,7 @@ class UDA_ItemData;
* Single inventory slot entry.
*/
USTRUCT(BlueprintType)
struct FRAMEWORK_API FInventorySlot
struct PG_FRAMEWORK_API FInventorySlot
{
GENERATED_BODY()
@@ -63,8 +63,8 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnWeightChanged, float, CurrentWei
* C++ TArray operations (FindByPredicate, Sort, Filter) are natively compiled
* no BP interpretive array node overhead.
*/
UCLASS(ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
class FRAMEWORK_API UBPC_InventorySystem : public UActorComponent
UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_InventorySystem : public UActorComponent
{
GENERATED_BODY()

View 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;
};

View File

@@ -49,7 +49,7 @@ enum class EEquipmentSlot : uint8
* Equipment-specific data (shown when ItemType is Weapon or Tool).
*/
USTRUCT(BlueprintType)
struct FRAMEWORK_API FItemEquipmentData
struct PG_FRAMEWORK_API FItemEquipmentData
{
GENERATED_BODY()
@@ -76,7 +76,7 @@ struct FRAMEWORK_API FItemEquipmentData
* Consumable-specific data (shown when ItemType is Consumable).
*/
USTRUCT(BlueprintType)
struct FRAMEWORK_API FItemConsumableData
struct PG_FRAMEWORK_API FItemConsumableData
{
GENERATED_BODY()
@@ -97,7 +97,7 @@ struct FRAMEWORK_API FItemConsumableData
* Inspect-specific data (shown when bHasInspectMode is true).
*/
USTRUCT(BlueprintType)
struct FRAMEWORK_API FItemInspectData
struct PG_FRAMEWORK_API FItemInspectData
{
GENERATED_BODY()
@@ -122,7 +122,7 @@ struct FRAMEWORK_API FItemInspectData
* shows relevant sub-structs based on ItemType a massive UX win for designers.
*/
UCLASS(BlueprintType, Blueprintable, meta = (DisplayName = "Item Data"))
class FRAMEWORK_API UDA_ItemData : public UPrimaryDataAsset
class PG_FRAMEWORK_API UDA_ItemData : public UPrimaryDataAsset
{
GENERATED_BODY()

View 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;
};

View 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;
};

View 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;
};

View File

@@ -65,8 +65,8 @@ enum class EHeartRateTier : uint8
* directly. Gating rules are defined in DA_StateGatingTable (37 rules).
* In C++, the Chooser Table iteration is native-speed no BP interpretive overhead.
*/
UCLASS(ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
class FRAMEWORK_API UBPC_StateManager : public UActorComponent
UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_StateManager : public UActorComponent
{
GENERATED_BODY()

View 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;
};

View 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();
};

View 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();
};

View File

@@ -15,7 +15,7 @@
* Save slot metadata returned by GetSlotManifest().
*/
USTRUCT(BlueprintType)
struct FRAMEWORK_API FSaveSlotInfo
struct PG_FRAMEWORK_API FSaveSlotInfo
{
GENERATED_BODY()
@@ -54,7 +54,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSaveManifestUpdated, const TArray
* and async save operations impossible to match in Blueprint.
*/
UCLASS()
class FRAMEWORK_API USS_SaveManager : public UGameInstanceSubsystem
class PG_FRAMEWORK_API USS_SaveManager : public UGameInstanceSubsystem
{
GENERATED_BODY()

View 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;
};

View File

@@ -28,7 +28,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnKnockedDown, AActor*, KnockdownC
* Damage modifier for a specific damage type.
*/
USTRUCT(BlueprintType)
struct FRAMEWORK_API FDamageModifier
struct PG_FRAMEWORK_API FDamageModifier
{
GENERATED_BODY()
@@ -56,8 +56,8 @@ struct FRAMEWORK_API FDamageModifier
* triggers hit reactions (stagger, knockdown), and routes final damage to the
* health system. In C++, the damage pipeline is native-speed vectorized math.
*/
UCLASS(ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
class FRAMEWORK_API UBPC_DamageReceptionSystem : public UActorComponent
UCLASS(Blueprintable, ClassGroup = (Framework), meta = (BlueprintSpawnableComponent))
class PG_FRAMEWORK_API UBPC_DamageReceptionSystem : public UActorComponent
{
GENERATED_BODY()

View 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;
};

View 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;
};

View 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

View 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.*

View File

@@ -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

View File

@@ -5,10 +5,12 @@
> **Category:** 01-Core / Foundation
> **Build Phase:** Phase 0 — Item 1
> **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.

View File

@@ -1,5 +1,9 @@
# 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
| Property | Value |

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,9 @@
# 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
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.

View File

@@ -1,5 +1,9 @@
# 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
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.

View File

@@ -1,6 +1,8 @@
# 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

View File

@@ -1,5 +1,9 @@
# 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
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.

View File

@@ -3,9 +3,261 @@
## 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.
**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
- **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)
- **Parent Class:** `UPrimaryDataAsset`
@@ -13,9 +265,10 @@ The single source of truth for every item in the game. Each item is represented
| Property | Value |
|----------|-------|
| **Parent Class** | `UPrimaryDataAsset` |
| **Class Type** | Blueprint Function Library (Data Asset) |
| **Asset Path** | `Content/Data/Items/DA_Item_[Name]` |
| **Implements Interfaces** | None |
| **C++ Class** | `UDA_ItemData` (in `Source/PG_Framework/Public/Inventory/DA_ItemData.h`) |
| **Class Type** | Primary Data Asset |
| **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
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)
#### `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).
- **Parameters:** None
- **Parameters:** `OutErrors` (FString, out) — human-readable error messages
- **Flow:**
1. Check `ItemTag != None` — if None, log warning
2. Check `DisplayName != ""` — if empty, log warning
3. If `StackLimit < 1`, reset to 1
4. If `bIsKeyItem` then `bCanBeDropped = false` (forced)
5. Return true if all validations pass
1. Check `ItemTag != None` — if None, append error
2. Check `DisplayName != ""` — if empty, append error
3. If `StackLimit < 1`, append error
4. If `bIsKeyItem` then warn if `bCanBeDropped == true`
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
flowchart LR
A[Content Browser] --> B[Right-click > Data Asset > DA_ItemData]
B --> C[Name: DA_Item_MedKit]
C --> D[Fill ItemTag: Item.MedKit]
D --> E[Fill DisplayName: Med Kit]
E --> F[Assign Icon, Mesh]
F --> G[Set ItemType: Consumable]
G --> H[Fill ConsumableData]
H --> I[Save Asset]
I --> J[Registered in Asset Manager]
A[Content Browser] --> B[Right-click > Miscellaneous > Data Asset]
B --> C[Select Class: DA_ItemData]
C --> D[Name: DA_Item_MedKit]
D --> E[Open Asset > Fill Properties]
E --> F[ItemTag: Framework.Item.Consumable.MedKit]
F --> G[DisplayName: Med Kit]
G --> H[Set Icon, WorldMesh]
H --> I[ItemType: Consumable]
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
- Create one `DA_ItemData` per item. Name 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.
- The `CustomProperties` map future-proofs any per-project additions without modifying the base asset class.
- **Create one `DA_ItemData` per item.** Naming convention: `DA_Item_[ShortName]` (e.g., `DA_Item_MedKit`, `DA_Item_Flashlight`, `DA_Item_KeyCard_Omega`).
- **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.
- 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 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.
- 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.
- **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.
---

View File

@@ -0,0 +1,406 @@
# 150 — Platform Service Abstraction (`BPC_PlatformServiceAbstraction`)
> **Blueprint-Only Implementation** — UE 5.55.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.*

View File

@@ -1,5 +1,9 @@
# 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
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.

View File

@@ -1,5 +1,9 @@
# 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
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.

View File

@@ -1,5 +1,9 @@
# 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
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.

View File

@@ -1,5 +1,9 @@
# 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
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.

View File

@@ -3,15 +3,67 @@
**Parent Class:** `Actor`
**Category:** Inventory
**Target UE Version:** 5.55.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
`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 |
|----------|------|-------------|
| `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. |
---
@@ -110,7 +162,8 @@ When the player interacts (via [`BPC_InteractionDetector`](docs/blueprints/03-in
| 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. |
| `BeginPlay` | Caches references, sets up bobbing/rotation timeline, configures collision. |
| `SetPickupEnabled` | Toggles visibility and collision. |
@@ -151,7 +204,7 @@ Event BeginPlay
OnOverlapBegin (Player overlaps InteractionCollision)
→ Set VisualState = Highlighted
→ Show pickup prompt widget (if assigned)
→ Cache player's BPC_InventoryComponent
→ Cache player's BPC_InventorySystem
OnOverlapEnd (Player leaves InteractionCollision)
→ 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
→ Set VisualState = BeingPickedUp
→ Play pickup sound + particle effect
→ Call BPC_InventoryComponent.AddItem(Config.ItemData, Config.Quantity)
→ Call BPC_InventorySystem.AddItem(Config.ItemData, Config.Quantity)
→ If AddItem succeeded:
→ Broadcast OnPickupCollected(InteractingPlayer)
→ If bIsInfinite → do not destroy
@@ -214,12 +267,12 @@ OnRespawnComplete
|--------|--------------|
| `I_Interactable` | Implements the interface for interaction detection. |
| `BPC_InteractionDetector` | Sends overlap events and routes interaction call. |
| `BPC_InventoryComponent` | Target for item addition. |
| `DA_ItemData` | Supplies mesh, name, icon, stack limits. |
| `BPC_PlayerMetricsTracker` | Logs pickup event (item type, quantity, location). |
| `BPC_InventorySystem` (31) | **Target for item addition.** C++ component — call `AddItem(ItemData, Quantity)`. |
| `DA_ItemData` (07) | Supplies mesh, name, icon, stack limits, weight. |
| `BPC_PlayerMetricsTracker` (15) | Logs pickup event (item type, quantity, location). |
| `I_Persistable` | Optional: saves pickup state (available, transform, quantity). |
| `BPC_InventoryWeightSystem` | Checks weight capacity before allowing pickup. |
| `BPC_AdaptiveDifficulty` | May adjust item availability based on difficulty. |
| `BPC_KeyItemSystem` (34) | Checks `bIsKeyItem` — key items auto-protect from drop/clear. |
| `SS_AchievementSystem` (103) | Notified on collectible pickups for completion tracking. |
---
@@ -248,7 +301,7 @@ Player approaches world item → Overlap event
Player presses Interact key
→ BPC_InteractionDetector.InteractTarget → BP_ItemPickup.Interact_Implementation
→ Validate availability + inventory space
→ BPC_InventoryComponent.AddItem (ItemData, Quantity)
→ BPC_InventorySystem.AddItem (ItemData, Quantity)
→ Play pickup sound + particle
→ Broadcast OnPickupCollected
→ If bRespawns → StartRespawn (hide, wait, reappear)

View File

@@ -3,11 +3,11 @@
**Parent Class:** `ActorComponent`
**Category:** Inventory
**Target UE Version:** 5.55.7
**Build Phase:** 3 — Inventory
**Build Phase:** 4 — Inventory
---
## 1. Overview
> **⚡ 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
`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.

View File

@@ -2,6 +2,10 @@
**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.
**Depends On:** [`GI_GameFramework`](../01-core/04_GI_GameFramework.md), [`I_Persistable`](30_I_Persistable.md)

View File

@@ -2,6 +2,10 @@
**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.
**Depends On:** None

View File

@@ -2,20 +2,20 @@
**Parent:** `UUserWidget`
**Used by:** `SS_UIManager.PushMenu("SettingsMenu")`
**Depends On:** `SS_UIManager`, `SS_SettingsSystem`
**Depends On:** `SS_UIManager`, `SS_SettingsSystem`, `BPC_RenderPipelineManager`
---
### 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)
```cpp
```
E_SettingsTab
{
Audio,
Video,
Audio,
Gameplay,
Controls,
Accessibility
@@ -29,75 +29,215 @@ E_SettingsTab
| `ActiveTab` | `E_SettingsTab` | Currently visible tab |
| `TabButtons` | Array of `Button` | Tab navigation buttons |
| `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` | 30240 FPS |
| `BrightnessSlider` | `Slider` | 0.52.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` | 03 |
| `PostProcessQualitySlider` | `Slider` | 03 |
| `ViewDistanceQualitySlider` | `Slider` | 03 |
| `FoliageQualitySlider` | `Slider` | 03 |
| `AntiAliasingQualitySlider` | `Slider` | 03 |
**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` | 5128192 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 |
|------|------|-------------|
| `MasterVolumeSlider` | `Slider` | 0-100 |
| `SFXVolumeSlider` | `Slider` | 0-100 |
| `MusicVolumeSlider` | `Slider` | 0-100 |
| `VoiceVolumeSlider` | `Slider` | 0-100 |
| `MasterVolumeSlider` | `Slider` | 0100 |
| `SFXVolumeSlider` | `Slider` | 0100 |
| `MusicVolumeSlider` | `Slider` | 0100 |
| `VoiceVolumeSlider` | `Slider` | 0100 |
| `AmbientVolumeSlider` | `Slider` | 0100 |
| `UIVolumeSlider` | `Slider` | 0100 |
| `SpatialAudioToggle` | `CheckBox` | On/Off |
| `AudioQualityDropdown` | `ComboBoxString` | Low, Medium, High |
| `SubtitlesToggle` | `CheckBox` | On/Off |
**Video 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:**
### Gameplay Tab Children
| Name | Type | Description |
|------|------|-------------|
| `InvertYAxisToggle` | `CheckBox` | On/Off |
| `SensitivitySlider` | `Slider` | Mouse sensitivity 0.1-5.0 |
| `HoldDurationToggle` | `CheckBox` | Tap vs Hold for interactions |
| `AutoPickupToggle` | `CheckBox` | On/Off |
| `CrosshairToggle` | `CheckBox` | Show crosshair |
| `SensitivityXSlider` | `Slider` | Mouse sensitivity X |
| `SensitivityYSlider` | `Slider` | Mouse sensitivity Y |
| `FOVSlider` | `Slider` | 70120 degrees |
| `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 |
|------|------|-------------|
| `ActionMappingsList` | `ListView` | Key binding rows |
| `ResetDefaultsButton` | `Button` | Reset all bindings |
**Accessibility Tab Children:**
### Accessibility Tab Children
| Name | Type | Description |
|------|------|-------------|
| `SubtitleSizeDropdown` | `ComboBoxString` | Small, Medium, Large |
| `SubtitleBackgroundOpacity` | `Slider` | 0-100% |
| `SubtitleBackgroundOpacity` | `Slider` | 0100% |
| `ColorblindModeDropdown` | `ComboBoxString` | None, Protanopia, Deuteranopia, Tritanopia |
| `HighContrastUIToggle` | `CheckBox` | On/Off |
| `CameraShakeIntensity` | `Slider` | 0-100% |
| `MotionBlurToggle` | `CheckBox` | On/Off |
| `CameraShakeIntensity` | `Slider` | 0100% |
| `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 0100% |
| `AdaptiveTriggersToggle` | `CheckBox` | PS5 DualSense adaptive triggers |
| `ControllerSpeakerToggle` | `CheckBox` | PS5 controller speaker |
---
### Functions
| 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 |
| `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 |
| `SaveSettings` | — | — | SS_SettingsSystem.SaveToDisk |
| `ApplyVideoSettings` | — | — | Apply resolution, window mode, quality |
| `ResetToDefaults` | — | — | Load default values, re-populate UI |
| `ApplyVideoSettings` | — | — | SS_SettingsSystem.ApplyGraphicsSettings → BPC_RenderPipelineManager |
| `ResetToDefaults` | — | — | Load platform defaults, re-populate UI |
| `OnBack` | — | — | SaveSettings, SS_UIManager.PopMenu |
| `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
| Target System | Method | Why |
|--------------|--------|-----|
| [`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
- 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

View File

@@ -1,5 +1,9 @@
# 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.55.7
---

View File

@@ -1,5 +1,9 @@
# 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.55.7
---

View File

@@ -1,5 +1,9 @@
# 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)
**Parent Class:** `UActorComponent`
**Dependencies:** [`BPC_DamageReceptionSystem`](BPC_DamageReceptionSystem.md), [`BPC_StaminaSystem`](../02-player/09_BPC_StaminaSystem.md)

View File

@@ -8,6 +8,8 @@
`ActorComponent`
### 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_AudioManager`](66_BPC_AudioManager.md) — Controls audio quality
- [`BPC_VFXManager`](67_BPC_VFXManager.md) — Controls particle LOD
@@ -16,7 +18,7 @@
- [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) — Frame time measurement
### 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
@@ -128,13 +130,15 @@ Automatically adjusts graphics, audio, and gameplay quality settings based on re
└─► If CurrentQualityLevel == Level: return
└─► TargetQualityLevel = Level
└─► bIsScalingInProgress = true
└─► ApplyScalerSettings(Level)
└─► For each subsystem:
BPC_LightingManager: SetParticleLOD mapped to QualityLevel
└─► Delegate to BPC_RenderPipelineManager.ApplyQualityPreset(PresetName)
→ RenderPipelineManager handles ALL CVar application
→ 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_VFXManager: SetParticleLOD mapped to QualityLevel
BPC_AtmosphereController: Reduce preset complexity
└─► Apply UE console variables for resolution, shadows, textures, foliage
└─► bIsScalingInProgress = false
└─► CurrentQualityLevel = TargetQualityLevel
└─► OnQualityLevelChanged.Broadcast(CurrentQualityLevel)
@@ -215,6 +219,7 @@ Automatically adjusts graphics, audio, and gameplay quality settings based on re
| 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 |
| [`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 |

View File

@@ -12,8 +12,8 @@
- **Requires:** [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) — Player stats source
- **Requires:** [`BPC_NarrativeStateSystem`](../07-narrative/38_BPC_NarrativeStateSystem.md) — Story progression
- **Required By:** [`WBP_NotificationToast`](../06-ui/WBP_NotificationToast.md) — UI notification
- **Required By:** [`BPC_PlatformServiceAbstraction`](../12-settings/BPC_PlatformServiceAbstraction.md) — Platform API submission
- **Engine/Plugin Requirements:** GameplayTags
- **Required By:** [`BPC_PlatformServiceAbstraction`](../01-core/150_BPC_PlatformServiceAbstraction.md) — Platform API routing (Steam/PSN/Xbox/Nintendo)
- **Engine/Plugin Requirements:** GameplayTags, Platform SDK plugins (Steamworks, PSN, Xbox GDK, Nintendo SDK — all optional)
### 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.
@@ -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_NarrativeStateSystem`](../07-narrative/38_BPC_NarrativeStateSystem.md) | Direct read | Story achievements |
| [`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) |
---

View File

@@ -9,7 +9,7 @@
### Dependencies
- [`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
- [`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]
└─► ApplyGraphicsSettings():
Get "ResolutionScale", "ShadowQuality", etc.
Pass to BPC_PerformanceScaler.SetQualityLevel
Get "QualityPreset", "ResolutionScale", "UpscalerMethod", etc.
Pass to BPC_RenderPipelineManager.ApplyQualityPreset(QualityPreset)
→ RenderPipelineManager handles ALL CVars, reload detection, platform selection
└─► ApplyAudioSettings():
Get "MasterVolume", "SFXVolume", etc.
Pass to BPC_AudioManager volume modifiers
@@ -146,18 +147,31 @@ Centralized settings manager that stores, applies, and persists all player-confi
[RegisterDefaultSettings]
└─► Graphics category:
ResolutionScale Float 1.0 [0.51.5]
ShadowQuality Int 2 [03]
TextureQuality Int 2 [03]
PostProcessQuality Int 2 [03]
AntiAliasing Dropdown [TSR, TAA, FXAA, Off]
VSync Bool true
FrameRateLimit Int 60 [30240]
GlobalIllumination Dropdown [Lumen, SSGI, Off]
MotionBlur Bool true
DepthOfField Bool true
FoliageQuality Int 2 [03]
ViewDistance Int 2 [03]
QualityPreset Dropdown [Low, Medium, High, Ultra, Cinematic, Custom]
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.252.0]
DynamicResolution Bool false
DynamicResTargetFPS Int 60 [30120]
ShadowQuality Int 2 [04]
TextureQuality Int 2 [03]
PostProcessQuality Int 2 [03]
ViewDistanceQuality Int 2 [03]
FoliageQuality Int 2 [03]
AntiAliasingQuality Int 2 [03]
VSync Bool true
FrameRateLimit Int 60 [30240]
MotionBlur Bool true
DepthOfField Bool true
VolumetricClouds Bool true
HW_RayTracing Bool false ⚠ reload
Brightness Float 1.0 [0.52.0]
HDR Bool false (if display supports)
TexturePoolSizeMB Int 2048 [5128192]
└─► Audio category:
MasterVolume Float 1.0 [01]
SFXVolume Float 1.0 [01]
@@ -205,7 +219,8 @@ Centralized settings manager that stores, applies, and persists all player-confi
| 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_Movement`](../02-player/14_BPC_Movement.md) | Cast to player | Gameplay settings |
| [`SS_SaveManager`](../05-saveload/28_SS_SaveManager.md) | Direct call | Persistence |

View File

@@ -0,0 +1,577 @@
# 148 — Haptics Controller (`BPC_HapticsController`)
> **Blueprint-Only Implementation** — UE 5.55.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.02.0, 1.0 = default) |
| `DurationOverride` | `Float` | Override duration (-1 = use profile default) |
| `Priority` | `Int32` | Higher interrupts lower (0100) |
| `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 (09) |
| `Strength` | `Int32` | Effect strength (08) |
- **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.*

View File

@@ -0,0 +1,434 @@
# 149 — Render Pipeline Manager (`BPC_RenderPipelineManager`)
> **Blueprint-Only Implementation** — UE 5.55.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 9001600 / 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.*

View File

@@ -51,7 +51,8 @@ The Modular Game Framework uses `UDataAsset` (and subclass `UPrimaryDataAsset` w
| 11 | `DA_ScareEvent` | Adaptive | `BPC_ScareEventSystem` |
| 12 | `DA_RoomMutation` | Adaptive | `BPC_AdaptiveEnvironmentDirector` |
| 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` |
| 16 | `DA_EquipmentConfig` | Inventory | `BPC_EquipmentSlotSystem` |
| 17 | `DA_PuzzleData` | Interaction | `BP_PuzzleDeviceActor` |

View File

@@ -1,5 +1,9 @@
# 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`
**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.

View File

@@ -1,52 +1,178 @@
# DA_HapticProfile — Data Asset
**Parent Class:** `UDataAsset`
**Dependencies:** [`BPC_HapticsController`](../12-settings/)
**Purpose:** Defines haptic feedback profiles for controller vibration and force feedback effects tied to gameplay events.
**Parent Class:** `UPrimaryDataAsset`
**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. 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
### Core Fields
| Field | Type | Description |
|-------|------|-------------|
| `ProfileTag` | `FGameplayTag` | Unique haptic profile identifier |
| `EventType` | `EHapticEvent` | Damage, Heartbeat, Explosion, Footstep, WeaponFire, AmbientPulse |
| `IntensityCurve` | `UCurveFloat*` | Vibration intensity over time |
| `Duration` | `float` | Total haptic effect duration (seconds) |
| `MotorMask` | `EHapticMotor` | Left, Right, Both |
| `Priority` | `int32` | Higher priority overrides lower during conflicts |
| `bCanInterrupt` | `bool` | Can this effect be interrupted by higher priority |
| `PlatformMinIntensity` | `float` | Minimum intensity per platform capability |
| `ProfileTag` | `FGameplayTag` | Unique haptic profile identifier (e.g., `Haptic.Damage.Heavy`) |
| `EventType` | `EHapticEvent` | Event category this profile handles |
| `ForceFeedbackEffect_Generic` | `UForceFeedbackEffect*` | Waveform curve asset for PC/Xbox generic controllers |
| `ForceFeedbackEffect_PS5` | `UForceFeedbackEffect*` | Waveform curve asset for PS5 DualSense (higher fidelity) |
| `ForceFeedbackEffect_Xbox` | `UForceFeedbackEffect*` | Waveform curve asset for Xbox controllers (optional override) |
| `IntensityCurve` | `UCurveFloat*` | Optional: vibration intensity over time (0.01.0) |
| `Duration` | `float` | Total haptic effect duration in seconds |
| `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.01.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 (09) |
| `DualSense_TriggerStrength` | `int32` | PS5 trigger effect strength (08) |
### 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
- 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
- `ProfileTag` must be unique
- `Duration` must be > 0
- `IntensityCurve` must be valid
- `ProfileTag` must be unique across all profiles
- `Duration` must be > 0.0
- At least one `ForceFeedbackEffect_*` must be assigned (Generic falls back for all platforms)
- `Priority` must be 0100
- If `bIsHeartbeatProfile` is true, `TargetBPMRange_Min` and `TargetBPMRange_Max` must be set
- If `DualSense_TriggerSide != "None"`, trigger effect fields must be valid
---
## Example Data
### Damage Profile — Heavy Hit
| Field | Value |
|-------|-------|
| ProfileTag | `Haptic.Damage.Critical` |
| EventType | Damage |
| Duration | 0.5 |
| MotorMask | Both |
| Priority | 10 |
| `ProfileTag` | `Haptic.Damage.Heavy` |
| `EventType` | `HeavyDamage` |
| `Duration` | `0.4` |
| `MotorMask` | `Both` |
| `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
- [`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
- 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: 020 (ambient/footsteps), 3050 (weapons/reload/pickups), 6080 (damage), 90100 (scares/death).
- The `Haptic.` namespace should be registered in `DT_Tags_Player.csv` so `DA_GameTagRegistry` validates them at startup.

View File

@@ -5,7 +5,7 @@ Primary Data Asset that defines all Enhanced Input bindings across three platfor
## Dependencies
- **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`
## 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) |
| `ContextDefinitions` | `TArray<S_ContextDefinition>` | `Empty` | `Contexts` | IMC asset references and defaults |
| `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 |
| `AxisInvertSettings` | `TMap<FName, Bool>` | `Empty` | `Accessibility` | Per-action axis inversion (e.g., "IA_Look" = InvertY) |

View 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 9001600 / 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.51.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 (03) |
| `PostProcessQuality` | `int32` | Post-process complexity (03) |
| `ViewDistanceQuality` | `int32` | View distance tier (03) |
| `FoliageQuality` | `int32` | Foliage density tier (03) |
| `AntiAliasingQuality` | `int32` | AA quality tier (03) |
| `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.252.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.

View File

@@ -1,10 +1,14 @@
# 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
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
- **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.)
- **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) |
| `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 |
|-------|-------------|
| `PC_KeyboardMouse = 0` | Keyboard + Mouse |
| `Xbox = 1` | Xbox Series X\|S / Xbox One |
| `PS5_DualSense = 2` | PlayStation 5 DualSense |
| `PC_KeyboardMouse = 0` | Keyboard + Mouse (maps to `PC_Win64`, `PC_Linux`, `Mac`) |
| `Xbox = 1` | Xbox Series X\|S / Xbox One (maps to `Xbox_Series` or `Xbox_One`) |
| `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_HidingSystem` | `Function Call` | `SS_EnhancedInputManager::PushContext(Hiding) / PopContext(Hiding)` |
| `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_InventoryMenu` | `Function Call` | `SS_EnhancedInputManager::PushContext(WristwatchUI)` |
| `BP_PuzzleDeviceActor` | `Function Call` | `SS_EnhancedInputManager::PushContext(Inspection)` |

View File

@@ -1,5 +1,9 @@
# 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
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