Skip to content

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

  1. PlayerState with GAS - Your PlayerState must:

    • Implement IAbilitySystemInterface
    • Own an AbilitySystemComponent
    • Own an AttributeSet (or Motion discovers attributes by name)
  2. GameMode Configuration - Set your PlayerState class in GameMode

  3. Motion Components - Add to your character as normal (they're network-aware by default)

Test Multiplayer

In the Editor:

  1. Set Number of Players to 2 or more
  2. Set Net Mode to Play as Client
  3. Press Play
bash
# Console commands for debugging
showdebug abilitysystem    # View GAS state
log LogMotionCore Verbose  # Motion debug logging

Common Multiplayer Issues

IssueSolution
Movement not syncingEnsure PlayerState implements IAbilitySystemInterface
Tags not replicatingUse GameplayEffects instead of loose tags
Sprint/Crouch flickeringCheck server authority in component code
Camera effects on all clientsEffects 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:

RoleDescriptionMotion Example
AuthorityServer version of the actorServer's character instance
Autonomous ProxyLocally controlled client actorYour character on your client
Simulated ProxyRemote client actorOther players on your client
NoneNot networkedUI 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:

ModeGameplayEffectsGameplayCuesUse Case
FullReplicate to allReplicate to allSingle player
MixedReplicate to owner onlyReplicate to allWhat we use ✓
MinimalDon't replicateReplicate to allAI/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:

  1. PIE Multiplayer Settings:
- Test with multiple players: `Play` -> `Number of Players`: 2
- Net Mode: Play As Client
- Use Single Process: Unchecked (for true networking)
  1. 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 packets

Alternatively, 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 stats

Common Network Issues

IssueSymptomsSolution
Rubber-bandingCharacter snaps backCheck server validation logic, increase tolerance
Stamina DesyncDifferent stamina valuesEnsure GAS effects replicate properly
Missing MovementOthers don't see movementCheck actor replication settings
Tag MismatchStates out of syncVerify 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

  1. Always validate on server:
cpp
if (HasAuthority())
{
    // Make authoritative decisions here
}
  1. Use GAS for state management:
cpp
// Let GAS handle replication
ApplyGameplayEffectToSelf(SprintEffect);
  1. Implement client prediction:
cpp
// Predict locally, validate on server
PredictMovement();
ServerValidateMovement();
  1. Use Motion's helpers:
cpp
UMotionAbilitySystemHelper::GetAbilitySystemComponentFromActor(Actor);

Don'ts

  1. Don't trust client input:
cpp
// Bad: Direct application
void OnSprintInput() 
{
    StartSprinting(); // No validation!
}

// Good: Server validation
void OnSprintInput()
{
    ServerRequestSprint(true);
}
  1. Don't replicate everything:
cpp
// Bad: Replicate visual-only properties
UPROPERTY(Replicated)
float CameraShakeIntensity;

// Good: Keep visual properties local
UPROPERTY()
float CameraShakeIntensity;
  1. Don't ignore network roles:
cpp
// Bad: Assume authority
ApplyDamage(Target, Amount);

// Good: Check authority
if (HasAuthority())
{
    ApplyDamage(Target, Amount);
}
  1. 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.

Motion - Advanced First Person Character Controller