오늘의 목표
팀 프로젝트
- 멀티 플레이 테스트 환경 구축 (스팀 테스트ID 사용)
- 메인메뉴, 닉네임 변경 UI 개선 (피드백 반영)
- 로비, 인게임, 점수 화면 UI 디자인 및 구현
c++로 멀티플레이 환경 구축하기
1. 프로젝트 모듈 세팅 (.Build.cs)
// 기본 모듈들에 OnlineSubsystem 관련 모듈들을 추가합니다.
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore",
"OnlineSubsystem",
"OnlineSubsystemSteam",
"OnlineSubsystemUtils" // 이 3가지를 추가해 주세요!
});
2. 스팀 서브시스템 활성화 (DefaultEngine.ini)
프로젝트의 Config 폴더 안에 있는 DefaultEngine.ini 파일을 열고 맨 아래에 다음 내용(스팀 테스트용 설정)을 복사해서 붙여넣는다.
[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
[OnlineSubsystem]
DefaultPlatformService=Steam
[OnlineSubsystemSteam]
bEnabled=true
SteamDevAppId=480
bInitServerOnClient=true
[/Script/OnlineSubsystemSteam.SteamNetDriver]
NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection"
3. GameInstance 클래스 만들기


4. GameInstance 작성
SGMultiplayGameInstance.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "Interfaces/OnlineSessionInterface.h"
#include "SGMultiplayGameInstance.generated.h"
/**
*
*/
UCLASS()
class SOCCERGAME_API USGMultiplayGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
USGMultiplayGameInstance();
UFUNCTION(BlueprintCallable, Category = "MultiPlayer")
void CreateServer();
protected:
virtual void Init() override;
// 방 생성 완료시 실행될 함수
void OnCreateSessionComplete(FName Sessionname, bool bWasSuccessful);
private:
// 엔진의 세션 기능을 담당할 세션 매니저 포인터
IOnlineSessionPtr SessionInterface;
// 델리게이트 관련 변수
FOnCreateSessionCompleteDelegate CreateSessionCompleteDelegate;
FDelegateHandle CreateSessionCompleteDelegateHandle;
};
SGMultiplayGameInstance.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Multiplay/SGMultiplayGameInstance.h"
#include "OnlineSessionSettings.h"
#include "OnlineSubsystem.h"
USGMultiplayGameInstance::UMultiplayerGameInstance()
{
// 콜백 델리게이트와 OnCreateSessionComplete 연결
CreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnCreateSessionComplete)
}
void USGMultiplayGameInstance::Init()
{
Super::Init();
// Online Subsystem과 인터페이스 연결
IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get();
if (Subsystem)
{
SessionInterface = Subsystem->GetSessionInterface();
UE_LOG(LogTemp, Warning, TEXT("찾아낸 서브시스템: %s"), *Subsystem->GetSubsystemName().ToString())
}
}
void USGMultiplayGameInstance::CreateServer()
{
if (SessionInterface.IsValid())
{
// 방 생성 끝나면 알려주는 델리게이트 등록
CreateSessionCompleteDelegateHandle = SessionInterface->AddOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegate);
FOnlineSessionSettings SessionSettings;
SessionSettings.bIsLANMatch = false; // 스팀(인터넷) 통신 사용
SessionSettings.NumPublicConnections = 6; // 최대 인원수 TODO: 나중에 변수 가져오기
SessionSettings.bAllowJoinInProgress = false; // 게임 중 난입 허용 여부
SessionSettings.bAllowJoinViaPresence = true; // 스팀 친구창 등으로 접속 허용
SessionSettings.bShouldAdvertise = true; // 방이 검색되도록 허용
SessionSettings.bUsesPresence = true; // 스팀의 Presence(현재 상태) 기능 사용
// 엔진에 세션 생성 명령
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
SessionInterface->CreateSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, SessionSettings);
}
}
void USGMultiplayGameInstance::OnCreateSessionComplete(FName Sessionname, bool bWasSuccessful)
{
if (SessionInterface.IsValid())
{
// 델리게이트 해제
SessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegateHandle);
}
if (bWasSuccessful)
{
UE_LOG(LogTemp, Warning, TEXT("[Multiplay] 방 생성 성공! 로비로 이동합니다."))
UWorld* World = GetWorld();
if (World)
{
// 레벨 열기
World->ServerTravel("/Game/SoccerGame/Maps/SG_LobbyLevel?listen");
}
}
}
StandAlone 게임으로 실행해서 shift+tab을 눌렀을 때 다음처럼 Spacewar을 플레이중이라고 뜨면 성공.

기존 GameStart 로직


이미 SG_LobbyLevel로 이동하는 로직은 SGMultiplayGameInstance에서 구현되어있기 때문에, SGLobbyPlayerController쪽에서 UI를 띄울 수 있도록 서버/GameMode 담당 팀원에게 작업내용 인계함.
멀티플레이 세션 만들기 -> 접속 로직 구현
void USGMultiplayGameInstance::FindServers()
{
if (SessionInterface.IsValid())
{
FindSessionsCompleteDelegateHandle = SessionInterface->AddOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegate);
// 검색 바구니 세팅
SessionSearch = MakeShareable(new FOnlineSessionSearch());
SessionSearch->MaxSearchResults = 10000;
SessionSearch->bIsLanQuery = false;
SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);
// 검색 시작!
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
SessionInterface->FindSessions(*LocalPlayer->GetPreferredUniqueNetId(), SessionSearch.ToSharedRef());
}
}
void USGMultiplayGameInstance::JoinServer(int32 SessionIndex)
{
if (SessionInterface.IsValid() && SessionSearch.IsValid())
{
if (SessionSearch->SearchResults.IsValidIndex(SessionIndex))
{
JoinSessionCompleteDelegateHandle = SessionInterface->AddOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegate);
// 접속 시도
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
SessionInterface->JoinSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, SessionSearch->SearchResults[SessionIndex]);
}
}
}
oid USGMultiplayGameInstance::OnFindSessionsComplete(bool bWasSuccessful)
{
if (SessionInterface.IsValid())
{
SessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegateHandle);
}
if (bWasSuccessful && SessionSearch.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("[Multiplay] 검색 완료! 찾은 방 갯수: %d"), SessionSearch->SearchResults.Num());
if (SessionSearch->SearchResults.Num() > 0)
{
// 0번 서버에 바로 접속 (자동 접속 시스템)
JoinServer(0);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("[Multiplay] 빈 방이 없습니다. 직접 서버를 생성합니다."));
CreateServer();
}
}
}
void USGMultiplayGameInstance::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
if (SessionInterface.IsValid())
{
SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegateHandle);
}
if (Result == EOnJoinSessionCompleteResult::Success)
{
UE_LOG(LogTemp, Warning, TEXT("[Multiplay] 방 접속 성공! 로딩을 시작합니다."));
// 스팀 서버로부터 들어갈 방의 실제 네트워크 주소를 받기
FString ConnectInfo;
if (SessionInterface->GetResolvedConnectString(NAME_GameSession, ConnectInfo))
{
APlayerController* PlayerController = GetFirstLocalPlayerController(0);
if (PlayerController)
{
PlayerController->ClientTravel(ConnectInfo, ETravelType::TRAVEL_Absolute);
}
}
}
}
(AI한테 물어본 내용)
* 나중에 '서버 브라우저(방 목록 UI)'로 수정하고 싶을 때
- C++ 코드 수정: 위 코드에서 JoinServer(0); 와 CreateServer(); 라고 적힌 부분을 그냥 지워버립니다.
- 데이터 전달: 대신에 엔진이 찾아온 방 목록(SessionSearch->SearchResults) 데이터를 블루프린트(UI) 쪽으로 촥 넘겨주는 코드를 작성합니다.
- UI 연동: 유저가 화면에 뜬 방 목록 중 하나를 클릭하면, 그 버튼이 C++의 JoinServer(클릭한 인덱스 번호)를 호출하도록 연결만 해주면 끝납니다.
문제 발생
SGMainMenuGameMode, SGMainMenuPlayerController를 모둣 설정했는데 SGMainMenu 레벨에서 LobbyLevel레벨로 넘어가지 않는 현상 발생.
디버그 로그로 확인 결과 스팀을 통해 멀티플레이 세션을 생성하는 것에 실패함.

네트워크 세팅을 조금 변경함.
기존: 스팀 온라인 모드 무조건 사용
수정: 스팀 네트워크 연결이 되지 않을 시 LAN 모드 사용
-> 하지만 목표로 하는 것이 LAN 모드가 아니기 때문에 다시 스팀 온라인으로 연결하도록 설정했더니 왠지 오류가 고쳐짐.
'Unreal5 공부 > TIL' 카테고리의 다른 글
| 2026/07/21 TIL (0) | 2026.07.21 |
|---|---|
| 2026/07/20 TIL (1) | 2026.07.20 |
| 2026/07/09 TIL (1) | 2026.07.09 |
| 2026/07/06 TIL (0) | 2026.07.06 |
| 2026/07/02 TIL (0) | 2026.07.02 |
