- 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.
20 KiB
Player Character — BP_HorrorPlayerCharacter
Game: Project Void | Asset: BP_HorrorPlayerCharacter | Parent: GASP-based Character
Asset Path: Content/Game/Characters/BP_HorrorPlayerCharacter.uasset
Build Phase: 4 | Demonstrates: All 02-player systems (08-15) + 03-interaction (16) + 04-inventory (26,28,30,31,34) + 08-weapons (70,71,72) + 16-state (130)
Purpose
The player's physical embodiment in the game world. A GASP-based first-person character with all framework components attached. This is the single most important game asset — nearly every framework system interacts with it.
Architecture Overview
BP_HorrorPlayerCharacter (Character with GASP Animation Blueprint)
├── Components (auto-attached on spawn)
│ ├── CapsuleComponent (inherited)
│ ├── CameraComponent (first-person, head socket)
│ ├── CharacterMovementComponent (GASP — read-only)
│ ├── SkeletalMeshComponent (GASP — read-only)
│ │
│ ├── [02-player] BPC_HealthSystem # 08 — Health, damage, death
│ ├── [02-player] BPC_StaminaSystem # 09 — Sprint, action drain
│ ├── [02-player] BPC_StressSystem # 10 — Psychological stress
│ ├── [02-player] BPC_MovementStateSystem # 11 — Walk/Sprint/Crouch
│ ├── [02-player] BPC_HidingSystem # 12 — Hide in lockers/beds
│ ├── [02-player] BPC_EmbodimentSystem # 13 — First-person body
│ ├── [02-player] BPC_CameraStateLayer # 14 — FOV/offset layers
│ ├── [02-player] BPC_PlayerMetricsTracker # 15 — Accuracy, style
│ │
│ ├── [03-interaction] BPC_InteractionDetector # 16 — Raycast interact
│ │
│ ├── [04-inventory] BPC_InventorySystem # 31 — Inventory grid
│ ├── [04-inventory] BPC_EquipmentSlotSystem # 30 — Weapon/tool slots
│ ├── [04-inventory] BPC_ActiveItemSystem # 26 — Quick-slot cycling
│ ├── [04-inventory] BPC_ConsumableSystem # 28 — Use medkits/etc
│ ├── [04-inventory] BPC_KeyItemSystem # 34 — Key items
│ ├── [04-inventory] BPC_JournalSystem # 33 — Objectives
│ ├── [04-inventory] BPC_CollectibleTracker # 27 — Collectibles found
│ ├── [04-inventory] BPC_DocumentArchiveSystem# 29 — Read documents
│ ├── [04-inventory] BPC_ItemCombineSystem # 32 — Combine items
│ │
│ ├── [08-weapons] BPC_AmmoComponent # 70 — Ammo pool
│ ├── [08-weapons] BPC_RecoilSystem # 77 — Weapon recoil
│ ├── [08-weapons] BPC_HitReactionSystem # 75 — Hit flinch
│ ├── [08-weapons] BPC_DamageReceptionSystem # 72 — Receive damage
│ ├── [08-weapons] BPC_CombatFeedbackComponent# 71 — Hit markers
│ │
│ ├── [16-state] BPC_StateManager # 130 — Action gating
│ │
│ ├── [07-narrative] BPC_NarrativeStateSystem # 58 — Narrative flags
│ ├── [07-narrative] BPC_ObjectiveSystem # 59 — Active quests
│ └── [07-narrative] BPC_EndingAccumulator # 68 — Ending tracker
│
├── Interfaces Implemented
│ ├── I_Damageable (take damage)
│ ├── I_Interactable (can be interacted with by NPCs)
│ └── I_Persistable (save/load state)
Creation Steps
Step 1 — Create Blueprint
Content Browser → Game/Characters/
Right-click → Blueprint Class
Parent Class: Your GASP Character (or Character if GASP is added to ABP only)
Name: BP_HorrorPlayerCharacter
Step 2 — Add All Components
In the Components panel, add each component from the list above. Order doesn't matter for Blueprint components — they auto-initialize on BeginPlay.
Tip: Create a Blueprint function InitializeAllComponents called from Event BeginPlay and verify every component is valid before proceeding.
Step 3 — Configure GASP Integration
[GASP Animation Blueprint Setup]
│
├─ Assign GASP AnimBP to SkeletalMeshComponent → Anim Class
│ └─ Default: ABP_GASP (read-only, do not modify)
│
├─ [GASP Notifies] (extend via Animation Notify overrides, NOT by editing GASP)
│ └─ Add AnimNotify slots to GASP montages in your ABP child:
│ ├─ Notify_Footstep → BPC_MovementStateSystem.OnFootstep()
│ ├─ Notify_EnterAction → BPC_StateManager.EnterAction(Tag)
│ ├─ Notify_ExitAction → BPC_StateManager.ExitAction()
│ ├─ Notify_DamageApplied → BPC_DamageReceptionSystem.ReceiveDamage()
│ └─ (etc — see animation-catalog.md for all 14 notifies)
│
└─ [Motion Matching Database]
└─ Assign your motion matching pose search database
(GASP handles locomotion blend — you provide the poses)
Step 4 — Wire Event BeginPlay
Event BeginPlay
│
├─ Parent: Event BeginPlay (essential for GASP init)
│
├─ [Validate All Components]
│ ├─ For each BPC_ component:
│ │ ├─ GetComponentByClass → IsValid? → Log
│ │ └─ If invalid: FL_GameUtilities.LogError("Missing component: X")
│ │
│ └─ REQUIRED components (game cannot function without):
│ ├─ BPC_HealthSystem → if missing, ERROR + disable input
│ ├─ BPC_InventorySystem → if missing, ERROR
│ ├─ BPC_StateManager → if missing, ERROR
│ └─ BPC_InteractionDetector → if missing, ERROR
│
├─ [Initialize Health]
│ ├─ BPC_HealthSystem.SetMaxHealth(100.0)
│ └─ BPC_HealthSystem.SetCurrentHealth(100.0)
│
├─ [Initialize Stamina]
│ ├─ BPC_StaminaSystem.SetMaxStamina(100.0)
│ └─ BPC_StaminaSystem.SetCurrentStamina(100.0)
│
├─ [Initialize Stress]
│ ├─ BPC_StressSystem.SetCurrentStress(0.0)
│ └─ BPC_StressSystem.SetStressDecayRate(1.0) // per second in safe areas
│
├─ [Initialize Movement]
│ ├─ BPC_MovementStateSystem.SetMovementMode(Walking)
│ └─ BPC_MovementStateSystem.SetPosture(Standing)
│
├─ [Initialize Camera]
│ ├─ BPC_CameraStateLayer.SetDefaultFOV(90.0)
│ └─ BPC_EmbodimentSystem.SetVisibilityMode(FirstPerson)
│
├─ [Initialize Inventory]
│ ├─ BPC_InventorySystem.Initialize(GridWidth=6, GridHeight=4, MaxWeight=50.0)
│ └─ BPC_EquipmentSlotSystem.Initialize()
│ └─ Create slots: PrimaryWeapon, Tool, Armor (3 equipment slots)
│
├─ [Initialize State Manager]
│ ├─ BPC_StateManager.SetupGatingRules(DA_StateGatingTable)
│ └─ BPC_StateManager.SetInitialState(Standing)
│
├─ [Bind System Interconnections]
│ │
│ ├─ BPC_HealthSystem.OnHealthChanged → BPC_StateManager.UpdateVitalSignals
│ ├─ BPC_HealthSystem.OnDeath → BPC_DeathHandlingSystem.HandleDeath
│ ├─ BPC_StressSystem.OnStressTierChanged → BPC_CameraStateLayer.SetStressBlur
│ ├─ BPC_StressSystem.OnStressTierChanged → SS_AudioManager.SetFloatParameter("Stress", value)
│ ├─ BPC_StaminaSystem.OnStaminaDepleted → BPC_StateManager.ForcePushState(Exhausted)
│ ├─ BPC_MovementStateSystem.OnModeChanged → BPC_StateManager.UpdateMovementState
│ ├─ BPC_MovementStateSystem.OnFootstep → SS_AudioManager.PlayFootstep(SurfaceTag)
│ ├─ BPC_HidingSystem.OnHideStateChanged → BPC_StateManager.SetOverlayState
│ ├─ BPC_HidingSystem.OnHideStateChanged → SS_EnhancedInputManager.PushContext(Hiding)
│ └─ (etc — any cross-system event binding)
│
└─ [Ready]
└─ Broadcast OnPlayerReady
└─ Other systems wait for this before querying player state
Step 5 — Wire Input Handling
[Event Graph — Input]
IA_Move (Axis2D) → CharacterMovementComponent.AddMovementInput
IA_Look (Axis2D) → AddControllerYawInput + AddControllerPitchInput
│
├─ [State Gating Check]
│ └─ BPC_StateManager.IsActionPermitted(Move) ?
│ ├─ True → process input
│ └─ False → ignore input (blocked by state: cutscene, death, etc.)
│
IA_Interact (Pressed) → BPC_InteractionDetector.Interact()
└─ Also check: BPC_StateManager.IsActionPermitted(Interact)
IA_Sprint (Pressed) → BPC_StaminaSystem.StartSprint()
└─ BPC_StateManager.IsActionPermitted(Sprint)? AND Stamina > 0?
IA_Sprint (Released) → BPC_StaminaSystem.StopSprint()
IA_Crouch (Pressed) → BPC_MovementStateSystem.ToggleCrouch()
└─ BPC_StateManager.IsActionPermitted(Crouch)?
IA_Fire (Pressed) → BPC_ActiveItemSystem.UseEquippedItem()
├─ Routes to BP_Pistol_Held.UseItem() or BP_Shotgun_Held.UseItem()
└─ BPC_StateManager.IsActionPermitted(Fire)?
IA_Fire (Released) → (cease fire for auto weapons)
IA_Aim (Pressed) → BPC_CameraStateLayer.SetLayer(Aiming)
└─ FOV zooms to 55, slight camera offset forward
IA_Aim (Released) → BPC_CameraStateLayer.RemoveLayer(Aiming)
└─ FOV returns to 90
IA_Reload (Pressed) → BPC_ActiveItemSystem.ReloadEquippedItem()
└─ Routes to BP_Pistol_Held.Reload() or BP_Shotgun_Held.Reload()
IA_UseItem (Pressed) → BPC_ConsumableSystem.UseQuickSlotItem()
└─ Uses whatever is in the quick-use slot (medkit, syringe, etc.)
IA_Flashlight (Pressed) → Toggle flashlight (if equipped in Tool slot)
└─ Calls I_Toggleable.Toggle on BP_Flashlight_Held
IA_Jump (Pressed) → Character.Jump()
└─ Also: BPC_ContextualTraversalSystem.TryVaultOrMantle()
IA_OpenWatch (Pressed) → SS_EnhancedInputManager.PushContext(WristwatchUI)
├─ SS_UIManager.ShowMenu(InventoryMenu)
└─ Camera pans down to wristwatch
IA_PauseMenu (Pressed) → PC_HorrorController.OpenPauseMenu()
IA_QuickHeal (Pressed) → BPC_ConsumableSystem.QuickUse(MedKit)
└─ Uses first available medkit in inventory
IA_QuickSlot1-8 → BPC_ActiveItemSystem.SetQuickSlot(SlotIndex)
Step 6 — Wire Component Communication
Each BPC_ component should communicate via Event Dispatchers, not direct references to other components. This keeps components decoupled.
Core Dispatcher Bindings (in Event BeginPlay):
[Damage Pipeline]
BPC_DamageReceptionSystem.OnDamageReceived
├─ → BPC_HealthSystem.ApplyDamage(Damage)
├─ → BPC_HitReactionSystem.PlayHitReaction(DamageInfo)
├─ → BPC_CombatFeedbackComponent.ShowHitMarker(DamageInfo)
├─ → WBP_ScreenEffectController.ShowDamageVignette()
└─ → BPC_PlayerMetricsTracker.RecordDamageTaken(Damage)
[Health to State]
BPC_HealthSystem.OnHealthChanged(NewHealth, MaxHealth)
├─ → BPC_StateManager.UpdateVitalSignals()
├─ → WBP_DiegeticHUDFrame.UpdateHealthBar()
└─ → SS_AudioManager.SetFloatParameter("HealthPercent", ratio)
[Health to Death]
BPC_HealthSystem.OnDeath(DeathCause, Killer)
├─ → BPC_DeathHandlingSystem.HandleDeath()
└─ → BPC_StateManager.ForcePushState(Death)
[Stress System]
BPC_StressSystem.OnStressTierChanged(OldTier, NewTier)
├─ → PS_HorrorPlayerState.SetSanityTier(NewTier * 25) // convert tier to 0-100
├─ → BPC_CameraStateLayer.SetStressBlur(NewTier)
│ ├─ Calm → no blur
│ ├─ Uneasy → slight peripheral blur
│ ├─ Disturbed → moderate blur + vignette
│ ├─ Breaking → heavy blur + tunnel vision
│ └─ Catatonic → near-black screen
├─ → BPC_MemoryDriftSystem.SetIntensity(NewTier)
│ └─ Triggers visual/audio hallucinations
├─ → SS_AudioManager.SetFloatParameter("StressTier", NewTier)
└─ → BPC_StateManager.UpdateVitalSignals()
[Stamina System]
BPC_StaminaSystem.OnStaminaChanged(Current, Max)
├─ → WBP_DiegeticHUDFrame.UpdateStaminaBar()
└─ → (If depleted) BPC_StateManager.ForcePushState(Exhausted)
└─ Blocks Sprint action for 3 seconds
[Hiding System]
BPC_HidingSystem.OnEnterHiding(HidingSpot)
├─ → BPC_StateManager.SetOverlayState(Hiding)
├─ → SS_EnhancedInputManager.PushContext(Hiding)
├─ → BPC_CameraStateLayer.SetLayer(HidePeek)
├─ → BPC_StressSystem.StartStressDecay() // stress decays while hidden
└─ → WBP_DiegeticHUDFrame.ShowBreathHoldMeter()
BPC_HidingSystem.OnExitHiding()
├─ → BPC_StateManager.ClearOverlayState(Hiding)
├─ → SS_EnhancedInputManager.PopContext(Hiding)
├─ → BPC_CameraStateLayer.RemoveLayer(HidePeek)
└─ → WBP_DiegeticHUDFrame.HideBreathHoldMeter()
[Inventory Events]
BPC_InventorySystem.OnItemAdded(ItemData, SlotIndex)
├─ → WBP_InventoryMenu.UpdateSlot(SlotIndex)
├─ → BPC_CollectibleTracker.CheckItem(ItemData) → if collectible, track it
├─ → WBP_NotificationToast.Show("Picked up: " + DisplayName)
└─ → (If weapon) BPC_EquipmentSlotSystem.AutoEquipIfSlotEmpty(ItemData)
BPC_InventorySystem.OnItemRemoved(ItemData, SlotIndex)
├─ → WBP_InventoryMenu.ClearSlot(SlotIndex)
└─ → (If equipped) BPC_EquipmentSlotSystem.UnequipSlot()
[Equipment Events]
BPC_EquipmentSlotSystem.OnItemEquipped(SlotTag, ItemData)
├─ → BPC_StateManager.SetEquipmentState(SlotTag, ItemData)
├─ → Spawn BP_*_Held actor (pistol, shotgun, flashlight)
│ └─ Attach to hand socket on SkeletalMeshComponent
└─ → BPC_ActiveItemSystem.SetCurrentItem(SlotTag, HeldActor)
[State Manager Events]
BPC_StateManager.OnStateChanged(OldState, NewState)
├─ → BPC_MovementStateSystem.SetStateRestrictions(NewState)
│ └─ Death → no movement; Cutscene → no movement; etc.
├─ → WBP_HUDController.UpdateHUDActive(newState)
│ └─ Hide HUD during cutscene/death; show during gameplay
└─ → SS_EnhancedInputManager.SetContextVisibility(NewState)
└─ Disable gameplay contexts during UI/Cutscene states
BPC_StateManager.OnActionDenied(ActionTag, BlockReason)
└─ → WBP_InteractionPromptDisplay.ShowBlocked(ActionTag, BlockReason)
└─ "Cannot fire — you are hiding"
└─ "Cannot interact — cutscene playing"
Health, Stamina, Stress — The Core Survival Triad
These three systems work together to create the horror survival experience:
┌──────────────────────┐
│ HEALTH (100 max) │
│ ├─ Damage reduces │
│ ├─ Medkit restores │
│ └─ 0 HP = DEATH │
└──────────┬───────────┘
│ low health
▼
┌──────────────────────┐ ┌──────────────────────┐
│ STAMINA (100 max) │ │ STRESS (0-100) │
│ ├─ Sprint: -10/sec │ │ ├─ Darkness: +2/sec │
│ ├─ Vault: -15/use │ │ ├─ Enemy near: +5/s │
│ ├─ Combat: -5/action │ │ ├─ Void: +8/sec │
│ └─ Regen: +5/sec │ │ └─ Decay: -1/sec │
│ (only when still)│ │ (safe areas) │
└──────────┬───────────┘ └──────────┬───────────┘
│ depleted │ high stress (75+)
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ EXHAUSTED │ │ HALLUCINATIONS │
│ ├─ Can't run│ │ ├─ False enemies │
│ ├─ Walk slow│ │ ├─ Sounds/lights │
│ └─ 3s cooldown│ │ └─ Screen distort│
└──────────────┘ └──────────────────┘
Key Variables (Class Defaults)
| Variable | Type | Default | Purpose |
|---|---|---|---|
MaxHealth |
Float | 100.0 |
Set via BPC_HealthSystem in BeginPlay |
MaxStamina |
Float | 100.0 |
Set via BPC_StaminaSystem |
MaxStress |
Float | 100.0 |
Set via BPC_StressSystem |
WalkSpeed |
Float | 300.0 |
GASP default |
SprintSpeed |
Float | 600.0 |
GASP default |
CrouchSpeed |
Float | 150.0 |
GASP default |
InteractionRange |
Float | 300.0 |
How far the player can interact |
bCanSprint |
Boolean | true |
Disabled when exhausted or state-blocked |
bIsFirstPerson |
Boolean | true |
Horror game — no third-person toggle |
DefaultFOV |
Float | 90.0 |
Normal gameplay FOV |
AimFOV |
Float | 55.0 |
When aiming down sights |
HidePeekFOV |
Float | 70.0 |
When peeking from hiding |
Blueprint Wiring Checklist
Components
- Add BPC_HealthSystem (08)
- Add BPC_StaminaSystem (09)
- Add BPC_StressSystem (10)
- Add BPC_MovementStateSystem (11)
- Add BPC_HidingSystem (12)
- Add BPC_EmbodimentSystem (13)
- Add BPC_CameraStateLayer (14)
- Add BPC_PlayerMetricsTracker (15)
- Add BPC_InteractionDetector (16)
- Add BPC_InventorySystem (31)
- Add BPC_EquipmentSlotSystem (30)
- Add BPC_ActiveItemSystem (26)
- Add BPC_ConsumableSystem (28)
- Add BPC_KeyItemSystem (34)
- Add BPC_JournalSystem (33)
- Add BPC_CollectibleTracker (27)
- Add BPC_DocumentArchiveSystem (29)
- Add BPC_ItemCombineSystem (32)
- Add BPC_AmmoComponent (70)
- Add BPC_RecoilSystem (77)
- Add BPC_HitReactionSystem (75)
- Add BPC_DamageReceptionSystem (72)
- Add BPC_CombatFeedbackComponent (71)
- Add BPC_StateManager (130)
- Add BPC_NarrativeStateSystem (58)
- Add BPC_ObjectiveSystem (59)
- Add BPC_EndingAccumulator (68)
Event BeginPlay
- Call parent BeginPlay
- Validate all critical components → log errors if missing
- Initialize Health, Stamina, Stress to max values
- Initialize Inventory (6×4 grid, 50 weight)
- Initialize Equipment Slots (PrimaryWeapon, Tool, Armor)
- Initialize State Manager with DA_StateGatingTable
- Bind all inter-component dispatchers (Health→Death, Stress→Blur, etc.)
Input
- Bind IA_Move, IA_Look (always active unless state-blocked)
- Bind IA_Interact (state-gated)
- Bind IA_Sprint (stamina-gated + state-gated)
- Bind IA_Crouch (state-gated)
- Bind IA_Fire (state-gated + equipment-gated)
- Bind IA_Aim (equipment-gated)
- Bind IA_Reload (state-gated)
- Bind IA_UseItem (state-gated)
- Bind IA_Flashlight (state-gated)
- Bind IA_Jump (state-gated)
- Bind IA_OpenWatch (state-gated → pushes wristwatch context)
- Bind IA_PauseMenu
- Bind IA_QuickHeal
- Bind IA_QuickSlot1-8
- Every input check: BPC_StateManager.IsActionPermitted before processing
Notes for Expansion
- GASP customization: Never edit GASP AnimBP directly. Create a child AnimBP and override/insert notifies. Add your own locomotion states (limping, crawling) as new GASP states.
- Component order: Add components in dependency order if they have BeginPlay initialization dependencies. Health before DeathHandling; Inventory before Equipment.
- Multiplayer: All health/stamina/stress mutations must be server-authoritative. Use
HasAuthority()gates. Replicate withRepNotify. - Performance: If FPS drops, consider lazy-initializing non-critical components (CollectibleTracker, DocumentArchive, PlayerMetricsTracker).
- Mod support: Keep all default values in a
DA_PlayerConfigData Asset (health, stamina, speed, FOV) so modders can override without touching BP.
BP_HorrorPlayerCharacter — The player pawn for Project Void. See GAMEINDEX.md for full game structure. See INDEX.md for all framework component specs.