Add developer documentation for systems 11-16, including architecture overview and implementation patterns
- Created detailed documentation for systems 102-135 covering achievements, settings, polish, data assets, input management, and state management. - Added INDEX.md for easy navigation of developer reference materials. - Introduced architecture-overview.md to provide a high-level understanding of the framework's structure and communication methods. - Compiled implementation-patterns.md to outline common UE5 Blueprint patterns for consistent development practices.
This commit is contained in:
295
docs/developer/implementation-patterns.md
Normal file
295
docs/developer/implementation-patterns.md
Normal file
@@ -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.*
|
||||
Reference in New Issue
Block a user