Skip to content
UCIK Docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Add voice chat

Connect players to voice and give them control over their audio.

Select a voice-capable provider. For lobby voice, enable Enable Voice when creating the lobby, then join its voice channel after joining the lobby. Providers without lobby voice ignore that creation option.

The first available provider in Voice → Providers takes every channel. With Betide.Discord first, a lobby voice channel becomes the Discord call of the lobby’s Discord mirror, so the lobby must be mirrored to Discord (see lobbies); session and named channels are not available on Discord. Discord player volume maps 1.0 to Discord’s default of 100, master volumes stop at 1.0, and transmit targeting per channel is not supported. Audio device lists arrive asynchronously from Discord, so the first device query after sign-in can be empty.

Join lobby voice

Join lobby voice
Preparing your graph…

Loading the interactive viewer.

Game module dependencies: UCIKCore, BetideCore.

#include "Engine/GameInstance.h"
#include "Types/UCIKCrossplayTypes.h"
#include "UCIKCore.h"
#include "Voice/UCIKVoiceSubsystem.h"

void JoinPartyVoice(UGameInstance &GameInstance, const FUCIK_Lobby &Lobby)
{
    auto *Service = GameInstance.GetSubsystem<UUCIKVoiceSubsystem>();
    if (!Service)
        return;

    FUCIK_VoiceChannel Channel;
    Channel.Kind = EUCIK_VoiceChannelKind::Lobby;
    Channel.Lobby = Lobby.Primary;
    Service->JoinChannel(
        Channel,
        FBetideCallback<bool>::CreateWeakLambda(&GameInstance, [](const TBetideResult<bool> &Result) {
            if (!Result.bOk)
            {
                UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
                return;
            }
            UE_LOG(LogUCIK, Log, TEXT("Voice joined: %d"), Result.GetValue());
        }));
}

Microphone and devices

Push-to-talk
Push-to-talk
Preparing your graph…

Loading the interactive viewer.

Mute your microphone
Mute your microphone
Preparing your graph…

Loading the interactive viewer.

Microphone volume
Microphone volume
Preparing your graph…

Loading the interactive viewer.

Playback volume
Playback volume
Preparing your graph…

Loading the interactive viewer.

Choose a microphone
Choose a microphone
Preparing your graph…

Loading the interactive viewer.

Choose an output device
Choose an output device
Preparing your graph…

Loading the interactive viewer.

Uses the includes above.

void ReportVoiceControl(const FBetideError &Error)
{
    if (Error.IsError())
        UE_LOG(LogUCIK, Warning, TEXT("%s"), *Error.GetLogString());
}

void SetPushToTalk(UGameInstance &GameInstance, bool bPressed)
{
    if (auto *Voice = GameInstance.GetSubsystem<UUCIKVoiceSubsystem>())
        ReportVoiceControl(Voice->SetTransmitMode(
            bPressed ? EUCIK_VoiceTransmitMode::All : EUCIK_VoiceTransmitMode::None, {}));
}

void SetMicrophoneMuted(UGameInstance &GameInstance, bool bMuted)
{
    if (auto *Voice = GameInstance.GetSubsystem<UUCIKVoiceSubsystem>())
        ReportVoiceControl(Voice->SetLocalMuted(bMuted));
}

void SetMicrophoneVolume(UGameInstance &GameInstance, float Volume)
{
    if (auto *Voice = GameInstance.GetSubsystem<UUCIKVoiceSubsystem>())
        ReportVoiceControl(Voice->SetInputVolume(Volume));
}

void SetPlaybackVolume(UGameInstance &GameInstance, float Volume)
{
    if (auto *Voice = GameInstance.GetSubsystem<UUCIKVoiceSubsystem>())
        ReportVoiceControl(Voice->SetOutputVolume(Volume));
}

void ListVoiceDevices(UGameInstance &GameInstance)
{
    if (auto *Voice = GameInstance.GetSubsystem<UUCIKVoiceSubsystem>())
    {
        for (const auto &Device : Voice->GetInputDevices())
            UE_LOG(LogUCIK, Log, TEXT("Microphone: %s (%s)"), *Device.DisplayName, *Device.Id);
        for (const auto &Device : Voice->GetOutputDevices())
            UE_LOG(LogUCIK, Log, TEXT("Output: %s (%s)"), *Device.DisplayName, *Device.Id);
    }
}

void SelectMicrophone(UGameInstance &GameInstance, const FUCIK_VoiceDevice &Device)
{
    if (auto *Voice = GameInstance.GetSubsystem<UUCIKVoiceSubsystem>())
        ReportVoiceControl(Voice->SetInputDevice(Device.Id));
}

void SelectVoiceOutput(UGameInstance &GameInstance, const FUCIK_VoiceDevice &Device)
{
    if (auto *Voice = GameInstance.GetSubsystem<UUCIKVoiceSubsystem>())
        ReportVoiceControl(Voice->SetOutputDevice(Device.Id));
}

For push-to-talk, send Pressed = false after joining and on input release; send true while held. Volume ranges from 0 to 2, with 1 as normal. Pick devices from the returned list, not a hard-coded device ID.

Mute a player and leave

Mute a player
Mute a player
Preparing your graph…

Loading the interactive viewer.

Adjust player volume
Adjust player volume
Preparing your graph…

Loading the interactive viewer.

Show who is speaking
Show who is speaking
Preparing your graph…

Loading the interactive viewer.

Leave lobby voice
Leave lobby voice
Preparing your graph…

Loading the interactive viewer.

Uses the includes above.

FBetideCallback<bool> ReportVoiceResult(UGameInstance &GameInstance)
{
    return FBetideCallback<bool>::CreateWeakLambda(
        &GameInstance, [](const TBetideResult<bool> &Result) {
            if (!Result.bOk)
                UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
            else
                UE_LOG(LogUCIK, Log, TEXT("Voice updated"));
        });
}

void MuteVoicePlayer(UGameInstance &GameInstance, const FUCIK_PlayerHandle &Player, bool bMuted)
{
    if (auto *Voice = GameInstance.GetSubsystem<UUCIKVoiceSubsystem>())
        Voice->SetPlayerMuted(Player, bMuted, ReportVoiceResult(GameInstance));
}

void AdjustVoicePlayer(UGameInstance &GameInstance, const FUCIK_PlayerHandle &Player, float Volume)
{
    if (auto *Voice = GameInstance.GetSubsystem<UUCIKVoiceSubsystem>())
        Voice->SetPlayerVolume(Player, Volume, ReportVoiceResult(GameInstance));
}

bool IsVoicePlayerTalking(UGameInstance &GameInstance, const FUCIK_PlayerHandle &Player)
{
    const auto *Voice = GameInstance.GetSubsystem<UUCIKVoiceSubsystem>();
    return Voice && Voice->IsPlayerTalking(Player);
}

void LeavePartyVoice(UGameInstance &GameInstance, const FUCIK_Lobby &Lobby)
{
    if (auto *Voice = GameInstance.GetSubsystem<UUCIKVoiceSubsystem>())
    {
        FUCIK_VoiceChannel Channel;
        Channel.Kind = EUCIK_VoiceChannelKind::Lobby;
        Channel.Lobby = Lobby.Primary;
        Voice->LeaveChannel(Channel, ReportVoiceResult(GameInstance));
    }
}

For a mute that survives sessions, use UUCIKSocialSubsystem::SetPlayerMuted from Social/UCIKSocialSubsystem.h.

Positional voice

Add Crossplay Positional Voice to the pawn class, give it an attenuation asset with spatialization on, and every remote player’s voice plays from their own pawn. It follows whichever voice the crossplay lobby uses (EOS lobby voice or Discord), so there is nothing to change when a lobby switches voice service, and there are no room names to set.

The component matches its pawn’s player state to a lobby member, so it works once the player has joined the crossplay lobby and travelled with it. Players whose pawn has no component keep ordinary voice. Mute, player volume and deafen from the crossplay voice nodes still apply. Is Crossplay Voice Positional reports whether the speaker currently plays through the component.

Positional speech is played by Unreal, not by the voice service, so the service’s echo cancellation does not hear it. Players on speakers without a headset may send echo back; recommend headsets. The kits have their own components for games that use one service alone: EIK Voice Chat Synth Component for EOS and Discord Positional Voice for Discord.

Travel, credentials, and testing

The subsystem restores channels, mutes, and transmit state after non-seamless travel. Reset Crossplay Voice After Travel is available for an explicit reset; avoid adding a second join on every map load.

Named rooms can require an access token and endpoint from your server. Credentials depend on the voice provider; a lobby handle is not a general-purpose room token.

An EOS token room joined with EIK Token Voice Chat Join Channel is controlled by these nodes while no crossplay lobby voice is active, and Get Joined Crossplay Voice Channels lists it. EIK owns the room: it is not rejoined after travel, and an active lobby voice keeps the controls.

Test with two signed-in accounts and separate audio devices. Check OS microphone permissions and the selected input device before debugging network routing. The Steam-only preset does not supply a voice provider; EOS voice and Discord voice have their own setup.

Was this page helpful?