본문 바로가기

EOS 보이스 채팅 구현

@iamrain2026. 1. 5. 20:47

1) 사용 플러그인 및 설정

  • Tarcopy.uproject
    • EOSVoiceChat, EOSVoiceChatPTT, OnlineSubsystemEOS 플러그인 활성화
  • Config/DefaultEngine.ini
    • EOSVoiceChat 활성화
    • PTT 기본값은 토글용으로 사용하기 위해 bAlwaysTransmitByDefault=false로 설정
[EOSVoiceChat]
bEnabled=true

[/Script/EOSVoiceChatPTT.UEOSVoiceChatSubsystem]
bEnableVoiceLobby=true
VoiceLobbyIdPrefix=VOICE
VoiceLobbyBucketId=voice
VoiceLobbyMaxMembers=16
bAlwaysTransmitByDefault=false

2) 핵심 서브시스템 구조

보이스 채팅은 UGameInstanceSubsystem 기반인 UEOSVoiceChatSubsystem에서 처리.

  • 파일: Plugins/EOSVoiceChatPTT/Source/EOSVoiceChatPTT/Public/UEOSVoiceChatSubsystem.h
  • 주요 기능
    • EOS Voice Chat 초기화/접속/로그인
    • Lobby RTC 우선 접속 및 fallback 채널
    • V 키 토글 동작
    • 음성 표시(아이콘)용 브로드캐스트
UCLASS(Config=Engine)
class EOSVOICECHATPTT_API UEOSVoiceChatSubsystem : public UGameInstanceSubsystem
{
  UFUNCTION(BlueprintCallable)
  void ToggleMute(bool bMute);

  UFUNCTION(BlueprintCallable)
  void SetPTTEnabled(bool bEnabled);

  UFUNCTION(BlueprintCallable)
  void SetAlwaysTransmit(bool bEnabled);

  UFUNCTION(BlueprintCallable)
  void JoinChannel(const FString& ChannelName);

  UFUNCTION(BlueprintCallable)
  void LeaveChannel();

  UFUNCTION(BlueprintCallable)
  bool IsVoiceIndicatorActive() const;

  UPROPERTY(BlueprintAssignable)
  FOnVoiceTransmitStateChanged OnVoiceTransmitStateChanged;
};

3) 초기화 및 로그인 흐름

  • Initialize()에서 config 로드 후 InitializeVoiceChat() 실행
  • HandleVoiceChatInitialized()HandleVoiceChatConnected()EnsureConnectLogin()
  • Dedicated Server에서는 EnsureConnectLogin()이 즉시 return 되어 EOS Connect 로그인 시도 자체를 하지 않음
void UEOSVoiceChatSubsystem::EnsureConnectLogin()
{
  if (!VoiceChat || !VoiceChat->IsInitialized() || !VoiceChat->IsConnected()) return;
  if (IsRunningDedicatedServer()) return;
  ...
}

EOS Connect 로그인은 DeviceId 방식으로 수행됨.

  • 같은 PC에서 서버/클라를 동시에 켤 때 동일 PUID가 생성되어 RTC가 충돌할 수 있음 → 서버에서는 로그인 스킵

4) 채널/Lobby 접속 흐름

JoinChannel()은 다음 순서로 시도함.

1) Voice Lobby (Lobby RTC) 우선
2) 세션 Lobby RTC
3) Manual RTC (설정되어 있으면)
4) EOS VoiceChat 일반 채널 join

if (bEnableVoiceLobby && bPreferLobbyRtc)
{
  if (ActiveLobbyId.IsEmpty()) { EnsureVoiceLobby(); ... }
  if (TryUseLobbyRtc(ActiveLobbyId)) { ... }
}
...
ActiveRoomName = Sanitized;
ChannelMode = EEOSVoiceChannelMode::VoiceChat;
VoiceChatUser->JoinChannel(...);

Lobby ID 생성

  • 서버 주소를 기반으로 CRC32 해시 → VOICE_xxxxxxxx 형태
const uint32 Hash = FCrc::StrCrc32(*ServerKey);
const FString Base = FString::Printf(TEXT("%s_%08X"), *VoiceLobbyIdPrefix, Hash);

5) 맵 전환 시 처리

  • Redwood(자동 조인 맵) 이외 맵으로 이동하면
    • Lobby RTC 채널 leave
    • Lobby 상태 강제 리셋
    • PTT/AlwaysTransmit 상태 리셋
if (ChannelMode == EEOSVoiceChannelMode::LobbyRtc || ChannelMode == EEOSVoiceChannelMode::LobbyRtcSdk)
{
  LeaveChannel();
}

LeaveVoiceLobby();
if (bVoiceLobbyInFlight)
{
  bVoiceLobbyInFlight = false;
  VoiceLobbyIdOverride.Reset();
  ActiveLobbyId.Reset();
}

bPTTEnabled = bEnablePTTByDefault;
bAlwaysTransmit = bAlwaysTransmitByDefault;
bIsPTTActive = false;
UpdateRTCSending(false);
UpdateVoiceIndicatorFromState();

6) PTT → 토글 동작 변경

토글 방식 입력은 Enhanced Input을 우선 사용하고, 실패 시 Legacy 바인딩을 사용함.
중복 호출 방지를 위해 Enhanced가 성공하면 Legacy는 바인딩하지 않음.

void UEOSVoiceChatSubsystem::OnPTTPressed()
{
  if (bAlwaysTransmit) return;
  if (!bPTTEnabled || bMuted) return;

  bIsPTTActive = !bIsPTTActive;
  UpdateRTCSending(bIsPTTActive);
  UpdateVoiceIndicatorFromState();
}

void UEOSVoiceChatSubsystem::OnPTTReleased()
{
  // 토글 모드에서는 처리 없음
}

7) 음성 전송 상태 표시 (UI)

음성 상태 변화는 서브시스템에서 브로드캐스트 됨.

  • OnVoiceTransmitStateChanged(bool bIsActive)
void UEOSVoiceChatSubsystem::UpdateVoiceIndicatorFromState()
{
  const bool bActive = IsVoiceIndicatorActive();
  if (bVoiceIndicatorActive == bActive) return;

  bVoiceIndicatorActive = bActive;
  OnVoiceTransmitStateChanged.Broadcast(bVoiceIndicatorActive);
}

UI는 UUISubsystem에서 ShowUI/HideUI로 제어.

  • EUIType::VoiceIndicatorDA_UIConfig에 등록해야 함
void UUISubsystem::HandleVoiceTransmitStateChanged(bool bIsActive)
{
  if (bIsActive) ShowUI(EUIType::VoiceIndicator);
  else HideUI(EUIType::VoiceIndicator);
}

8) Lobby 퇴장 안정화

LeaveVoiceLobby()는 EOS 핸들이 없거나 ActiveLobbyId가 비어 있어도 상태를 리셋하도록 수정되어, 다음 접속에서 막히는 문제를 방지.

if (!EOSLobbyHandle || !LocalProductUserId || ActiveLobbyId.IsEmpty())
{
  ActiveLobbyId.Reset();
  VoiceLobbyIdOverride.Reset();
  bVoiceLobbyInFlight = false;
  return;
}

'Unreal' 카테고리의 다른 글

오브젝트 풀링 (Object Pooling)  (0) 2026.01.09
Voxel Terrain  (0) 2026.01.06
EOS 보이스 인터페이스  (0) 2025.12.26
Unreal Engine 패키징  (0) 2025.12.24
Installed Engine vs Source Build Engine  (0) 2025.12.23
iamrain
@iamrain :: Annals of Unreal

iamrain 님의 블로그 입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차