Multiplayer

Multiplayer Lobby and Proximity Chat

Friends only Steam lobbies with invite overlay, LAN fallback, push to talk, and positional voice that follows the body.

  • C++
  • Multiplayer
  • Steam Online Services
  • Voice Chat
  • CommonUI
  • Blueprints
Settings row with the setting name, a value slider, and a green mic level bar, shown in the Widget Designer.

Feature context

Project context
Obversus: Umbra
Status
In development
Engine
Unreal Engine 5.8

Responsibilities

  • Designed and implemented the online layer in C++: lobby host, find and join, invites, and leave cleanup.
  • Built proximity voice: a talker on the player state with data driven attenuation that survives respawn.
  • Built the lobby menu with friend avatars and the player list.

Overview

Host rehosts the current map as a friends only listen session, friends join from the menu or a Steam invite, and leaving cleans up so hosting works again after the host drops. Voice is push to talk with a mic selector, input gain, and a noise gate, and the settings menu shows a live mic level meter. It plays from the speaker’s body with distance falloff and wall muffling. The player list polls game state on a timer and diffs joins and leaves, no tick.

Architecture

Sessions use presence plus lobbies with friends only visibility, because Steam rejects mismatched flags. Find and join goes through friend session lookup, LAN uses lobbyless search, invites use the Steam overlay. Voice transport is the platform voice implementation; proximity is client side spatialization only, no voice replication code.

The talker lives on the player state so it survives respawn, and repoints at the new pawn root on possession:

void UProximityVoiceTalker::BeginPlay()
{
    Super::BeginPlay();
    APlayerState* PlayerState = GetOwner<APlayerState>();
    if (!PlayerState) { return; }
    Settings.AttenuationSettings = GetDefault<UOnlineSettings>()->VoiceAttenuation.LoadSynchronous();
    RegisterWithPlayerState(PlayerState);
    PlayerState->OnPawnSet.AddDynamic(this, &ThisClass::HandlePawnSet);
    HandlePawnSet(PlayerState, PlayerState->GetPawn(), nullptr);
}
void UProximityVoiceTalker::HandlePawnSet(APlayerState* Player, APawn* NewPawn, APawn* OldPawn)
{
    // The voice engine reads this when the next sentence starts.
    Settings.ComponentToAttachTo = NewPawn ? NewPawn->GetRootComponent() : nullptr;
}

Attenuation is a data asset, so falloff and occlusion tune without code. Push to talk is a local only ability that always closes the mic on end, and the mic device reapplies on session join.

The mic gate uses one threshold for two jobs. Below it the mic sends silence, and the same level sets silence detection, so the engine stops sending packets while the player is quiet. Gain and gate write the voice console variables through the settings object. The settings menu opens its own capture and scales the live level bar against the gate slider’s range, so the player sets the gate by watching their own voice.

Settings row with the setting name on the left and a value slider on the right, with a green mic level bar under the slider, shown in the Widget Designer.