Add Common UE5 Blueprint Implementation Patterns

2026-05-19 11:13:11 +00:00
parent c82cac4e37
commit d7dadd5dea

@@ -0,0 +1,295 @@
# Common UE5 Blueprint Implementation Patterns
**Purpose:** This document catalogs the recurring Blueprint implementation patterns used throughout the framework. When implementing a blueprint spec into an actual UE5 Blueprint asset, reference these patterns for consistent, correct implementation.
---
## Pattern 1: Interface-First Communication
**When to Use:** Any time System A needs to call a function on System B without knowing System B's concrete class.
**Blueprint Implementation:**
```
[System A Blueprint]
TargetActor = GetOwner() // or any actor reference
bImplements = DoesImplementInterface(TargetActor, I_Interactable)
Branch(bImplements)
True → Call I_Interactable::ExecuteInteraction(TargetActor, Self)
False → Return / Log Warning
```
**Framework Examples:** `BPC_InteractionDetector``I_Interactable`, `SS_SaveManager``I_Persistable`, Weapons → `I_Damageable`
**Key Rules:**
- Always check `DoesImplementInterface` before calling
- Never cast to concrete class — use the interface
- Default return values (false/0.0f/empty) handle missing implementations gracefully
---
## Pattern 2: Event Dispatcher Binding (UI Display)
**When to Use:** Any system that needs to react to state changes in another system.
**Blueprint Implementation:**
```
[HUD Widget Blueprint — Event Construct]
PlayerPawn = GetOwningPlayerPawn()
HealthComponent = PlayerPawn.FindComponentByClass(BPC_HealthSystem)
Bind Event to HealthComponent.OnHealthChanged
→ Custom Event: OnHealthChangedHandler(OldHealth, NewHealth, Delta)
→ ProgressBar.SetPercent(NewHealth / MaxHealth)
[HUD Widget Blueprint — Event Destruct]
Unbind Event from HealthComponent.OnHealthChanged
```
**Framework Examples:** `WBP_HUDController` binds to `OnHealthChanged`, `GS_CoreGameState` binds to `OnGamePhaseChanged`
**Key Rules:**
- Always unbind in Event Destruct to prevent dangling references
- Never use Event Tick for polling — always bind to dispatchers
- Normalize values (0.0-1.0) before sending to UI for smooth interpolation
---
## Pattern 3: Subsystem Lookup (Global Service Resolution)
**When to Use:** Any system that needs a global service (save, audio, UI, input).
**Blueprint Implementation:**
```
[Any Blueprint]
GameInstance = GetGameInstance()
Cast to GI_GameFramework → Framework
SubsystemRef = Framework.GetSubsystem(Tag) // or FL_GameUtilities.GetSubsystemSafe()
IsValid(SubsystemRef)?
True → Call Subsystem function
False → Log warning, return gracefully
```
**Framework Examples:** Any system calling `SS_SaveManager.Save()`, `SS_AudioManager.PlaySound()`
**Key Rules:**
- Always use `FL_GameUtilities.GetSubsystemSafe()` — returns null instead of crashing
- Check validity of the returned subsystem before calling functions
- Subsystems are singletons — no need to cache, just look up when needed
---
## Pattern 4: Data Asset Configuration
**When to Use:** Any system that needs configurable data (item stats, weapon balance, encounter rules).
**Blueprint Implementation:**
```
[Component Configuration]
Variable: ItemData
Type: DA_ItemData (Object Reference → Primary Data Asset)
Instance Editable: ✓
Expose on Spawn: ✓
Category: "Config"
[Runtime Usage]
ItemData.ItemTag → GameplayTag
ItemData.DisplayName → Text for UI
ItemData.Weight → Float for carry capacity
ItemData.StackLimit → Integer for stack management
```
**Framework Examples:** All systems that reference `DA_*` Data Assets
**Key Rules:**
- Data Assets are read-only at runtime — never modify them
- Use Soft Object References for async loading large Data Asset collections
- Register Data Assets with Primary Asset Manager for streaming support
---
## Pattern 5: Timer-Based Ticks (Not Event Tick)
**When to Use:** Any repeating operation that doesn't need per-frame precision (regen, decay, scan).
**Blueprint Implementation:**
```
[BeginPlay]
Set Timer by Event
Event: RegenTick
Time: 0.1 (seconds)
Looping: ✓
[Custom Event: RegenTick]
CurrentStamina = Min(MaxStamina, CurrentStamina + RegenRate * 0.1)
Fire OnStaminaChanged
If CurrentStamina >= MaxStamina:
Clear Timer (RegenTimerHandle)
```
**Framework Examples:** `BPC_HealthSystem` regen, `BPC_StaminaSystem` drain, `BPC_StressSystem` decay, `BPC_InteractionDetector` scan
**Key Rules:**
- Use 0.1s intervals for smooth but performant updates
- Store timer handles and clear them on component destroy
- Never use Event Tick unless absolutely necessary (physics, camera)
---
## Pattern 6: State Machine with Enum Switch
**When to Use:** Any system that has distinct, mutually exclusive states.
**Blueprint Implementation:**
```
[Function: SetState(NewState)]
OldState = CurrentState
Switch on NewState:
Case StateA:
// StateA setup logic
Case StateB:
// StateB setup logic
Case StateC:
// StateC setup logic
CurrentState = NewState
Fire OnStateChanged(OldState, NewState)
```
**Framework Examples:** `BP_DoorActor` (Closed/Opening/Open/Closing/Locked/Barricaded), `BP_WeaponBase` (Holstered/Equipping/Ready/Firing/Reloading), `BPC_AIStateMachine` (Patrol/Search/Combat/Flee)
**Key Rules:**
- Store previous state for transition detection
- Fire dispatcher on every state change
- Block operations that don't make sense in current state
---
## Pattern 7: Validation Before Action
**When to Use:** Any function that modifies state and can fail.
**Blueprint Implementation:**
```
[Function: AddItem(ItemData, Quantity)]
// Validate
FreeSlot = FindFreeSlot()
If FreeSlot < 0:
Fire OnInventoryFull
Return E_InventoryOperationResult::InventoryFull
OverWeight = CheckWeight(ItemData.Weight * Quantity)
If OverWeight:
Return E_InventoryOperationResult::WeightCapacityExceeded
// Execute
SetSlot(FreeSlot, ItemData, Quantity)
RecalculateWeight()
Fire OnItemAdded
Return E_InventoryOperationResult::Success
```
**Framework Examples:** `BPC_InventorySystem.AddItem`, `BPC_StaminaSystem.DrainStamina`, `BPC_HidingSystem.EnterHideSpot`
**Key Rules:**
- Validate ALL conditions before making ANY changes
- Return descriptive result codes, not just true/false
- Fire error dispatchers with human-readable failure reasons
---
## Pattern 8: RepNotify for Networked State
**When to Use:** Any replicated variable that needs local effects when changed remotely.
**Blueprint Implementation:**
```
[Variable]
CurrentHealth: Float
Replication: RepNotify
OnRep Function: OnRep_CurrentHealth
[Function: OnRep_CurrentHealth]
Fire OnHealthChanged (with cached old value)
// This runs on clients when server changes the value
// Local effects (UI update, effects) fire here
```
**Framework Examples:** All replicated variables in `GS_CoreGameState`, `BPC_InventorySystem.Slots`
**Key Rules:**
- RepNotify fires on clients, NOT on server (server handles effects in the setter)
- Cache old value before replication to use in OnRep
- Single-player games can ignore replication entirely — framework is SP-first
---
## Pattern 9: Tag-Driven Filtering
**When to Use:** Any time you need to filter or categorize (items, interactions, states, narrative).
**Blueprint Implementation:**
```
[Function: HasTag(Actor, Tag)]
TagContainer = Actor.GetGameplayTagContainer() // or via interface
Return TagContainer.HasTag(Tag)
[Function: FindItemsByTag(Tag)]
FilteredArray = []
For Each Slot in Inventory.Slots:
If Slot.Entry.ItemData.ItemTag.MatchesTag(Tag):
Add to FilteredArray
Return FilteredArray
```
**Framework Examples:** Every system — tags replace booleans and strings everywhere
**Key Rules:**
- Framework tags use `Framework.` prefix; project tags use `Game.` prefix
- All tags documented in `GI_GameTagRegistry`
- Never use `FName` or `FString` for state — always GameplayTags
---
## Pattern 10: Linked Actor Notifications
**When to Use:** When one actor's state change should trigger other actors (door opens → lights turn on).
**Blueprint Implementation:**
```
[Variable: LinkedActors]
Type: Array of Actor
Instance Editable: ✓
Category: "Connections"
[On Door Opened]
For Each LinkedActor in LinkedActors:
bImplements = DoesImplementInterface(LinkedActor, I_Toggleable)
If bImplements:
Call I_Toggleable::SetState(LinkedActor, True)
```
**Framework Examples:** `BP_DoorActor` notifying lights/traps, `BP_PuzzleDeviceActor` unlocking connected doors
**Key Rules:**
- Use interfaces (I_Toggleable, I_Adjustable) for linked actor communication
- Designer sets LinkedActors in editor — no code changes needed
- Order of linked actor notification may matter — test edge cases
---
## Quick Reference: Which Pattern for What?
| Need | Pattern |
|------|---------|
| Call a function on unknown actor type | Pattern 1: Interface-First |
| React to another system's state change | Pattern 2: Dispatcher Binding |
| Access global services (save, audio, UI) | Pattern 3: Subsystem Lookup |
| Configure system behavior without code | Pattern 4: Data Asset |
| Repeating operation at interval | Pattern 5: Timer-Based Tick |
| Manage mutually exclusive states | Pattern 6: State Machine |
| Operation that can fail | Pattern 7: Validation Before Action |
| Network state synchronization | Pattern 8: RepNotify |
| Filter/categorize by type | Pattern 9: Tag-Driven Filtering |
| Chain reactions between actors | Pattern 10: Linked Actor Notifications |
---
*Implementation Patterns v1.0 — Reference when building blueprint specs into UE5 Blueprint assets.*