본문 바로가기
Unreal

언리얼 스팀 연동

by ji-han 2025. 5. 9.

1. 스팀 플러그인 적용

 

 

2. https://partner.steamgames.com/  스팀 SDK 다운로드

 

Steamworks - Build & Distribute Your Games on Steam

Steamworks is a set of tools and services that help game developers and publishers build their games and get the most out of distributing on Steam.

partner.steamgames.com

 

  • redistributable_bin/steam_api.dll
  • redistributable_bin/win64/steam_api64.dll
  • redistributable_bin/steam_appid.txt

SDK에서 3개의 파일을

Binaries/Win64/ 또는  빌드된 실행 파일(.exe)이 들어간 폴더 옆에 배치

 

 

 

 

3. DefaultEngine.ini 파일에 다음 코드 추가

여기서 480은 테스트용 앱 ID이기 때문에

상용 게임이면 반드시 본인 앱 ID로 바꿔야 함

 

-> 여기까지 진행했을 때 스팀 오버레이가 나타나는 것을 확인

이후 세션을 생성하여 멀티플레이가 가능하도록 만들어 보려고 함

 

제가 생각했던 구현 방법은

1. https://vreue4.com/advanced-sessions-binaries 여기서 advanced-Sessions을 이용하여 BP를 통해 구현

2. C++을 통한 구현

2가지 중에 우선 C++을 통한 구현을 선택하여 진행

 

게임 인스턴스 -> 세션 생성, 참가, 세션 목록 찾기

#include "ZoneGameInstance.h"
#include "OnlineSubsystem.h"
#include "OnlineSubsystemSteam.h"
#include "OnlineSessionSettings.h"
#include "Interfaces/OnlineIdentityInterface.h"
#include "Kismet/GameplayStatics.h"
#include "OnlineMenuWidget.h"
#include "GameFramework/HUD.h"
#include "GameFramework/PlayerController.h"

void UZoneGameInstance::Init()
{
    IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get();
    if (Subsystem)
    {
        SessionInterface = Subsystem->GetSessionInterface();
    }
}

void UZoneGameInstance::CreateRoom()
{
    if (!SessionInterface.IsValid()) return;

    IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get();
    if (!Subsystem) return;

    bool bIsUsingLAN = (Subsystem->GetSubsystemName().ToString() == "NULL");

    FString UniqueSessionNameStr;
    if (bIsUsingLAN)
    {
        UniqueSessionNameStr = FString::Printf(TEXT("LANSession_%d"), FDateTime::Now().GetTicks());
    }
    else
    {
        IOnlineIdentityPtr IdentityInterface = Subsystem->GetIdentityInterface();
        if (!IdentityInterface.IsValid()) return;

        TSharedPtr<const FUniqueNetId> UserId = IdentityInterface->GetUniquePlayerId(0);
        if (!UserId.IsValid()) return;

        UniqueSessionNameStr = FString::Printf(TEXT("SteamSession_%s"), *UserId->ToString());
    }

    CurrentSessionName = FName(*UniqueSessionNameStr);

    FNamedOnlineSession* ExistingSession = SessionInterface->GetNamedSession(CurrentSessionName);
    if (ExistingSession)
    {
        SessionInterface->OnDestroySessionCompleteDelegates.AddUObject(this, &UZoneGameInstance::OnSessionDestroyed);
        SessionInterface->DestroySession(CurrentSessionName);
    }
    else
    {
        ActuallyCreateSession(bIsUsingLAN);
    }
}

void UZoneGameInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
    if (bWasSuccessful)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("Successfully!"));
        UGameplayStatics::OpenLevel(GetWorld(), "LobbyMap", true, "listen");
    }
}

void UZoneGameInstance::OnSessionDestroyed(FName DestroyedSessionName, bool bWasSuccessful)
{
    if (DestroyedSessionName == CurrentSessionName)
    {
        IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get();
        bool bIsUsingLAN = (Subsystem->GetSubsystemName().ToString() == "NULL");
        ActuallyCreateSession(bIsUsingLAN);
    }
}

void UZoneGameInstance::ActuallyCreateSession(bool bIsUsingLAN)
{
    FOnlineSessionSettings SessionSettings;
    SessionSettings.bIsLANMatch = bIsUsingLAN;
    SessionSettings.NumPublicConnections = 4;
    SessionSettings.bShouldAdvertise = true;
    SessionSettings.bUsesPresence = !bIsUsingLAN;

    bool bCreated = SessionInterface->CreateSession(0, CurrentSessionName, SessionSettings);
    if (bCreated)
    {
        SessionInterface->OnCreateSessionCompleteDelegates.AddUObject(this, &UZoneGameInstance::OnCreateSessionComplete);
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, FString::Printf(TEXT("Session creation started: %s"), *CurrentSessionName.ToString()));
    }
    else
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Failed to start session creation"));
    }
}

void UZoneGameInstance::Shutdown()
{
    Super::Shutdown();
    if (SessionInterface.IsValid())
    {
        FNamedOnlineSession* ExistingSession = SessionInterface->GetNamedSession(CurrentSessionName);
        if (ExistingSession)
        {
            SessionInterface->DestroySession(CurrentSessionName);
            GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("Session destroyed on shutdown"));
        }
    }
}

void UZoneGameInstance::FindRooms()
{
    CombinedSearchResults.Empty();
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Find"));

    // LAN 세션 검색
    LANSessionSearch = MakeShareable(new FOnlineSessionSearch());
    LANSessionSearch->bIsLanQuery = true;
    LANSessionSearch->MaxSearchResults = 10;
    SessionInterface->OnFindSessionsCompleteDelegates.AddUObject(this, &UZoneGameInstance::OnLANFindSessionsComplete);
    SessionInterface->FindSessions(0, LANSessionSearch.ToSharedRef());

    // 온라인 세션 검색
    OnlineSessionSearch = MakeShareable(new FOnlineSessionSearch());
    OnlineSessionSearch->bIsLanQuery = false;
    OnlineSessionSearch->MaxSearchResults = 10;
    SessionInterface->OnFindSessionsCompleteDelegates.AddUObject(this, &UZoneGameInstance::OnOnlineFindSessionsComplete);
    SessionInterface->FindSessions(0, OnlineSessionSearch.ToSharedRef());
}

void UZoneGameInstance::OnLANFindSessionsComplete(bool bWasSuccessful)
{
    if (bWasSuccessful && LANSessionSearch.IsValid())
    {
        CombinedSearchResults.Append(LANSessionSearch->SearchResults);
    }
    CheckAndPopulateCombinedResults();
}

void UZoneGameInstance::OnOnlineFindSessionsComplete(bool bWasSuccessful)
{
    if (bWasSuccessful && OnlineSessionSearch.IsValid())
    {
        CombinedSearchResults.Append(OnlineSessionSearch->SearchResults);
    }
    CheckAndPopulateCombinedResults();
}

void UZoneGameInstance::CheckAndPopulateCombinedResults()
{
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("check"));
    // 둘 다 완료되었는지 확인
    if (LANSessionSearch.IsValid() && OnlineSessionSearch.IsValid() &&
        LANSessionSearch->SearchState == EOnlineAsyncTaskState::Done &&
        OnlineSessionSearch->SearchState == EOnlineAsyncTaskState::Done)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow,
            FString::Printf(TEXT("CombinedSearchResults count: %d"), CombinedSearchResults.Num()));
        if (UWorld* World = GetWorld())
        {
            if (APlayerController* PC = World->GetFirstPlayerController())
            {
                if (UOnlineMenuWidget* MainMenu = Cast<UOnlineMenuWidget>(PC->GetHUD()))
                {
                    MainMenu->PopulateSessionList(CombinedSearchResults);
                }
            }
        }
    }
}

void UZoneGameInstance::JoinRoom(const FOnlineSessionSearchResult& SearchResult)
{
    SessionInterface->JoinSession(0, FName("MySession"), SearchResult);
    SessionInterface->OnJoinSessionCompleteDelegates.AddUObject(this, &UZoneGameInstance::OnJoinSessionComplete);
}

void UZoneGameInstance::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
    FString Address;
    if (SessionInterface->GetResolvedConnectString(SessionName, Address))
    {
        APlayerController* PC = GetFirstLocalPlayerController();
        if (PC)
        {
            PC->ClientTravel(Address, ETravelType::TRAVEL_Absolute);
        }
    }
}

Lan이 아닌 스팀에서 스팀고유ID를 이용하여 세션을 생성하는 것은 테스트해보지 못함

세션 찾기시 목록들이 보이지 않음

 

메인 위젯 -> 버튼2개, 목록 리스트 

#include "OnlineMenuWidget.h"
#include "SessionSlotWidget.h"
#include "OnlineSessionSettings.h"
#include "Components/ScrollBox.h"
#include "Components/Button.h"
#include "Kismet/GameplayStatics.h"
#include "ZoneGameInstance.h"

bool UOnlineMenuWidget::Initialize()
{
    if (!Super::Initialize()) return false;

    if (CreateRoomButton)
    {
        CreateRoomButton->OnClicked.AddDynamic(this, &UOnlineMenuWidget::OnCreateRoomClicked);
    }

    if (JoinRoomButton)
    {
        JoinRoomButton->OnClicked.AddDynamic(this, &UOnlineMenuWidget::OnJoinRoomClicked);
    }

    return true;
}

void UOnlineMenuWidget::PopulateSessionList(const TArray<FOnlineSessionSearchResult>& SearchResults)
{
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow,
        FString::Printf(TEXT("PopulateSessionList called with %d results"), SearchResults.Num()));

    CurrentSearchResults = SearchResults;
    SessionListScrollBox->ClearChildren();

    for (int32 i = 0; i < SearchResults.Num(); ++i)
    {
        USessionSlotWidget* NewSlot = CreateWidget<USessionSlotWidget>(this, SessionSlotWidgetClass);
        NewSlot->Setup(i, SearchResults[i].Session.OwningUserName);
        NewSlot->OnJoinSessionClicked.AddDynamic(this, &UOnlineMenuWidget::OnSessionSlotClicked);
        SessionListScrollBox->AddChild(NewSlot);
    }
}

void UOnlineMenuWidget::OnSessionSlotClicked(int32 Index)
{
    if (UZoneGameInstance* GI = Cast<UZoneGameInstance>(GetGameInstance()))
    {
        GI->JoinRoom(CurrentSearchResults[Index]);
    }
}

void UOnlineMenuWidget::OnCreateRoomClicked()
{
    if (UZoneGameInstance* GI = Cast<UZoneGameInstance>(UGameplayStatics::GetGameInstance(this)))
    {
        GI->CreateRoom();
    }
}

void UOnlineMenuWidget::OnJoinRoomClicked()
{
    if (UZoneGameInstance* GI = Cast<UZoneGameInstance>(UGameplayStatics::GetGameInstance(this)))
    {
        GI->FindRooms();
    }
}

버튼 클릭시 실행되는 이벤트 배당

 

크게 2개의 C++ 클래스를 작성하였지만

advanced-Sessions을 이용하여 작성하신 분이 더 빠르게 구현해주셨기에

세션 생성및 참가의 C++구현은 프로젝트 완료 후 혼자 다시 해볼 예정입니다

'Unreal' 카테고리의 다른 글

스팀 친구 초대(실패)  (1) 2025.05.23
쿼터뷰  (0) 2025.04.28
FIFA 매칭 시스템  (0) 2025.04.25
간단한 미니맵 만들기  (0) 2025.04.11
FPS / V-Sync  (0) 2025.04.02