- Updated Master Blueprint Index to include Multiplayer Networking support. - Added detailed Multiplayer Networking sections across all developer documentation (01-11). - Introduced authority maps, key patterns, and RPC guidelines for each system. - Established a comprehensive multiplayer networking architecture document outlining core principles, replication strategies, and anti-cheat considerations. - Enhanced UI documentation to clarify local-only behavior and binding to replicated dispatchers. - Implemented client prediction strategies and RPC naming conventions for consistency across the framework.
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 `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 |
|
|
|
|
---
|
|
|
|
## 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.*
|