- Updated references from GI_GameTagRegistry to DA_GameTagRegistry in architecture overview and implementation patterns documentation. - Added new Blueprint specification for GI_StarterGameInstance, detailing its purpose, configuration, and integration pattern. - Introduced DA_GameTagRegistry Blueprint specification, centralizing GameplayTag management and providing functions for tag validation and logging. - Created documentation for the Starter GameInstance, outlining its role in the project setup and how other systems can integrate with it.
388 lines
12 KiB
Markdown
388 lines
12 KiB
Markdown
# 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 `DA_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 |
|
|
|
|
---
|
|
|
|
## Multiplayer Networking Patterns
|
|
|
|
### Pattern 11: Server Authority Gate
|
|
|
|
**When to Use:** Every function that modifies replicated state.
|
|
|
|
**Blueprint Implementation:**
|
|
```
|
|
[Function: ApplyDamage(DamageEvent)]
|
|
Switch HasAuthority
|
|
Authority:
|
|
→ Execute authoritative logic (damage calc, health modification)
|
|
→ Fire dispatchers
|
|
Remote:
|
|
→ Return (client cannot modify replicated state directly)
|
|
→ Client calls Server_ RPC to request the change
|
|
```
|
|
|
|
**Key Rule:** Never modify a replicated variable without `HasAuthority()` check.
|
|
|
|
### Pattern 12: Server RPC Wrapper
|
|
|
|
**When to Use:** Client needs to request a state change from the server.
|
|
|
|
**Blueprint Implementation:**
|
|
```
|
|
[Client Event Graph — Input: Interact pressed]
|
|
→ Call Server_Interact(TargetActor) // RPC: Run on Server, Reliable
|
|
|
|
[Server RPC: Server_Interact(TargetActor)]
|
|
Switch HasAuthority
|
|
Authority:
|
|
→ Validate distance, conditions
|
|
→ Call I_Interactable.Execute_OnInteract(TargetActor, Instigator)
|
|
→ State changes replicate automatically
|
|
```
|
|
|
|
**Naming:** All Server RPCs prefixed with `Server_`.
|
|
|
|
### Pattern 13: OnRep Dispatcher Relay
|
|
|
|
**When to Use:** Any replicated variable that should trigger the same effects as single-player.
|
|
|
|
**Blueprint Implementation:**
|
|
```
|
|
[Variable: CurrentHealth]
|
|
Replication: Replicated Using OnRep_CurrentHealth
|
|
|
|
[Function: OnRep_CurrentHealth]
|
|
→ Broadcast OnHealthChanged(OldHealth, CurrentHealth, Delta)
|
|
→ UI, audio, effects react exactly as in single-player path
|
|
→ No multiplayer-specific code in consumer systems
|
|
```
|
|
|
|
**Key Rule:** `OnRep_` fires the SAME dispatcher the SP mutation code fires. Zero consumer changes needed.
|
|
|
|
### Pattern 14: Client Prediction with Correction
|
|
|
|
**When to Use:** Actions that need instant feedback (firing, using items, interacting).
|
|
|
|
**Blueprint Implementation:**
|
|
```
|
|
[Client: Input → Fire pressed]
|
|
→ Client prediction: play fire animation, muzzle flash, reduce ammo display
|
|
→ Call Server_StartFire()
|
|
|
|
[Server: Server_StartFire()]
|
|
→ Validate weapon state, ammo, cooldown
|
|
→ If valid: execute authoritative fire logic, consume ammo
|
|
→ If invalid: log warning, return (client will be corrected via OnRep)
|
|
|
|
[Client: OnRep_Ammo]
|
|
→ If server count matches client prediction: no visible change
|
|
→ If different: correct ammo display, stop fire animation
|
|
```
|
|
|
|
### Pattern 15: Multicast for Cosmetic Events
|
|
|
|
**When to Use:** Server needs to trigger a cosmetic-only event on all clients.
|
|
|
|
**Blueprint Implementation:**
|
|
```
|
|
[Server: Explosion triggered]
|
|
→ Multicast_PlayExplosionFX(Location, Radius)
|
|
→ All clients play VFX, SFX, camera shake locally
|
|
→ No replication of individual particles
|
|
```
|
|
|
|
**Key Rule:** Multicast is for **cosmetic only** — never use for state changes. State changes replicate via variable RepNotify.
|
|
|
|
---
|
|
|
|
*Implementation Patterns v1.0 — Reference when building blueprint specs into UE5 Blueprint assets.*
|