Compare commits

...

34 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
Lefteris Notas
3ca87a7893 Add asset creation sheets for various systems and UI components
- Created Save/Load & Death Loop asset sheet detailing checkpoint, death handling, and respawn systems.
- Added UI widget creation sheets for pause menu, settings menu, inventory, journal viewer, objective display, notification toast, screen effect controller, accessibility UI, diegetic HUD frame, menu flow controller, and credits screen.
- Implemented HUD controller and interaction prompt display assets with detailed wiring instructions.
- Established narrative systems asset sheet including components for narrative state, objectives, dialogue playback, and branching consequences.
- Developed weapons, AI, and adaptive systems asset creation sheet outlining weapon base, enemy AI components, and adaptive gameplay mechanics.
- Compiled meta, settings, and polish asset creation sheet covering progress tracking, achievement systems, accessibility settings, and various polish components.
- Defined input actions and mapping contexts for enhanced input system, including gameplay actions and context priorities.
- Created state management assets including enums, structs, data asset, and state manager component for action gating and state transitions.
2026-05-20 15:34:06 +03:00
Lefteris Notas
4a7c871f29 Update Developer Reference and add C++ Integration Guide
- Updated version in INDEX.md from 1.3 to 1.4, reflecting new files and C++ systems migrated.
- Added cpp-integration-guide.md detailing setup and usage for 12 C++ classes.
- Introduced cpp-blueprint-status.md to track the status of all 135 systems, including C++ and Blueprint specifications.
2026-05-20 15:16:32 +03:00
173 changed files with 24398 additions and 1298 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

@@ -0,0 +1,71 @@
# BP_CoreGameMode — Asset Implementation
> **UE5 Asset:** `/Game/Framework/Core/BP_CoreGameMode`
> **Parent Class:** `GM_CoreGameMode` (C++) or `GameModeBase`
> **Asset Type:** Blueprint Class (GameMode)
---
## Create This Asset
### Step 1: Create Blueprint
1. Content Browser → `Content/Framework/Core/`
2. Right-click → **Blueprint Class**
3. Parent: `GM_CoreGameMode` (C++) OR `GameModeBase`
4. Name: `BP_CoreGameMode`
### Step 2: Set as Default GameMode
**Project Settings → Maps & Modes → Default GameMode** → `BP_CoreGameMode`
### Step 3: Configure Class Defaults
| Variable | Type | Value |
|----------|------|-------|
| `GameState Class` | `AGS_CoreGameState` | `BP_CoreGameState` |
| `Player Controller Class` | `APlayerController` | Your `BP_CorePlayerController` |
| `Default Pawn Class` | `APawn` | Your GASP player pawn |
| `HUD Class` | `AHUD` | `WBP_HUDController` |
---
## What to Wire
### Function: TransitionToChapter
```
Input: ChapterTag (GameplayTag)
[BlueprintCallable]
├─ Branch: CurrentGamePhase == Loading?
│ True → Print Warning: "Already loading" → Return
├─ Set CurrentChapterTag = ChapterTag
├─ Switch HasAuthority → True:
│ ├─ Get GameInstance → Cast to GI_GameFramework
│ ├─ SetGamePhase(Loading)
│ ├─ Get GameState → Cast to GS_CoreGameState → SetChapter(ChapterTag)
│ ├─ Open Level (by ChapterTag mapping — use Data Asset lookup)
│ └─ On level loaded → SetGamePhase(InGame)
└─ Call OnChapterTransition(ChapterTag)
```
### Function: HandlePlayerDead
```
Input: DeadController (AController)
[BlueprintCallable]
├─ IsValid(DeadController)? → False: Return
├─ Set bPauseAllowed = false
├─ Get GameInstance → Cast to GI_GameFramework → SetGamePhase(DeathLoop)
└─ TODO: Route to respawn or AltDeathSpace
```
### Event Dispatchers to Create
| Dispatcher | Parameters |
|------------|-----------|
| `OnChapterTransition` | `NewChapter` (GameplayTag) |
| `OnGameOverTriggered` | `EndingTag` (GameplayTag) |
---
## Test It
- [ ] PIE: check output log for "GM_CoreGameMode::InitGame"
- [ ] Check that player spawns with correct pawn
- [ ] Call `TransitionToChapter` → phase changes to Loading → level loads

View File

@@ -0,0 +1,88 @@
# BP_CoreGameState — Asset Implementation
> **UE5 Asset:** `/Game/Framework/Core/BP_CoreGameState`
> **Parent Class:** `GS_CoreGameState` (C++) or `GameStateBase`
> **Asset Type:** Blueprint Class (GameState)
---
## Create This Asset
### Step 1: Create Blueprint
1. Content Browser → `Content/Framework/Core/`
2. Right-click → **Blueprint Class**
3. Parent: `GS_CoreGameState` (C++) OR `GameStateBase`
4. Name: `BP_CoreGameState`
### Step 2: Assign in GameMode
Open `BP_CoreGameMode` → Class Defaults → `GameState Class``BP_CoreGameState`
---
## What to Wire
### Variables (if BP-only GameStateBase)
| Variable | Type | Replication | Default |
|----------|------|-------------|---------|
| `ElapsedPlayTime` | `Float` | Replicated | `0.0` |
| `ActiveChapterTag` | `GameplayTag` | Replicated | Empty |
| `ActiveNarrativePhase` | `GameplayTag` | Replicated | Empty |
| `bEncounterActive` | `Boolean` | Replicated | `false` |
| `ActiveObjectiveTags` | `Array<GameplayTag>` | Replicated | Empty |
### Override: BeginPlay
```
[Event BeginPlay]
├─ Parent: BeginPlay
├─ Get GameInstance → Cast to GI_GameFramework → Store as CachedFramework
└─ Enable Tick
```
### Override: Tick
```
[Event Tick]
├─ IsValid(CachedFramework)? AND CurrentGamePhase == InGame?
│ True → ElapsedPlayTime += DeltaSeconds
│ ├─ TimeUpdateAccumulator += DeltaSeconds
│ └─ Accumulator >= 1.0?
│ ├─ Reset accumulator
│ └─ Call OnElapsedPlayTimeUpdated(ElapsedPlayTime)
```
### Function: SetChapter
```
Input: ChapterTag (GameplayTag)
[Server-only]
├─ If ChapterTag == ActiveChapterTag → Return
├─ Set ActiveChapterTag = ChapterTag
└─ Call OnChapterChanged(ChapterTag)
```
### Function: AddObjective / RemoveObjective
```
AddObjective(ObjectiveTag):
├─ If already in array → Return
├─ Add to ActiveObjectiveTags
└─ Call OnObjectiveTagsChanged
RemoveObjective(ObjectiveTag):
├─ Remove from array
└─ Call OnObjectiveTagsChanged
```
### Event Dispatchers to Create
| Dispatcher | Parameters |
|------------|-----------|
| `OnElapsedPlayTimeUpdated` | `ElapsedSeconds` (Float) |
| `OnChapterChanged` | `NewChapter` (GameplayTag) |
| `OnNarrativePhaseChanged` | `NewPhase` (GameplayTag) |
| `OnEncounterActiveStateChanged` | `bActive` (Boolean) |
| `OnObjectiveTagsChanged` | (none) |
---
## Test It
- [ ] PIE: check that GS_CoreGameState spawns
- [ ] Call SetChapter → OnChapterChanged fires → HUD updates
- [ ] Call AddObjective → OnObjectiveTagsChanged fires → objective list refreshes

View File

@@ -0,0 +1,78 @@
# BP_GameFramework — Asset Implementation
> **UE5 Asset:** `/Game/Framework/Core/BP_GameFramework`
> **Parent Class:** `GI_GameFramework` (C++) or `GameInstance` (BP-only)
> **Asset Type:** Blueprint Class (GameInstance)
---
## Create This Asset
### Step 1: Create Blueprint
1. Content Browser → `Content/Framework/Core/`
2. Right-click → **Blueprint Class**
3. Parent: `GI_GameFramework` (C++) OR `GameInstance`
4. Name: `BP_GameFramework`
### Step 2: Set as Game Instance Class
**Project Settings → Maps & Modes → Game Instance Class** → `BP_GameFramework`
### Step 3: Configure Class Defaults
| Variable | Type | Value |
|----------|------|-------|
| `TagRegistry` | `DA_GameTagRegistry` | Assign `DA_GameTagRegistry` asset |
| `bValidateTagsOnInit` | `Boolean` | `true` |
| `bLogTagsOnInit` | `Boolean` | `true` (dev) / `false` (ship) |
---
## What to Wire
### Override: Event Init
```
[Event Init]
├─ Parent: Event Init
├─ Print String: "BP_GameFramework: Init started"
├─ Branch: bValidateTagsOnInit?
│ True → Call ValidateFrameworkTags (see below)
├─ IsValid(TagRegistry)? → Branch
│ False → Print Error → Call OnFrameworkInitFailed("TagRegistry not assigned")
│ True →
│ ├─ Set bFrameworkInitialized = true
│ ├─ Call OnFrameworkReady
│ └─ Print String: "Framework ready"
```
### Function: ValidateFrameworkTags
```
[BlueprintCallable, Private]
├─ IsValid(TagRegistry)? → Branch
│ False → Print Error → Return
├─ TagRegistry → GetAllRegisteredTags → AllTags
├─ Array Length(AllTags) → TagCount
├─ Branch: TagCount == 0?
│ True → Print Warning: "No Gameplay Tags registered!"
│ False → Print: "{TagCount} tags registered"
├─ Branch: bLogTagsOnInit?
│ True → TagRegistry → LogAllTags
└─ Return
```
### Function: IsFrameworkReady
```
[BlueprintPure, Public] → Boolean
└─ Return bFrameworkInitialized
```
### Event Dispatchers to Create
| Dispatcher | Parameters |
|------------|-----------|
| `OnFrameworkReady` | (none) |
| `OnFrameworkInitFailed` | `ErrorReason` (String) |
---
## Test It
- [ ] PIE: output log shows "Framework ready" and tag count
- [ ] Clear TagRegistry variable → PIE → `OnFrameworkInitFailed` fires
- [ ] Set `bLogTagsOnInit = true` → all tag names printed to log

View File

@@ -0,0 +1,68 @@
# DA_GameTagRegistry — Asset Implementation
> **UE5 Asset:** `/Game/Framework/Core/DA_GameTagRegistry`
> **Parent Class:** `DA_GameTagRegistry` (C++) or `PrimaryDataAsset` (BP-only)
> **Asset Type:** Data Asset
---
## Create This Asset
### Step 1: Create Data Asset
1. Content Browser → `Content/Framework/Core/`
2. Right-click → **Miscellaneous → Data Asset**
3. Class: `DA_GameTagRegistry` (if C++ compiled) OR `PrimaryDataAsset`
4. Name: `DA_GameTagRegistry`
### Step 2: Configure Class Defaults (if BP-only PrimaryDataAsset)
| Variable | Type | Value |
|----------|------|-------|
| `TagNamespace` | `Text` | `"Framework tag namespace documentation"` |
| `bIsFrameworkTag` | `Boolean` | `true` |
| `TagDataTables` | `Array<Data Table>` | Add all 11 DT_Tags_* tables |
### Step 3: Assign to GameInstance
Open `BP_GameFramework` → Class Defaults → `TagRegistry` → assign `DA_GameTagRegistry`
---
## What to Wire (BP-Only Version)
### Function: GetAllRegisteredTags
```
Inputs: none → Output: Array<GameplayTag>
[Pure Function Graph]
├─ Make Array<GameplayTag> → LocalTags
├─ ForEachLoop (TagDataTables)
│ ├─ Branch: IsValid(Array Element)?
│ │ True → Get Data Table Row Names
│ │ └─ ForEachLoop (RowNames)
│ │ └─ Get Data Table Row → Break GameplayTagTableRow → Tag
│ │ └─ ADD to LocalTags
└─ Return LocalTags
```
### Function: ValidateTag
```
Input: Tag (GameplayTag) → Output: Boolean
[Pure Function Graph]
└─ Is Gameplay Tag Valid(Tag) → Return
```
### Function: GetTagDisplayName
```
Input: Tag (GameplayTag) → Output: Text
[Pure Function Graph]
└─ Get Tag Display Name(Tag) → Return
```
---
## Test It
- [ ] Open `DA_GameTagRegistry` → verify `TagDataTables` has 11 entries
- [ ] PIE: output log shows `"N tags registered across 11 Data Tables"`
- [ ] Call `ValidateTag(Framework.Player.State.Alive)` → returns `true`
- [ ] Call `ValidateTag(Unregistered.Tag)` → returns `false`

View File

@@ -0,0 +1,59 @@
# DT_Tags_* — Data Tables (11 assets)
> **UE5 Assets:** `Content/Framework/Core/DataTables/DT_Tags_{Category}`
> **Row Structure:** `GameplayTagTableRow`
> **Count:** 11 tables
---
## Create These Assets
### Step 1: Create All 11 Data Tables
For each category below, repeat this process:
1. Content Browser → `Content/Framework/Core/DataTables/`
2. Right-click → **Miscellaneous → Data Table**
3. Row Structure: `GameplayTagTableRow`
4. Name: `DT_Tags_{Category}`
5. Right-click the Data Table → **Import** → select the CSV file from `docs/blueprints/01-core/data-tables/DT_Tags_{Category}.csv`
### The 11 Tables
| # | Data Table | CSV Source | Namespaces |
|---|-----------|------------|------------|
| 1 | `DT_Tags_Player` | `DT_Tags_Player.csv` | Player.State, Stress, Posture, Movement, Camera, Body, Overlay, Vitals |
| 2 | `DT_Tags_Interaction` | `DT_Tags_Interaction.csv` | Interaction.Type, Context, Prompt, HidingSpot, Traversal, Door |
| 3 | `DT_Tags_Item` | `DT_Tags_Item.csv` | Item.Type, Slot, Rarity, Context |
| 4 | `DT_Tags_Narrative` | `DT_Tags_Narrative.csv` | Narrative.Flag, Phase, Choice, Ending, Trial, Cutscene, Lore, Objective |
| 5 | `DT_Tags_AI` | `DT_Tags_AI.csv` | AI.Alert, Archetype, Stimulus, Behavior, Memory |
| 6 | `DT_Tags_Save` | `DT_Tags_Save.csv` | Save, DeathSpace, Checkpoint, Respawn, RunHistory |
| 7 | `DT_Tags_Environment` | `DT_Tags_Environment.csv` | Environment.Atmosphere, Scare, Light, Pacing, Performance |
| 8 | `DT_Tags_Combat` | `DT_Tags_Combat.csv` | Combat.Damage, Weapon, Ammo, FireMode, HitReaction, Feedback, Shield |
| 9 | `DT_Tags_State` | `DT_Tags_State.csv` | State.Action, Overlay, Vital, Gating |
| 10 | `DT_Tags_Audio` | `DT_Tags_Audio.csv` | Audio.Bus, Room, Parameter, Surface |
| 11 | `DT_Tags_Achievement` | `DT_Tags_Achievement.csv` | Achievement |
### Step 2: Register All Tables with Engine
**⚠️ CRITICAL — Without this, NO tags are recognized!**
1. **Project Settings → GameplayTags → Gameplay Tag Table List**
2. Click `+` 11 times
3. Assign each `DT_Tags_*` Data Table to a slot
4. Restart editor (or recompile Blueprints) to refresh tag cache
### CSV Format
Each CSV has 3 columns:
```
Name,Tag,DevComment
Alive,"Framework.Player.State.Alive","Player is alive"
Dead,"Framework.Player.State.Dead","Player is dead"
...
```
---
## Test It
- [ ] Open `DA_GameTagRegistry` → call `GetAllRegisteredTags()` → returns ~334 tags
- [ ] Make Literal Gameplay Tag → type `Framework.Player.State.Alive` → recognized (not red)
- [ ] `Is Gameplay Tag Valid(Framework.Player.State.Alive)``true`
- [ ] `Is Gameplay Tag Valid(Fake.Tag)``false`

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

@@ -0,0 +1,98 @@
# BPC_HealthSystem — Asset Implementation
> **UE5 Asset:** `/Game/Framework/Player/BPC_HealthSystem`
> **Parent Class:** `ActorComponent`
> **Attach To:** Player pawn + Enemy pawns
---
## Create This Asset
1. Content Browser → `Content/Framework/Player/`
2. Right-click → **Blueprint Class** → Parent: `ActorComponent`
3. Name: `BPC_HealthSystem`
---
## Variables to Add
| Variable | Type | Default | Category |
|----------|------|---------|----------|
| `MaxHealth` | `Float` | `100.0` | Config |
| `CurrentHealth` | `Float` | `100.0` | State |
| `bIsDead` | `Boolean` | `false` | State |
| `DamageResistance` | `Float` | `0.0` | Config |
| `bCanHeal` | `Boolean` | `true` | Config |
| `HealthRegenRate` | `Float` | `0.0` | Config |
| `HealthRegenDelay` | `Float` | `5.0` | Config |
| `LastDamageTime` | `Float` | `0.0` | State |
---
## What to Wire
### Override: BeginPlay
```
[Event BeginPlay]
├─ Set CurrentHealth = MaxHealth
└─ Enable Tick (if regen > 0)
```
### Function: ApplyDamage
```
Input: DamageAmount(Float), DamageCauser(AActor), DamageType(GameplayTag)
[BlueprintCallable]
├─ Branch: bIsDead? → True: Return 0
├─ Calculate: EffectiveDamage = DamageAmount * (1.0 - DamageResistance)
├─ Set CurrentHealth = FMax(CurrentHealth - EffectiveDamage, 0)
├─ Set LastDamageTime = GetGameTimeInSeconds
├─ Call OnHealthChanged(CurrentHealth, MaxHealth, -EffectiveDamage)
├─ Branch: CurrentHealth <= 0?
│ True → Set bIsDead = true → Call OnDeath(DamageCauser)
└─ Return EffectiveDamage
```
### Function: Heal
```
Input: HealAmount(Float)
[BlueprintCallable]
├─ Branch: bIsDead? → True: Return 0
├─ Branch: Not bCanHeal? → True: Return 0
├─ Calculate: ActualHeal = FMin(HealAmount, MaxHealth - CurrentHealth)
├─ Set CurrentHealth += ActualHeal
├─ Call OnHealthChanged(CurrentHealth, MaxHealth, ActualHeal)
└─ Return ActualHeal
```
### Override: Tick
```
[Event Tick]
├─ Branch: bIsDead OR HealthRegenRate <= 0 → Return
├─ Branch: (GameTime - LastDamageTime) < HealthRegenDelay → Return
├─ Heal(HealthRegenRate * DeltaSeconds)
```
### Event Dispatchers to Create
| Dispatcher | Parameters |
|------------|-----------|
| `OnHealthChanged` | `Current` (Float), `Max` (Float), `Delta` (Float) |
| `OnDeath` | `Killer` (AActor) |
| `OnHealed` | `Amount` (Float), `Healer` (AActor) |
| `OnDamageReceived` | `Amount` (Float), `Causer` (AActor), `Type` (GameplayTag) |
---
## Attach to Pawn
1. Open player pawn Blueprint
2. **Add Component → BPC_HealthSystem**
3. Optionally adjust `MaxHealth`, `DamageResistance` in Details panel
---
## Test It
- [ ] PIE: check BeginPlay → CurrentHealth = 100
- [ ] Call ApplyDamage(25) → health drops to 75 → OnHealthChanged fires
- [ ] Call Heal(10) → health rises to 85 → OnHealed fires
- [ ] Call ApplyDamage(100) → death → OnDeath fires → bIsDead = true

View File

@@ -0,0 +1,138 @@
# BPC_StaminaSystem / BPC_StressSystem / BPC_MovementStateSystem
# BPC_HidingSystem / BPC_EmbodimentSystem / BPC_CameraStateLayer / BPC_PlayerMetricsTracker
#
# Player Component Assets — Quick Creation Sheet (7 components)
> **UE5 Path:** `Content/Framework/Player/`
> **Parent Class:** `ActorComponent`
> **Attach To:** Player pawn
---
## Quick Creation — All 7 Components
For each component below:
1. Content Browser → `Content/Framework/Player/`
2. Right-click → **Blueprint Class** → Parent: `ActorComponent`
3. Name: as listed
4. Attach to player pawn Blueprint via **Add Component**
---
## BPC_StaminaSystem
| Variable | Type | Default |
|----------|------|---------|
| `MaxStamina` | Float | `100.0` |
| `CurrentStamina` | Float | `100.0` |
| `SprintDrainRate` | Float | `10.0` |
| `StaminaRegenRate` | Float | `5.0` |
| `ExhaustionThreshold` | Float | `10.0` |
| `bExhausted` | Boolean | `false` |
**Key Functions:** `ConsumeStamina(Amount)`, `IsExhausted()`
**Dispatchers:** `OnStaminaChanged`, `OnExhaustionStateChanged`
---
## BPC_StressSystem
| Variable | Type | Default |
|----------|------|---------|
| `CurrentStress` | Float | `0.0` |
| `StressDecayRate` | Float | `1.0` |
| `StressTier` | Enum (E_PlayerVitalSignals) | `Calm` |
**Key Functions:** `AddStress(Amount, Source)`, `GetStressTier()`
**Dispatchers:** `OnStressChanged`, `OnStressTierChanged(Tier)`
**Stress Tiers:** Calm → Tense → Anxious → Fearful → Catatonic
---
## BPC_MovementStateSystem
| Variable | Type | Default |
|----------|------|---------|
| `CurrentMovementMode` | GameplayTag | `Framework.Player.Movement.Standing` |
| `CurrentPosture` | GameplayTag | `Framework.Player.Posture.Standing` |
| `bIsSprinting` | Boolean | `false` |
| `bIsCrouching` | Boolean | `false` |
**Key Functions:** `SetMovementMode(Tag)`, `SetPosture(Tag)`, `GetMovementIntensity()`
**Dispatchers:** `OnMovementModeChanged`, `OnPostureChanged`
**GASP Liaison:** This component updates GASP variables — never modify GASP directly.
---
## BPC_HidingSystem
| Variable | Type | Default |
|----------|------|---------|
| `CurrentHideState` | GameplayTag | `Framework.Player.State.Exposed` |
| `bIsPeeking` | Boolean | `false` |
| `bIsHoldingBreath` | Boolean | `false` |
| `BreathHoldMax` | Float | `8.0` |
| `BreathHoldCurrent` | Float | `8.0` |
**Key Functions:** `EnterHide(HidingSpot)`, `ExitHide()`, `Peek()`, `HoldBreath()`
**Dispatchers:** `OnHideStateChanged`, `OnBreathHoldChanged`
---
## BPC_EmbodimentSystem
| Variable | Type | Default |
|----------|------|---------|
| `bFirstPersonBodyVisible` | Boolean | `true` |
| `bArmsOnlyMode` | Boolean | `false` |
| `BodyMesh` | SkeletalMesh | (assign) |
**Key Functions:** `SetBodyVisibility(bVisible)`, `SetArmsOnlyMode(bActive)`
**Dispatchers:** `OnEmbodimentModeChanged`
---
## BPC_CameraStateLayer
| Variable | Type | Default |
|----------|------|---------|
| `BaseFOV` | Float | `90.0` |
| `CurrentFOV` | Float | `90.0` |
| `FOVSmoothSpeed` | Float | `5.0` |
| `CameraOffset` | Vector | `(0,0,0)` |
**Key Functions:** `SetFOVOverride(Tag, FOV)`, `ClearFOVOverride(Tag)`, `SetCameraOffset(Tag, Offset)`
**Dispatchers:** `OnFOVChanged`, `OnCameraOffsetChanged`
---
## BPC_PlayerMetricsTracker
| Variable | Type | Default |
|----------|------|---------|
| `TotalShotsFired` | Integer | `0` |
| `TotalShotsHit` | Integer | `0` |
| `TotalDeaths` | Integer | `0` |
| `TotalKills` | Integer | `0` |
| `Accuracy` | Float | `0.0` |
| `KDRatio` | Float | `0.0` |
**Key Functions:** `RecordShot(bHit)`, `RecordDeath()`, `RecordKill()`, `GetAccuracy()`
**Dispatchers:** `OnMetricsUpdated`
---
## Test All Components
- [ ] All 7 components visible on player pawn
- [ ] BeginPlay sets default values correctly
- [ ] Each dispatcher fires when its state changes
- [ ] Components don't conflict (Stamina drain doesn't affect Stress directly — let StateManager coordinate)

View File

@@ -0,0 +1,129 @@
# Interaction Systems — Asset Creation Sheet (8 assets)
> **UE5 Path:** `Content/Framework/Interaction/`
---
## BPC_InteractionDetector (Component)
**Parent:** `ActorComponent` → Attach to player pawn
| Variable | Type | Default |
|----------|------|---------|
| `InteractionRange` | Float | `300.0` |
| `InteractionChannel` | TraceChannel | `Visibility` |
| `HoldDuration` | Float | `0.5` |
| `CurrentFocusedActor` | Actor | None |
**Key Logic:**
```
Tick → LineTraceByChannel(Camera Forward * Range)
├─ Hit actor implements I_Interactable?
│ True → if different from CurrentFocused → Call OnFocusBegin on new, OnFocusEnd on old
│ False → Clear focus
├─ If holding interact → accumulate hold timer
│ Timer >= HoldDuration → trigger OnInteract
└─ Fire dispatchers: OnFocusBegin, OnFocusEnd, OnHoldProgress
```
**Dispatchers:** `OnInteractionDetected(Actor, PromptText)`, `OnFocusLost`, `OnHoldProgress(Float)`
---
## I_HidingSpot (Interface)
**Create:** Right-click → **Blueprint → Blueprint Interface** → Name: `I_HidingSpot`
| Function | Inputs | Outputs |
|----------|--------|---------|
| `CanPlayerHide` | `Player` (Actor) | `Boolean`, `BlockReason` (Text) |
| `OnPlayerEnter` | `Player` (Actor) | — |
| `OnPlayerExit` | `Player` (Actor) | — |
| `GetEntryTransform` | — | `Transform` |
| `GetPeekTransforms` | — | `Array<Transform>` |
| `IsOccupied` | — | `Boolean` |
| `GetHidingSpotType` | — | `GameplayTag` |
---
## BPC_DiegeticDisplay (Component)
**Parent:** `ActorComponent` → Attach to wristwatch/helmet actor
| Variable | Type | Default |
|----------|------|---------|
| `DisplayWidgetClass` | `WBP_DiegeticHUDFrame` | Assign |
| `RenderTarget` | `TextureRenderTarget2D` | 512x512 |
| `bIsActive` | Boolean | `false` |
---
## BP_DoorActor (Actor)
**Parent:** `Actor` → Add StaticMesh (door frame) + StaticMesh (door panel)
| Variable | Type | Default |
|----------|------|---------|
| `DoorState` | GameplayTag | `Framework.Interaction.Door.Closed` |
| `bIsLocked` | Boolean | `false` |
| `RequiredKeyTag` | GameplayTag | Empty |
| `OpenAngle` | Float | `90.0` |
| `OpenSpeed` | Float | `3.0` |
**Implements:** `I_Interactable`, `I_Lockable`, `I_Toggleable`, `I_Persistable`
---
## BP_PuzzleDeviceActor (Actor)
**Parent:** `Actor` → Base class for all puzzle devices
| Variable | Type | Default |
|----------|------|---------|
| `PuzzleData` | `DA_PuzzleData` | Assign |
| `CurrentStep` | Integer | `0` |
| `bIsSolved` | Boolean | `false` |
| `ActivationSequence` | `Array<Actor>` | Linked devices |
---
## BPC_ContextualTraversalSystem (Component)
**Attach to:** Player pawn — works with GASP Motion Warping
| Variable | Type | Default |
|----------|------|---------|
| `TraversalTraceDistance` | Float | `200.0` |
| `VaultHeight` | Float | `120.0` |
| `MantleHeight` | Float | `200.0` |
---
## BPC_PhysicsDragSystem (Component)
**Attach to:** Player pawn
| Variable | Type | Default |
|----------|------|---------|
| `DragForce` | Float | `500.0` |
| `HoldDistance` | Float | `200.0` |
| `HeldActor` | Actor | None |
---
## BPC_UsableWorldObjectSystem (Component)
**Attach to:** Player pawn
| Variable | Type | Default |
|----------|------|---------|
| `CurrentUsable` | Actor | None |
| `bIsUsing` | Boolean | `false` |
---
## Test These
- [ ] Walk up to door → prompt "Open Door [E]" → press E → door opens
- [ ] Locked door → prompt "Locked — Requires Key Card" → can't open
- [ ] Run toward low wall → auto-vault (ContextualTraversal)
- [ ] Grab physics object → drags in front of player → release throws

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

@@ -0,0 +1,111 @@
# Inventory Systems — Asset Creation Sheet (10 assets)
> **UE5 Path:** `Content/Framework/Inventory/`
---
## C++ Already Done
- `BPC_InventorySystem` (31) — attach directly to pawn
- `DA_ItemData` (07) — create Data Asset instances per item
---
## BP Assets to Create
### BP_ItemPickup (Actor)
**Parent:** `Actor` → Add StaticMesh, SphereCollision, RotatingMovement
| Variable | Type | Default |
|----------|------|---------|
| `ItemData` | `DA_ItemData` | Assign |
| `Quantity` | Integer | `1` |
| `BobAmplitude` | Float | `0.5` |
| `BobSpeed` | Float | `2.0` |
**Implements:** `I_Interactable`
**OnInteract:**
```
├─ Get Player → BPC_InventorySystem → CanAddItem(ItemData)?
│ True → AddItem(ItemData, Quantity) → Destroy self
│ False → Show "Inventory Full" prompt
```
### BPC_ContainerInventory (Component)
**Attach to:** Chests, drawers, cabinets, safes
| Variable | Type | Default |
|----------|------|---------|
| `ContainerSlots` | `Array<FInventorySlot>` | Empty |
| `ContainerSize` | Integer | `12` |
| `bIsOpen` | Boolean | `false` |
### BPC_ActiveItemSystem (Component)
**Attach to:** Player pawn — manages quick-slot item
| Variable | Type | Default |
|----------|------|---------|
| `QuickSlotCount` | Integer | `4` |
| `ActiveItemIndex` | Integer | `0` |
| `QuickSlots` | `Array<DA_ItemData>` | Empty |
### BPC_CollectibleTracker (Component)
**Attach to:** Player pawn
| Variable | Type | Default |
|----------|------|---------|
| `FoundCollectibles` | `Array<GameplayTag>` | Empty |
| `TotalCollectibles` | Integer | `0` |
| `SetBonuses` | `Array<GameplayTag>` | Empty |
### BPC_ConsumableSystem (Component)
**Attach to:** Player pawn
| Variable | Type | Default |
|----------|------|---------|
| `LastConsumedItem` | `DA_ItemData` | None |
| `UseCooldown` | Float | `0.5` |
**UseItem(ItemData):**
```
├─ BPC_HealthSystem → Heal(Item.ConsumableData.HealthRestore)
├─ BPC_StressSystem → AddStress(-Item.ConsumableData.StressReduce)
└─ BPC_InventorySystem → RemoveItem(Item, 1)
```
### BPC_DocumentArchiveSystem (Component)
| Variable | Type |
|----------|------|
| `ArchivedDocuments` | `Array<GameplayTag>` |
| `bHasUnread` | Boolean |
### BPC_EquipmentSlotSystem (Component)
| Variable | Type | Default |
|----------|------|---------|
| `EquipmentSlots` | `Array<FInventorySlot>` | 6 slots |
| `EquippedWeapon` | `DA_ItemData` | None |
### BPC_ItemCombineSystem (Component)
| Variable | Type |
|----------|------|
| `CraftingRecipes` | `Array<S_CraftingRecipe>` |
### BPC_JournalSystem (Component)
| Variable | Type |
|----------|------|
| `ActiveQuests` | `Array<GameplayTag>` |
| `CompletedQuests` | `Array<GameplayTag>` |
### BPC_KeyItemSystem (Component)
| Variable | Type |
|----------|------|
| `KeyItems` | `Array<GameplayTag>` |
| `bAutoUseOnTarget` | Boolean |
---
## Test These
- [ ] Drop item pickup in level → walk over → item added to inventory
- [ ] Use health pack → health increases, item removed from inventory
- [ ] Combine items → new item appears in inventory
- [ ] Equip weapon → appears on character mesh

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

@@ -0,0 +1,124 @@
# Save/Load & Death Loop — Asset Creation Sheet (9 assets)
> **UE5 Path:** `Content/Framework/Save/`
---
## C++ Already Done
- `SS_SaveManager` (35) — auto-created subsystem, no BP needed
- `I_Persistable` (36) — in `I_InterfaceLibrary.h`
---
## BP Assets to Create
### BP_Checkpoint (Actor)
**Parent:** `Actor` → Add BoxCollision (trigger)
| Variable | Type | Default |
|----------|------|---------|
| `CheckpointTag` | GameplayTag | e.g. `Framework.Save.Checkpoint.Chapter1_Start` |
| `bActivated` | Boolean | `false` |
| `SpawnTransform` | Transform | (auto-set from actor location) |
**OnOverlapBegin(Player):**
```
├─ If bActivated → Return
├─ Set bActivated = true
├─ Get SaveManager → CreateCheckpoint(CheckpointTag)
├─ Play checkpoint sound + particle effect
└─ Show WBP_NotificationToast: "Checkpoint Reached"
```
---
### BPC_AltDeathSpaceSystem (Component)
**Attach to:** Player pawn
| Variable | Type | Default |
|----------|------|---------|
| `bInAltDeathSpace` | Boolean | `false` |
| `DeathSpaceScene` | Level | Assign |
| `ExitFound` | Boolean | `false` |
---
### BPC_DeathHandlingSystem (Component)
**Attach to:** Player pawn
| Variable | Type | Default |
|----------|------|---------|
| `DeathAnimation` | AnimMontage | Assign |
| `RespawnDelay` | Float | `3.0` |
| `bDeathSequencePlaying` | Boolean | `false` |
**Bind to:** `BPC_HealthSystem.OnDeath`
**OnDeath(Killer):**
```
├─ Play DeathAnimation
├─ Delay(3.0)
├─ Determine outcome:
│ ├─ AltDeathSpace chance → EnterAltDeathSpace
│ └─ Normal death → Call GM_CoreGameMode.HandlePlayerDead
└─ Call BPC_RunHistoryTracker.RecordDeath
```
---
### BPC_PersistentCorpseSystem (Component)
**Attach to:** Player pawn
| Variable | Type |
|----------|------|
| `CorpsePersistenceDuration` | Float = `-1` (infinite) |
| `CorpseSnapshotData` | `Array<Byte>` |
---
### BPC_PersistentWorldStateRecorder (Component)
**Attach to:** Player pawn or GameState
| Variable | Type |
|----------|------|
| `RecordedStates` | `Array<S_WorldStateEntry>` |
| `bIsRecording` | Boolean |
---
### BPC_PlayerRespawnSystem (Component)
**Attach to:** Player pawn
| Variable | Type | Default |
|----------|------|---------|
| `LastCheckpointTag` | GameplayTag | Empty |
| `bRespawning` | Boolean | `false` |
**Respawn():**
```
├─ Get SaveManager → LoadCheckpoint(active slot)
├─ Find BP_Checkpoint with LastCheckpointTag
├─ Teleport player to checkpoint transform
├─ Restore health, clear death state
└─ Call BPC_StateManager.RestorePreviousState
```
---
### BPC_RunHistoryTracker (Component)
**Attach to:** Player pawn
| Variable | Type |
|----------|------|
| `TotalDeaths` | Integer |
| `TotalPlayTime` | Float |
| `ChaptersCompleted` | `Array<GameplayTag>` |
| `CheckpointsReached` | `Array<GameplayTag>` |
---
## Test These
- [ ] Walk into checkpoint trigger → toast "Checkpoint Reached" → game saved
- [ ] Die → death animation plays → respawn at last checkpoint
- [ ] Alt death → void space loads → find exit → return to normal world
- [ ] Corpse persists after respawn

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

@@ -0,0 +1,197 @@
# WBP_PauseMenu / WBP_SettingsMenu / WBP_InventoryMenu
# WBP_JournalDocumentViewer / WBP_ObjectiveDisplay
# WBP_NotificationToast / WBP_ScreenEffectController
# WBP_AccessibilityUI / WBP_DiegeticHUDFrame
# WBP_MenuFlowController / WBP_CreditsScreen
#
# UI Widget Assets — Quick Creation Sheet (11 widgets)
> **UE5 Path:** `Content/Framework/UI/`
> **Parent Class:** `UserWidget`
---
## WBP_PauseMenu
**Canvas:** Vertical Box (centered) → Resume, Settings, Save, Load, Quit to Menu, Quit to Desktop
**Key Logic:**
```
Event Construct:
└─ Set Input Mode: UI + ShowCursor
btn_Resume.OnClicked:
└─ Call SS_UIManager.PopWidget() → Set Input Mode: Game
```
---
## WBP_SettingsMenu
**Canvas:** Tab buttons (Audio, Video, Controls, Gameplay, Accessibility) + settings panels
**Key Logic:**
```
Audio Tab → Volume sliders → SS_AudioManager.SetBusVolume(Category, Volume)
Video Tab → Resolution dropdown, Fullscreen toggle, Quality presets
Controls Tab → Key rebinding list → SS_EnhancedInputManager.RebindKey(Action, Key)
Gameplay Tab → Difficulty, subtitle toggle, aim assist
Accessibility Tab → WBP_AccessibilityUI embedded
On Apply → SaveSettings → SS_SettingsSystem.ApplyAndSave()
On Back → If dirty → "Save changes?" dialog
```
---
## WBP_InventoryMenu
**Canvas:** Grid panel (8 columns) + item detail panel on right + weight bar at bottom
**Key Logic:**
```
Event Construct:
├─ Get Pawn → BPC_InventorySystem → Bind OnInventoryChanged → RefreshGrid
└─ Bind OnWeightChanged → UpdateWeightBar
RefreshGrid:
├─ Clear Grid children
└─ For each slot → Create WBP_InventorySlot widget → Add to Grid
OnSlotClicked(SlotIndex):
├─ If dragging → SwapSlots
├─ If right-click → ShowContextMenu (Use, Equip, Drop, Examine)
└─ Show item detail in right panel
```
---
## WBP_JournalDocumentViewer
**Canvas:** ScrollBox with document entries + detail view on right
**Key Logic:**
```
Construct → BPC_DocumentArchiveSystem → GetAllDocuments()
└─ For each doc → create list item → on click → show full text + image
```
---
## WBP_ObjectiveDisplay
**Canvas:** Vertical Box (top-right) — list of active objectives
**Key Logic:**
```
Bind GS_CoreGameState.OnObjectiveTagsChanged → Refresh
└─ For each ActiveObjectiveTag → lookup DA_ObjectiveData → show title + progress
```
---
## WBP_NotificationToast
**Canvas:** Border (top-center, slides in/out) → Icon + Title + Subtitle
**Key Logic:**
```
ShowToast(Title, Subtitle, Icon, Duration):
├─ Set text
├─ Play SlideIn animation
└─ Delay(Duration) → Play SlideOut → Remove from parent
```
---
## WBP_ScreenEffectController
**Canvas:** Full-screen overlay images (vignette, blood, healing, flash)
**Key Logic:**
```
PlayEffect(EffectType, Intensity, Duration):
├─ Switch EffectType
│ Damage → Show red vignette, fade to Intensity
│ Healing → Show green edge glow
│ Death → Show black fade
│ Flash → Show white screen
└─ Timeline: fade in, hold, fade out
Bind BPC_HealthSystem.OnHealthChanged → if damage → PlayEffect(Damage)
```
---
## WBP_AccessibilityUI
**Canvas:** Toggles and sliders for accessibility options
**Controls:**
- Subtitles: On/Off + size slider
- Colorblind Mode: Off/Protanopia/Deuteranopia/Tritanopia
- Controller Remap: Per-platform button mapping
- Difficulty: Story/Easy/Normal/Hard/Nightmare
- Aim Assist: Off/Low/Medium/High
**On change → call SS_SettingsSystem.SaveAccessibilitySettings()**
---
## WBP_DiegeticHUDFrame
**Canvas:** Empty container that renders to a material on the player's wristwatch/helmet
**Key Logic:**
- This widget renders to a RenderTarget
- The render target is assigned to the wristwatch mesh material
- Contents show mini-HUD: health dots, ammo count, compass
---
## WBP_MenuFlowController
**Not visual** — state machine that manages menu transitions.
**States:** MainMenu → NewGame → Loading → InGame → Pause → Settings → Death → Credits
**Key Logic:**
```
PushMenu(MenuWidget):
├─ If current menu → hide
├─ Create new menu → add to viewport
└─ Store in MenuStack
PopMenu():
├─ Remove top menu from viewport
└─ Restore previous menu
OnBackPressed:
├─ If menu stack has entries → PopMenu
└─ If game and no stack → Show PauseMenu
```
---
## WBP_CreditsScreen
**Canvas:** ScrollBox — auto-scrolling credits text
**Key Logic:**
```
Event Construct:
└─ Start Timeline → scroll credits text from bottom to top over 60 seconds
OnSkip (any key press):
└─ Jump to end → show "Thanks for Playing" → auto-transition to MainMenu
```
---
## Test All Widgets
- [ ] Each widget displays in isolation (right-click → Run Widget)
- [ ] Pause menu toggles cursor correctly
- [ ] Settings save and persist across sessions
- [ ] Inventory grid updates when items change
- [ ] Notification toast animates in/out
- [ ] Screen effects don't block input

View File

@@ -0,0 +1,72 @@
# WBP_HUDController — Asset Implementation
> **UE5 Asset:** `/Game/Framework/UI/WBP_HUDController`
> **Parent Class:** `UserWidget`
> **Purpose:** Root HUD widget — manages all HUD sub-widgets
---
## Create This Widget
1. Content Browser → `Content/Framework/UI/`
2. Right-click → **User Interface → Widget Blueprint**
3. Name: `WBP_HUDController`
---
## Designer Canvas Setup
```
Canvas Panel (root)
├─ WBP_InteractionPromptDisplay (bottom-center)
├─ WBP_ObjectiveDisplay (top-right)
├─ WBP_NotificationToast (top-center)
├─ HealthBar (ProgressBar) (bottom-left) [inline or sub-widget]
├─ StaminaBar (ProgressBar) (bottom-left, below health)
├─ AmmoDisplay (TextBlock) (bottom-right)
├─ Crosshair (Image) (center)
└─ WBP_DiegeticHUDFrame (full screen, behind everything)
```
---
## What to Wire
### Event: Event Construct
```
[Event Construct]
├─ Get Owning Player Pawn → Store as CachedPawn
├─ Get Pawn → GetComponentByClass(BPC_HealthSystem) → Bind OnHealthChanged
├─ GetComponentByClass(BPC_StaminaSystem) → Bind OnStaminaChanged
├─ Get GameState → Cast to GS_CoreGameState → Bind OnObjectiveTagsChanged
└─ Get GameInstance → Cast to GI_GameFramework → Bind OnGamePhaseChanged
```
### Health Binding (Custom Event)
```
[OnHealthChanged(Current, Max, Delta)]
├─ HealthBar → Set Percent(Current / Max)
├─ Branch: Delta < 0?
│ True → Play Animation (DamageVignette)
└─ Branch: Current <= 0?
└─ Call WBP_ScreenEffectController → PlayDeathEffect
```
### GamePhase Binding
```
[OnGamePhaseChanged(NewPhase)]
├─ Switch on NewPhase:
│ MainMenu → Hide HUD
│ InGame → Show HUD
│ Paused → Dim HUD, show pause overlay
│ Cutscene → Hide HUD
│ DeathLoop → Show death screen
```
---
## Test It
- [ ] PIE with player pawn: HUD displays
- [ ] Take damage: health bar updates
- [ ] Sprint: stamina bar drains
- [ ] Open menu: HUD hides/disables

View File

@@ -0,0 +1,70 @@
# WBP_InteractionPromptDisplay — Asset Implementation
> **UE5 Asset:** `/Game/Framework/UI/WBP_InteractionPromptDisplay`
> **Parent Class:** `UserWidget`
---
## Create This Widget
1. Content Browser → `Content/Framework/UI/`
2. Right-click → **User Interface → Widget Blueprint**
3. Name: `WBP_InteractionPromptDisplay`
---
## Designer Canvas
```
Canvas Panel
└─ Border (background, semi-transparent black)
└─ Horizontal Box
├─ Image (key icon, e.g. "E" key)
└─ Vertical Box
├─ TextBlock "PromptText" (e.g. "Pick Up")
└─ TextBlock "SubText" (e.g. "MedKit")
```
---
## What to Wire
### Function: ShowPrompt
```
Inputs: PromptText(Text), SubText(Text), KeyIcon(Texture2D), bShowHoldProgress(Boolean)
├─ Set PromptText = PromptText
├─ Set SubText = SubText
├─ Set KeyIcon = KeyIcon
├─ Branch: bShowHoldProgress?
│ True → Show HoldProgressBar
├─ Set Visibility = Visible
└─ Play Animation (FadeIn)
```
### Function: HidePrompt
```
├─ Set Visibility = Hidden
└─ Stop All Animations
```
### Function: UpdateHoldProgress
```
Input: Progress(0.0-1.0)
└─ HoldProgressBar → Set Percent(Progress)
```
---
## Events to Handle
| Event | Action |
|-------|--------|
| `BPC_InteractionDetector.OnFocusBegin` | ShowPrompt with object data |
| `BPC_InteractionDetector.OnFocusEnd` | HidePrompt |
| `BPC_InteractionDetector.OnHoldProgress` | UpdateHoldProgress |
---
## Test It
- [ ] Look at an interactable object → prompt appears
- [ ] Hold interact key → progress bar fills
- [ ] Look away → prompt disappears

View File

@@ -0,0 +1,75 @@
# WBP_MainMenu — Asset Implementation
> **UE5 Asset:** `/Game/Framework/UI/WBP_MainMenu`
> **Parent Class:** `UserWidget`
---
## Create This Widget
1. Content Browser → `Content/Framework/UI/`
2. Right-click → **User Interface → Widget Blueprint**
3. Name: `WBP_MainMenu`
---
## Designer Canvas
```
Canvas Panel
└─ Vertical Box (centered)
├─ Image (game logo) "LogoImage"
├─ Spacer
├─ Button "btn_NewGame" "New Game"
├─ Button "btn_Continue" "Continue"
├─ Button "btn_LoadGame" "Load Game"
├─ Button "btn_Settings" "Settings"
├─ Button "btn_Credits" "Credits"
└─ Button "btn_Quit" "Quit Game"
└─ TextBlock "VersionText" (bottom-right) "v1.0.0"
```
---
## What to Wire
### Event: Event Construct
```
[Event Construct]
├─ Get GameInstance → Cast to GI_GameFramework
├─ Get SaveManager → GetSlotManifest()
├─ Branch: Any slot has data?
│ True → btn_Continue.SetIsEnabled(true)
│ False → btn_Continue.SetIsEnabled(false)
└─ Set Input Mode: UI + Show Cursor
```
### Button Handlers
```
btn_NewGame.OnClicked:
├─ Call GI_GameFramework.ClearAllSessionFlags()
├─ Call GI_GameFramework.SetActiveSlot(new slot index)
├─ Call OpenLevel (first chapter map)
└─ Set Input Mode: Game
btn_Continue.OnClicked:
├─ Call SaveManager.QuickLoad()
└─ OnLoadComplete → OpenLevel (saved chapter)
btn_LoadGame.OnClicked:
└─ Push WBP_LoadGameMenu to SS_UIManager stack
btn_Settings.OnClicked:
└─ Push WBP_SettingsMenu
btn_Quit.OnClicked:
└─ Quit Game (or ConsoleCommand "quit")
```
---
## Test It
- [ ] Game opens at main menu → cursor visible
- [ ] No save data → Continue grayed out
- [ ] Click New Game → level loads → cursor hidden
- [ ] Click Quit → game exits

View File

@@ -0,0 +1,108 @@
# Narrative Systems — Asset Creation Sheet (11 assets)
> **UE5 Path:** `Content/Framework/Narrative/`
---
## Components (attach to player pawn or GameState)
### BPC_NarrativeStateSystem
| Variable | Type | Default |
|----------|------|---------|
| `ActiveFlags` | `Array<GameplayTag>` | Empty |
| `CurrentChapter` | GameplayTag | Empty |
| `CurrentPhase` | GameplayTag | Empty |
**SetFlag(Tag):** Add to ActiveFlags → broadcast OnNarrativeFlagSet
### BPC_ObjectiveSystem
| Variable | Type | Default |
|----------|------|---------|
| `ActiveObjectives` | `Array<GameplayTag>` | Empty |
| `CompletedObjectives` | `Array<GameplayTag>` | Empty |
**ActivateObjective(Tag):** → Add to Active → broadcast OnObjectiveActivated → GS_CoreGameState.AddObjective(Tag)
### BPC_DialoguePlaybackSystem
| Variable | Type |
|----------|------|
| `DialogueQueue` | `Array<FDialogueLine>` |
| `bIsPlaying` | Boolean |
| `bAutoAdvance` | Boolean |
**PlayDialogue(DialogueData):** → Show subtitles → play VO → advance queue
### BPC_DialogueChoiceSystem
| Variable | Type |
|----------|------|
| `CurrentChoices` | `Array<FDialogueChoice>` |
| `SelectedChoiceIndex` | Integer |
**PresentChoices(Choices):** → Show WBP_DialogueChoice widget → wait for selection → return
### BPC_BranchingConsequenceSystem
| Variable | Type |
|----------|------|
| `PendingConsequences` | `Array<FDialogueChoice>` |
**ExecuteConsequence(Choice):** → Grant items, set narrative flags, change relationships, trigger cutscenes
### BPC_TrialScenarioSystem
| Variable | Type | Default |
|----------|------|---------|
| `TrialTimer` | Float | `0.0` |
| `bTrialActive` | Boolean | `false` |
| `TrialSuccessThreshold` | Float | `60.0` |
### BPC_CutsceneBridge
| Variable | Type |
|----------|------|
| `CurrentCutscene` | `DA_CutsceneData` |
| `bCutscenePlaying` | Boolean |
**PlayCutscene(Data):** → Set phase to Cutscene → play Sequence → on finish → restore phase
### BPC_LoreUnlockSystem
| Variable | Type |
|----------|------|
| `UnlockedLore` | `Array<GameplayTag>` |
**UnlockLore(Tag):** → Add to unlocked → show notification → add to journal
### BPC_EndingAccumulator
| Variable | Type |
|----------|------|
| `EndingConditions` | `TMap<GameplayTag, Float>` |
| `DominantEnding` | GameplayTag |
---
## Data Assets (create instances per content)
| Asset | Purpose |
|-------|---------|
| `DA_DialogueData` | Dialogue lines, speaker, VO, conditions |
| `DA_CutsceneData` | Level sequence, milestone flags, skip policy |
| `DA_ObjectiveData` | Title, description, prerequisites, rewards |
| `DA_TrialData` | Timer, success conditions, failure consequences |
---
## Actor — BP_NarrativeTriggerVolume
**Parent:** `TriggerVolume`
| Variable | Type |
|----------|------|
| `TriggerTag` | GameplayTag |
| `bTriggerOnce` | Boolean |
| `NarrativeActions` | `Array<S_NarrativeAction>` |
**OnOverlapBegin:** → Execute narrative actions (set flag, start dialogue, activate objective, play cutscene)
---
## Test These
- [ ] Walk into trigger → dialogue starts → subtitles show
- [ ] Dialogue choices appear → select option → consequence fires
- [ ] Objective activates → shows in HUD objective display
- [ ] Trial scenario starts → timer counts down → succeed/fail

Some files were not shown because too many files have changed in this diff Show More