언리얼용 플러그인: Amazon에서 로컬 테스트 설정 GameLift Anywhere - 아마존 GameLift

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

언리얼용 플러그인: Amazon에서 로컬 테스트 설정 GameLift Anywhere

이 워크플로우에서는 Amazon GameLift 기능을 위한 클라이언트 및 서버 게임 코드를 추가하고 플러그인을 사용하여 로컬 워크스테이션을 테스트 게임 서버 호스트로 지정합니다. 통합 작업을 완료하면 플러그인을 사용하여 게임 클라이언트 및 서버 구성 요소를 빌드합니다.

Amazon GameLift Anywhere 워크플로를 시작하려면:
  • 언리얼 에디터 메인 툴바에서 Amazon GameLift 메뉴를 선택하고 Host with Anywhere를 선택합니다. 이 작업을 수행하면 Anywhere 배포 플러그인 페이지가 열리고 게임 구성 요소를 통합, 빌드 및 시작하는 6단계 프로세스를 제공합니다.

1단계: 프로필 설정

이 워크플로를 따를 때 사용할 프로필을 선택합니다. 선택한 프로필은 워크플로의 모든 단계에 영향을 줍니다. 생성하는 모든 리소스는 프로필 AWS 계정과 연결되며 프로필의 기본 AWS 지역에 배치됩니다. 프로필 사용자의 권한에 따라 AWS 리소스 및 작업에 대한 액세스 권한이 결정됩니다.

  1. 사용 가능한 프로필 드롭다운 목록에서 프로필을 선택합니다. 아직 프로필이 없거나 새 프로필을 만들려면 Amazon GameLift 메뉴로 이동하여 AWS 사용자 프로필 설정을 선택합니다.

  2. 부트스트랩 상태가 “활성”이 아닌 경우, Bootstrap 프로필을 선택하고 상태가 “활성”으로 변경될 때까지 기다리십시오.

2단계: 게임 코드 설정

이 단계에서는 클라이언트와 서버 코드에 대한 일련의 업데이트를 수행하여 호스팅 기능을 추가합니다. 언리얼 에디터의 소스 빌드 버전을 아직 설정하지 않은 경우 플러그인에서 지침과 소스 코드에 대한 링크를 제공합니다.

플러그인을 사용하면 게임 코드를 통합할 때 몇 가지 편리함을 활용할 수 있습니다. 최소한의 통합을 통해 기본 호스팅 기능을 설정할 수 있습니다. 더 광범위한 사용자 지정 통합을 수행할 수도 있습니다. 이 섹션의 정보는 최소 통합 옵션에 대해 설명합니다. 플러그인에 포함된 테스트 맵을 사용하여 게임 프로젝트에 클라이언트 Amazon GameLift 기능을 추가할 수 있습니다. 서버 통합의 경우 제공된 코드 샘플을 사용하여 프로젝트의 게임 모드를 업데이트합니다.

서버 게임 모드 통합

게임 서버와 Amazon GameLift 서비스 간의 통신을 지원하는 서버 코드를 게임에 추가합니다. 게임 서버는 새 게임 세션 시작과 같은 GameLift Amazon의 요청에 응답하고 게임 서버 상태 및 플레이어 연결 상태를 보고할 수 있어야 합니다.

  1. 코드 편집기에서 일반적으로 프로젝트 루트 폴더에 있는 게임 프로젝트의 솔루션(.sln) 파일을 엽니다. 예를 들면 GameLiftUnrealApp.sln입니다.

  2. 솔루션이 열린 상태에서 프로젝트 게임 모드 헤더 파일인 [project-name]GameMode.h 파일을 찾습니다. 예를 들면 GameLiftUnrealAppGameMode.h입니다.

  3. 헤더 파일을 다음 예제 코드에 맞게 변경합니다. 반드시 GameLiftServer "“를 자신의 프로젝트 이름으로 바꾸십시오. 이러한 업데이트는 게임 서버에만 적용되므로 클라이언트에서 사용할 수 있도록 원본 게임 모드 파일의 백업 복사본을 만드는 것이 좋습니다.

    // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #pragma once #include "CoreMinimal.h" #include "GameFramework/GameModeBase.h" #include "GameLiftServerGameMode.generated.h" struct FProcessParameters; DECLARE_LOG_CATEGORY_EXTERN(GameServerLog, Log, All); UCLASS(minimalapi) class AGameLiftServerGameMode : public AGameModeBase { GENERATED_BODY() public: AGameLiftServerGameMode(); protected: virtual void BeginPlay() override; private: void InitGameLift(); private: TSharedPtr<FProcessParameters> ProcessParameters; };
  4. 관련 소스 파일인 [project-name]GameMode.cpp 파일(예: GameLiftUnrealAppGameMode.cpp)을 엽니다. 코드를 다음 예제 코드에 맞게 변경합니다. 반드시 GameLiftUnrealApp "“를 자신의 프로젝트 이름으로 바꾸십시오. 이러한 업데이트는 게임 서버에만 적용되므로 클라이언트에서 사용할 수 있도록 원본 파일의 백업 복사본을 만드는 것이 좋습니다.

    다음 예제 코드는 Amazon과의 서버 통합에 필요한 최소 요소를 추가하는 방법을 보여줍니다 GameLift.

    • Amazon GameLift API 클라이언트를 초기화합니다. Amazon GameLift Anywhere 플릿에는 서버 파라미터를 사용한 InitSDK() 호출이 필요합니다. Anywhere 플릿에 연결하면 플러그인은 서버 파라미터를 콘솔 인수로 저장합니다. 샘플 코드는 런타임 시 값에 액세스할 수 있습니다.

    • OnStartGameSessionOnProcessTerminate, 및 onHealthCheck 등 Amazon GameLift 서비스의 요청에 응답하는 데 필요한 콜백 함수를 구현하십시오.

    • 게임 세션을 호스팅할 준비가 되면 지정된 ProcessReady() 포트로 전화하여 Amazon GameLift 서비스에 알리십시오.

    // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 #include "GameLiftServerGameMode.h" #include "UObject/ConstructorHelpers.h" #include "Kismet/GameplayStatics.h" #if WITH_GAMELIFT #include "GameLiftServerSDK.h" #include "GameLiftServerSDKModels.h" #endif #include "GenericPlatform/GenericPlatformOutputDevices.h" DEFINE_LOG_CATEGORY(GameServerLog); AGameLiftServerGameMode::AGameLiftServerGameMode() : ProcessParameters(nullptr) { // Set default pawn class to our Blueprinted character static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter")); if (PlayerPawnBPClass.Class != NULL) { DefaultPawnClass = PlayerPawnBPClass.Class; } UE_LOG(GameServerLog, Log, TEXT("Initializing AGameLiftServerGameMode...")); } void AGameLiftServerGameMode::BeginPlay() { Super::BeginPlay(); #if WITH_GAMELIFT InitGameLift(); #endif } void AGameLiftServerGameMode::InitGameLift() { #if WITH_GAMELIFT UE_LOG(GameServerLog, Log, TEXT("Calling InitGameLift...")); // Getting the module first. FGameLiftServerSDKModule* GameLiftSdkModule = &FModuleManager::LoadModuleChecked<FGameLiftServerSDKModule>(FName("GameLiftServerSDK")); //Define the server parameters for a GameLift Anywhere fleet. These are not needed for a GameLift managed EC2 fleet. FServerParameters ServerParametersForAnywhere; bool bIsAnywhereActive = false; if (FParse::Param(FCommandLine::Get(), TEXT("glAnywhere"))) { bIsAnywhereActive = true; } if (bIsAnywhereActive) { UE_LOG(GameServerLog, Log, TEXT("Configuring server parameters for Anywhere...")); // If GameLift Anywhere is enabled, parse command line arguments and pass them in the ServerParameters object. FString glAnywhereWebSocketUrl = ""; if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereWebSocketUrl="), glAnywhereWebSocketUrl)) { ServerParametersForAnywhere.m_webSocketUrl = TCHAR_TO_UTF8(*glAnywhereWebSocketUrl); } FString glAnywhereFleetId = ""; if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereFleetId="), glAnywhereFleetId)) { ServerParametersForAnywhere.m_fleetId = TCHAR_TO_UTF8(*glAnywhereFleetId); } FString glAnywhereProcessId = ""; if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereProcessId="), glAnywhereProcessId)) { ServerParametersForAnywhere.m_processId = TCHAR_TO_UTF8(*glAnywhereProcessId); } else { // If no ProcessId is passed as a command line argument, generate a randomized unique string. ServerParametersForAnywhere.m_processId = TCHAR_TO_UTF8( *FText::Format( FText::FromString("ProcessId_{0}"), FText::AsNumber(std::time(nullptr)) ).ToString() ); } FString glAnywhereHostId = ""; if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereHostId="), glAnywhereHostId)) { ServerParametersForAnywhere.m_hostId = TCHAR_TO_UTF8(*glAnywhereHostId); } FString glAnywhereAuthToken = ""; if (FParse::Value(FCommandLine::Get(), TEXT("glAnywhereAuthToken="), glAnywhereAuthToken)) { ServerParametersForAnywhere.m_authToken = TCHAR_TO_UTF8(*glAnywhereAuthToken); } UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_YELLOW); UE_LOG(GameServerLog, Log, TEXT(">>>> Web Socket URL: %s"), *ServerParametersForAnywhere.m_webSocketUrl); UE_LOG(GameServerLog, Log, TEXT(">>>> Fleet ID: %s"), *ServerParametersForAnywhere.m_fleetId); UE_LOG(GameServerLog, Log, TEXT(">>>> Process ID: %s"), *ServerParametersForAnywhere.m_processId); UE_LOG(GameServerLog, Log, TEXT(">>>> Host ID (Compute Name): %s"), *ServerParametersForAnywhere.m_hostId); UE_LOG(GameServerLog, Log, TEXT(">>>> Auth Token: %s"), *ServerParametersForAnywhere.m_authToken); UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_NONE); } UE_LOG(GameServerLog, Log, TEXT("Initializing the GameLift Server...")); //InitSDK will establish a local connection with GameLift's agent to enable further communication. FGameLiftGenericOutcome InitSdkOutcome = GameLiftSdkModule->InitSDK(ServerParametersForAnywhere); if (InitSdkOutcome.IsSuccess()) { UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_GREEN); UE_LOG(GameServerLog, Log, TEXT("GameLift InitSDK succeeded!")); UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_NONE); } else { UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_RED); UE_LOG(GameServerLog, Log, TEXT("ERROR: InitSDK failed : (")); FGameLiftError GameLiftError = InitSdkOutcome.GetError(); UE_LOG(GameServerLog, Log, TEXT("ERROR: %s"), *GameLiftError.m_errorMessage); UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_NONE); return; } ProcessParameters = MakeShared<FProcessParameters>(); //When a game session is created, GameLift sends an activation request to the game server and passes along the game session object containing game properties and other settings. //Here is where a game server should take action based on the game session object. //Once the game server is ready to receive incoming player connections, it should invoke GameLiftServerAPI.ActivateGameSession() ProcessParameters->OnStartGameSession.BindLambda([=](Aws::GameLift::Server::Model::GameSession InGameSession) { FString GameSessionId = FString(InGameSession.GetGameSessionId()); UE_LOG(GameServerLog, Log, TEXT("GameSession Initializing: %s"), *GameSessionId); GameLiftSdkModule->ActivateGameSession(); }); //OnProcessTerminate callback. GameLift will invoke this callback before shutting down an instance hosting this game server. //It gives this game server a chance to save its state, communicate with services, etc., before being shut down. //In this case, we simply tell GameLift we are indeed going to shutdown. ProcessParameters->OnTerminate.BindLambda([=]() { UE_LOG(GameServerLog, Log, TEXT("Game Server Process is terminating")); GameLiftSdkModule->ProcessEnding(); }); //This is the HealthCheck callback. //GameLift will invoke this callback every 60 seconds or so. //Here, a game server might want to check the health of dependencies and such. //Simply return true if healthy, false otherwise. //The game server has 60 seconds to respond with its health status. GameLift will default to 'false' if the game server doesn't respond in time. //In this case, we're always healthy! ProcessParameters->OnHealthCheck.BindLambda([]() { UE_LOG(GameServerLog, Log, TEXT("Performing Health Check")); return true; }); //GameServer.exe -port=7777 LOG=server.mylog ProcessParameters->port = FURL::UrlConfig.DefaultPort; TArray<FString> CommandLineTokens; TArray<FString> CommandLineSwitches; FCommandLine::Parse(FCommandLine::Get(), CommandLineTokens, CommandLineSwitches); for (FString SwitchStr : CommandLineSwitches) { FString Key; FString Value; if (SwitchStr.Split("=", &Key, &Value)) { if (Key.Equals("port")) { ProcessParameters->port = FCString::Atoi(*Value); } } } //Here, the game server tells GameLift where to find game session log files. //At the end of a game session, GameLift uploads everything in the specified //location and stores it in the cloud for access later. TArray<FString> Logfiles; Logfiles.Add(TEXT("GameServerLog/Saved/Logs/GameServerLog.log")); ProcessParameters->logParameters = Logfiles; //The game server calls ProcessReady() to tell GameLift it's ready to host game sessions. UE_LOG(GameServerLog, Log, TEXT("Calling Process Ready...")); FGameLiftGenericOutcome ProcessReadyOutcome = GameLiftSdkModule->ProcessReady(*ProcessParameters); if (ProcessReadyOutcome.IsSuccess()) { UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_GREEN); UE_LOG(GameServerLog, Log, TEXT("Process Ready!")); UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_NONE); } else { UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_RED); UE_LOG(GameServerLog, Log, TEXT("ERROR: Process Ready Failed!")); FGameLiftError ProcessReadyError = ProcessReadyOutcome.GetError(); UE_LOG(GameServerLog, Log, TEXT("ERROR: %s"), *ProcessReadyError.m_errorMessage); UE_LOG(GameServerLog, SetColor, TEXT("%s"), COLOR_NONE); } UE_LOG(GameServerLog, Log, TEXT("InitGameLift completed!")); #endif }

클라이언트 게임 맵 통합

스타트업 게임 맵에는 게임 세션을 요청하고 연결 정보를 사용하여 게임 세션에 연결하는 기본 코드가 이미 포함되어 있는 블루프린트 로직과 UI 요소가 포함되어 있습니다. 맵을 그대로 사용하거나 필요에 따라 수정할 수 있습니다. 스타트업 게임 맵을 다른 게임 애셋(예: Unreal Engine에서 제공하는 3인칭 템플릿 프로젝트)과 함께 사용합니다. 이러한 애셋은 콘텐츠 브라우저에서 사용할 수 있습니다. 이를 사용하여 플러그인의 배포 워크플로를 테스트하거나 게임용 사용자 지정 백엔드 서비스를 생성하기 위한 가이드로 사용할 수 있습니다.

스타트업 맵에는 다음과 같은 특성이 있습니다.

  • 여기에는 Anywhere 플릿과 관리형 EC2 플릿 모두에 대한 로직이 포함됩니다. 클라이언트를 실행할 때 두 플릿 중 하나에 연결하도록 선택할 수 있습니다.

  • 클라이언트 기능에는 게임 세션 찾기 (SearchGameSessions()), 새 게임 세션 생성 (CreateGameSession()), 게임 세션에 직접 참여하기 등이 포함됩니다.

  • 프로젝트의 Amazon Cognito 사용자 풀(배포된 Anywhere 솔루션의 일부)에서 고유한 플레이어 ID를 가져옵니다.

스타트업 게임 맵을 사용하려면
  1. UE 편집기에서 프로젝트 설정, 맵 및 모드 페이지를 열고 기본 맵 섹션을 확장합니다.

  2. 에디터 스타트업 맵의 경우 드롭다운 목록에서 StartupMap "“를 선택합니다. ... > Unreal Projects/[project-name]/Plugins/Amazon GameLift Plugin Content/Maps에 있는 파일을 검색해야 할 수도 있습니다.

  3. 게임 디폴트 맵의 경우 드롭다운 목록에서 동일한 StartupMap "“를 선택합니다.

  4. 서버 기본 맵의 경우 ThirdPersonMap "“를 선택합니다. 이는 게임 프로젝트에 포함된 기본 맵입니다. 이 맵은 게임에 참여하는 두 명의 플레이어를 위해 설계되었습니다.

  5. 서버 기본 맵의 세부 정보 패널을 엽니다. GameMode 오버라이드를 “없음”으로 설정합니다.

  6. 기본 모드 섹션을 확장하고 전역 기본 서버 게임 모드를 서버 통합을 위해 업데이트한 게임 모드로 설정합니다.

프로젝트를 이렇게 변경하고 나면 게임 컴포넌트를 빌드할 준비가 된 것입니다.

게임 구성 요소 구축

  1. 새 서버 및 클라이언트 대상 파일 생성

    1. 게임 프로젝트 폴더에서 소스 폴더로 이동하여 Target.cs 파일을 찾습니다.

    2. [project-name]Editor.Target.cs 파일을 이름이 [project-name]Client.Target.cs[project-name]Server.Target.cs인 새 파일 두 개에 복사합니다.

    3. 다음과 같이 각 새 파일을 편집하여 클래스 이름과 대상 유형 값을 업데이트합니다.

    UnrealProjects > MyGame > Source > MyGameClient.Target.cs // Copyright Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic; public class MyGameClientTarget : TargetRules { public MyGameClientTarget(TargetInfo Target) : base(Target) { Type = TargetType.Client; DefaultBuildSettings = BuildSettingsVersion.V2; IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_1; ExtraModuleNames.Add("MyGame"); } }
    UnrealProjects > MyGame > Source > MyGameServer.Target.cs // Copyright Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic; public class MyGameServerTarget : TargetRules { public MyGameServerTarget(TargetInfo Target) : base(Target) { Type = TargetType.Server; DefaultBuildSettings = BuildSettingsVersion.V2; IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_1; ExtraModuleNames.Add("MyGame"); } }
  2. .Build.cs 파일을 업데이트합니다.

    1. 프로젝트에 대한 .Build.cs 파일을 엽니다. 이 파일은 UnrealProjects/[project name]/Source/[project name]/[project name].Build.cs에 위치합니다.

    2. 다음 코드 샘플에 표시된 대로 ModuleRules 클래스를 업데이트합니다.

      public class MyGame : ModuleRules { public GameLiftUnrealApp(TargetInfo Target) { PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); bEnableExceptions = true; if (Target.Type == TargetRules.TargetType.Server) { PublicDependencyModuleNames.AddRange(new string[] { "GameLiftServerSDK" }); PublicDefinitions.Add("WITH_GAMELIFT=1"); } else { PublicDefinitions.Add("WITH_GAMELIFT=0"); } } }
  3. 게임 프로젝트 솔루션을 다시 빌드합니다.

  4. Unreal Engine 편집기의 소스 빌드 버전에서 게임 프로젝트를 엽니다.

  5. 클라이언트와 서버 모두에 대해 다음을 수행합니다.

    1. 대상을 선택합니다. 플랫폼, Windows로 이동하여 다음 중 하나를 선택합니다.

      • 서버: [your-application-name]Server

      • 클라이언트: [your-application-name]Client

    2. 빌드를 시작합니다. 플랫폼, Windows, 패키지 프로젝트로 이동합니다.

각 패키징 프로세스는 실행 파일([your-application-name]Client.exe 또는 [your-application-name]Server.exe)을 생성합니다.

플러그인에서 로컬 워크스테이션에 있는 클라이언트 및 서버 빌드 실행 파일의 경로를 설정합니다.

3단계: Anywhere 플릿에 연결

이 단계에서는 사용할 Anywhere 플릿을 지정합니다. Anywhere 플릿은 게임 서버 호스팅을 위해 어디에서나 배치할 수 있는 컴퓨팅 리소스 모음을 정의합니다.

  • 현재 사용 중인 AWS 계정에 기존 Anywhere 플릿이 있는 경우 플릿 이름 드롭다운 필드를 열고 플릿을 선택합니다. 이 드롭다운에는 현재 활성 사용자 프로필의 AWS 해당 지역 내 Anywhere 플릿만 표시됩니다.

  • 기존 플릿이 없거나 새 플릿을 생성하려는 경우 새 Anywhere 플릿 생성을 선택하고 플릿 이름을 입력합니다.

프로젝트에 대해 Anywhere 플릿을 선택하면 Amazon은 플릿 상태가 활성 상태인지 GameLift 확인하고 플릿 ID를 표시합니다. 언리얼 에디터의 출력 로그에서 이 요청의 진행 상황을 추적할 수 있습니다.

4단계: 워크스테이션 등록

이 단계에서는 로컬 워크스테이션을 새로운 Anywhere 플릿의 컴퓨팅 리소스로 등록합니다.

  1. 로컬 시스템의 컴퓨팅 이름을 입력합니다. 플릿에 두 개 이상의 컴퓨팅을 추가하는 경우 이름은 고유해야 합니다.

  2. 로컬 시스템의 IP 주소를 입력합니다. 이 필드의 기본값은 컴퓨터의 퍼블릭 IP 주소입니다. 게임 클라이언트와 서버를 같은 컴퓨터에서 실행하는 한 localhost (127.0.0.1) 를 사용할 수도 있습니다.

  3. 컴퓨팅 등록을 선택합니다. 언리얼 에디터의 출력 로그에서 이 요청의 진행 상황을 추적할 수 있습니다.

이 조치에 대한 응답으로 Amazon은 컴퓨팅에 연결할 수 GameLift 있는지 확인하고 새로 등록된 컴퓨팅에 대한 정보를 반환합니다. 또한 Amazon GameLift 서비스와의 통신을 초기화할 때 게임 실행 파일에 필요한 콘솔 인수를 생성합니다.

5단계: 인증 토큰 생성

Anywhere 컴퓨팅에서 실행되는 게임 서버 프로세스에는 서비스를 호출하기 위한 인증 토큰이 GameLift 필요합니다. 플러그인에서 게임 서버를 실행할 때마다 플러그인은 Anywhere 플릿에 대한 인증 토큰을 자동으로 생성하고 저장합니다. 인증 토큰 값은 명령줄 인수로 저장되며, 서버 코드를 런타임 시 검색할 수 있습니다.

이 단계에서는 어떤 조치도 취하지 않아도 됩니다.

6단계: 게임 실행

이제 GameLift Amazon을 사용하여 로컬 워크스테이션에서 멀티플레이어 게임을 시작하고 플레이하는 데 필요한 모든 작업을 완료했습니다.

  1. 게임 서버를 시작합니다. 게임 서버는 게임 세션을 호스팅할 준비가 GameLift 되면 Amazon에 알립니다.

  2. 게임 클라이언트를 시작하고 새 기능을 사용하여 새 게임 세션을 시작합니다. 이 요청은 새 백엔드 서비스를 GameLift 통해 Amazon으로 전송됩니다. 이에 대한 응답으로 GameLift Amazon은 로컬 시스템에서 실행되는 게임 서버를 호출하여 새 게임 세션을 시작합니다. 게임 세션이 플레이어를 받아들일 준비가 되면 GameLift Amazon은 게임 클라이언트가 게임 세션에 참여할 수 있도록 연결 정보를 제공합니다.