Appearance
Networking Model
A comprehensive guide to Motion's multiplayer networking architecture, covering replication, prediction, and state synchronization for networked character movement.
Prerequisites
This guide assumes basic familiarity with Unreal Engine's gameplay framework. If you're new to GAS, read the GAS Primer first.
Multiplayer Quick Start
Get Motion working in multiplayer with these essential steps:
Minimum Requirements
PlayerState with GAS - Your PlayerState must:
- Implement
IAbilitySystemInterface - Own an
AbilitySystemComponent - Own an
AttributeSet(or Motion discovers attributes by name)
- Implement
GameMode Configuration - Set your PlayerState class in GameMode
Motion Components - Add to your character as normal (they're network-aware by default)
Test Multiplayer
In the Editor:
- Set
Number of Playersto 2 or more - Set
Net ModetoPlay as Client - Press Play
bash
# Console commands for debugging
showdebug abilitysystem # View GAS state
log LogMotionCore Verbose # Motion debug loggingCommon Multiplayer Issues
| Issue | Solution |
|---|---|
| Movement not syncing | Ensure PlayerState implements IAbilitySystemInterface |
| Tags not replicating | Use GameplayEffects instead of loose tags |
| Sprint/Crouch flickering | Check server authority in component code |
| Camera effects on all clients | Effects should only apply to locally controlled |
Key Networking Rules
- Server applies effects - Only apply GameplayEffects on
HasAuthority() - Client predicts locally - Use loose tags for immediate feedback
- GAS replicates automatically - Tags and attributes sync via GAS
For detailed explanations, continue reading below.
Introduction
Motion supports multiplayer from the ground up. It leverages Unreal Engine's networking system and GAS for state management. Every movement component is network-aware for smooth character control in any environment.
Key Networking Features
- Server-Authoritative Movement: All movement decisions are validated by the server
- Client Prediction: Responsive controls with predictive movement
- GAS Integration: Leverages GAS's built-in replication for attributes and effects
- Minimal Network Overhead: Only essential data is replicated
- Universal Compatibility: Works with any networked ACharacter
Core Networking Concepts
Server-Client Architecture
Unreal Engine uses a server-client model where the server has ultimate authority over the game state:
Key Principles:
- Server validates all gameplay decisions
- Clients predict locally for responsiveness
- Server corrects client predictions when needed
- "Never trust the client" - validate everything server-side
Network Roles
Every actor in a networked game has a role that determines its behavior:
| Role | Description | Motion Example |
|---|---|---|
| Authority | Server version of the actor | Server's character instance |
| Autonomous Proxy | Locally controlled client actor | Your character on your client |
| Simulated Proxy | Remote client actor | Other players on your client |
| None | Not networked | UI elements, local effects |
Connection Flow
Understanding how players connect and initialize is crucial for Motion's networking:
Important
Returning false in validation functions disconnects the client. In PIE this looks like a respawn. In Release builds it kicks them out.
Motion's Network Architecture
Component-Based Design
Motion uses a modular component architecture that integrates seamlessly with Unreal's networking:
PlayerState Ownership Pattern
Motion uses MotionPlayerState to own the AbilitySystemComponent, ensuring persistence across respawns:
cpp
// MotionPlayerState.cpp
AMotionPlayerState::AMotionPlayerState(const FObjectInitializer& ObjectInitializer)
{
// Enable replication
bReplicates = true;
// Create and configure ASC
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
AbilitySystemComponent->SetIsReplicated(true);
// Mixed mode: Server authority with client prediction
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
}Why PlayerState?
- Persists across character respawns
- Maintains attributes between deaths
- Simplifies ownership for networking
- Standard pattern for GAS games
GAS Replication Modes
Motion uses Mixed Replication Mode for optimal performance:
| Mode | GameplayEffects | GameplayCues | Use Case |
|---|---|---|---|
| Full | Replicate to all | Replicate to all | Single player |
| Mixed | Replicate to owner only | Replicate to all | What we use ✓ |
| Minimal | Don't replicate | Replicate to all | AI/NPCs |
Replication in Motion
What Gets Replicated
Motion carefully manages what data is replicated to minimize bandwidth:
Attribute Replication
Motion's attributes replicate automatically through GAS:
cpp
// MotionAttributeSet.cpp
void UMotionAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Replicate movement attributes
DOREPLIFETIME_CONDITION_NOTIFY(UMotionAttributeSet, WalkSpeed, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UMotionAttributeSet, SprintSpeed, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UMotionAttributeSet, JumpVelocity, COND_None, REPNOTIFY_Always);
// Replicate stamina attributes
DOREPLIFETIME_CONDITION_NOTIFY(UMotionAttributeSet, Stamina, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UMotionAttributeSet, MaxStamina, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UMotionAttributeSet, StaminaRegenRate, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UMotionAttributeSet, StaminaDrainRate, COND_None, REPNOTIFY_Always);
}Tag-Based State Synchronization
Motion uses GameplayTags for state synchronization across the network:
Client-Server Communication
Input Flow Diagram
Motion implements client prediction for responsive movement:
Benefits:
- Zero perceived latency for local player
- Smooth movement even with high ping
- Server can still correct if needed
Movement State Networking
Crouch Network Handling
Crouch uses a dual-layer system combining client prediction with server authority:
cpp
void UMotionCrouchingComponent::HandleCrouchInputStateChange(bool bPressed)
{
if (!CachedCharacter->IsLocallyControlled())
return;
if (bPressed)
{
if (CachedCharacter->HasAuthority())
{
// Server: Direct authoritative state change
SetWantsToCrouch(true);
}
else
{
// Client: Apply loose tag for immediate feedback
UMotionAbilitySystemHelper::AddGameplayTagToActor(
CachedCharacter, MotionGameplayTags::Motion_State_WantsToCrouch);
// Send RPC to server
ServerRequestCrouch(true);
}
}
}
void UMotionCrouchingComponent::UpdateCrouchingState(float DeltaTime)
{
// Local client triggers built-in crouch system
if (CachedCharacter->IsLocallyControlled())
{
if (bWantsToCrouch && !bIsCurrentlyCrouching)
{
// Character->Crouch() triggers automatic server RPC
CachedCharacter->Crouch();
}
else if (!bWantsToCrouch && bIsCurrentlyCrouching)
{
// Character->UnCrouch() triggers automatic server RPC
CachedCharacter->UnCrouch();
}
}
}Key Features:
- Client prediction via loose tags for zero-latency feedback
- Server validation with rate limiting and collision checks
- Built-in CharacterMovementComponent handles capsule changes
- Camera height interpolation managed separately by MotionCameraComponent
Jump Prediction
Jump combines client prediction with custom enhancements for advanced features:
cpp
void UMotionJumpComponent::HandleJumpInputStateChange(bool bPressed)
{
if (!CachedCharacter->IsLocallyControlled())
return;
if (bPressed)
{
if (CachedCharacter->HasAuthority())
{
// Server: Direct authoritative state change
SetWantsToJump(true);
}
else
{
// Client: Apply loose tag for immediate feedback
UMotionAbilitySystemHelper::AddGameplayTagToActor(
CachedCharacter, MotionGameplayTags::Motion_State_WantsToJump);
// Send RPC to server
ServerRequestJump(true);
}
}
}
bool UMotionJumpComponent::PerformJump(bool bIsAdditionalJump)
{
// Custom jump counter avoids Unreal's synchronization bugs
if (IsInCoyoteTime() && !bUsedCoyoteJump)
{
bUsedCoyoteJump = true;
MotionJumpCount = 1; // First jump from coyote time
}
else if (!IsInCoyoteTime())
{
MotionJumpCount++; // Air jump increment
}
// Sync Unreal's counter before jumping
CachedCharacter->JumpCurrentCount = MotionJumpCount - 1;
// Apply custom velocity modifications
ApplyDirectJumpVelocity(bIsAdditionalJump);
if (bIsAdditionalJump && !IsInCoyoteTime())
{
// Directional air jumps with custom velocity
PerformDirectionalJump();
}
else
{
// Standard ground/coyote jump - uses built-in prediction
CachedCharacter->Jump();
}
return true;
}Advanced Features:
- Coyote Time: Grace period for jumping after leaving ground
- Jump Buffering: Queue jump input before landing
- Multi-Jump: Air jumps with velocity multipliers
- Custom Counter: Reliable jump tracking avoiding Unreal's bugs
- Directional Jumps: Air jumps respect movement input
Stamina Management
Stamina synchronization for sprint limitations:
Testing Multiplayer
Local Testing Setup
Motion supports easy local multiplayer testing:
- PIE Multiplayer Settings:
- Test with multiple players: `Play` -> `Number of Players`: 2
- Net Mode: Play As Client
- Use Single Process: Unchecked (for true networking)- Window Arrangement:
Advanced Settings -> Multiplayer Options
- Editor Multiplayer Mode: Play multiple clients
- Create Audio Device for Every Player: Disabled (Default)Network Simulation
Test with simulated network conditions:
bash
# Console commands for network simulation
NetEmulation.PktLag=100 # 100ms latency
NetEmulation.PktLoss=2 # 2% packet loss
NetEmulation.PktDup=1 # 1% duplicate packetsAlternatively, enable Network Emulation in Editor Preferences:

Debugging Network Issues
Debug Commands
Essential console commands for network debugging:
bash
# Show network debug info
showdebug net
showdebug abilitysystem
stat net
stat game
# Network details
p.NetShowCorrections 1 # Show prediction corrections
net.EnableNetStats 1 # Show connection status
# GAS debugging
showdebug abilitysystem # Show ability system state
log LogMotionCore Verbose # Motion-specific logging
log LogAbilitySystem Verbose # GAS logging
# Performance monitoring
stat fps # Frame rate
stat unit # Frame time breakdown
stat rhi # Rendering statsCommon Network Issues
| Issue | Symptoms | Solution |
|---|---|---|
| Rubber-banding | Character snaps back | Check server validation logic, increase tolerance |
| Stamina Desync | Different stamina values | Ensure GAS effects replicate properly |
| Missing Movement | Others don't see movement | Check actor replication settings |
| Tag Mismatch | States out of sync | Verify tag replication, check authority |
Network Log Analysis
Key log patterns to watch for:
cpp
// Motion network logging
UE_LOG(LogMotionCore, Verbose, TEXT("Sprint RPC: Client->Server"));
UE_LOG(LogMotionCore, Warning, TEXT("Sprint validation failed"));
// GAS replication logging
UE_LOG(LogAbilitySystem, Verbose, TEXT("Effect applied: %s"), *EffectName);
UE_LOG(LogAbilitySystem, Warning, TEXT("Prediction miss: %s"), *AttributeName);
// Movement logging
UE_LOG(LogCharacterMovement, Verbose, TEXT("Correction delta: %f"), Delta);Best Practices
Do's
- Always validate on server:
cpp
if (HasAuthority())
{
// Make authoritative decisions here
}- Use GAS for state management:
cpp
// Let GAS handle replication
ApplyGameplayEffectToSelf(SprintEffect);- Implement client prediction:
cpp
// Predict locally, validate on server
PredictMovement();
ServerValidateMovement();- Use Motion's helpers:
cpp
UMotionAbilitySystemHelper::GetAbilitySystemComponentFromActor(Actor);Don'ts
- Don't trust client input:
cpp
// Bad: Direct application
void OnSprintInput()
{
StartSprinting(); // No validation!
}
// Good: Server validation
void OnSprintInput()
{
ServerRequestSprint(true);
}- Don't replicate everything:
cpp
// Bad: Replicate visual-only properties
UPROPERTY(Replicated)
float CameraShakeIntensity;
// Good: Keep visual properties local
UPROPERTY()
float CameraShakeIntensity;- Don't ignore network roles:
cpp
// Bad: Assume authority
ApplyDamage(Target, Amount);
// Good: Check authority
if (HasAuthority())
{
ApplyDamage(Target, Amount);
}- Don't use reliable RPCs on Tick:
cpp
// Bad: Flooding network
void Tick(float DeltaTime)
{
ServerUpdatePosition(GetActorLocation()); // Reliable RPC
}
// Good: Use replication
UPROPERTY(ReplicatedUsing=OnRep_Position)
FVector ReplicatedPosition;Next Steps
- Review the GAS Primer for attribute and effect replication details
- Test your implementation with the Quick Start Guide
- Explore individual components for specific networking features
- Join the Discord community for multiplayer testing partners
Additional Resources
Unreal Documentation
Community Resources
Pro Tip
Start with local multiplayer testing using PIE before moving to dedicated servers. This allows rapid iteration and easier debugging of networking issues.