Skip to main content

Implement Lobby with Unreal Engine SDK

Last updated on October 29, 2024

Introduction

AGS Shared Cloud

This topic is specific to the AccelByte Gaming Services (AGS) Shared Cloud tier.

AccelByte Gaming Services (AGS) Lobby provides a continuous connection between your game and its players by using WebSockets. This article will show you how to implement Lobby into your Unreal Engine game using the AGS Game SDK for the AGS Shared Cloud tier.

Prerequisites

To complete the steps in this article, you need to:

  1. Implement AGS Authentication into your game, ensuring it works properly.

  2. Set up your own Lobby Configurations in the AGS Admin Portal.

Quick Reference

References
#include "OnlineIdentityInterfaceAccelByte.h"
Lobby connect notification events
IdentityInterface->AddAccelByteOnConnectLobbyCompleteDelegate_Handle(USER_LOCAL_NUM
, FAccelByteOnConnectLobbyCompleteDelegate::CreateLambda([](int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FOnlineErrorAccelByte& Error)
{
// Lobby connection attempt finished. Check bWasSuccessful for result.
}));

IdentityInterface->AddAccelByteOnLobbyConnectionClosedDelegate_Handle(USER_LOCAL_NUM
, FAccelByteOnLobbyConnectionClosedDelegate::CreateLambda([](int32 LocalUserNum, const FUniqueNetId& UserId, int32 StatusCode, const FString& Reason, bool bWasClean)
{
// Lobby connection closed
}));

IdentityInterface->AddAccelByteOnLobbyReconnectingDelegate_Handle(USER_LOCAL_NUM
, FAccelByteOnLobbyReconnectingDelegate::CreateLambda([](int32 LocalUserNum, const FUniqueNetId& UserId, int32 StatusCode, const FString& Reason, bool bWasClean)
{
// Lobby is reconnecting
}));

IdentityInterface->AddAccelByteOnLobbyReconnectedDelegate_Handle(0
, FAccelByteOnLobbyReconnectedDelegate::CreateLambda([]()
{
// Lobby Reconnected
}));
Lobby connect
IdentityInterface->AddAccelByteOnConnectLobbyCompleteDelegate_Handle(USER_LOCAL_NUM
, FAccelByteOnConnectLobbyCompleteDelegate::CreateLambda([](int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FOnlineErrorAccelByte& Error)
{
// Lobby connection attempt finished. Check bWasSuccessful for result.
}));

IdentityInterface->ConnectAccelByteLobby(USER_LOCAL_NUM);
Lobby disconnect
IdentityInterface->AddAccelByteOnLobbyConnectionClosedDelegate_Handle(USER_LOCAL_NUM
, FAccelByteOnLobbyConnectionClosedDelegate::CreateLambda([](int32 LocalUserNum, const FUniqueNetId& UserId, int32 StatusCode, const FString& Reason, bool bWasClean)
{
// Lobby connection closed
}));
IdentityInterface->GetApiClient(USER_LOCAL_NUM)->Lobby.Disconnect();
Check if Lobby is connected
IdentityInterface->GetApiClient(USER_LOCAL_NUM)->Lobby.IsConnected();

AGS Online Subsystem Configurations for Lobby

Below are the available configurations that can be enabled in your project's DefaultEngine.ini file.

SectionConfiguration KeyValue TypeDetails
[OnlineSubsystemAccelByte]bAutoLobbyConnectAfterLoginSuccessbooleanSet to true to automatically connect to Lobby after user has logged in to AGS
[Core.Log]LogAccelByteLobbyLogVerbositySet to VeryVerbose for debugging
[Core.Log]LogWebSocketsLogVerbositySet to VeryVerbose for debugging

Quick start

In this section, you will learn how to use AGS Lobby for the AGS Shared Cloud tier.

  1. Create a user widget C++ class called AccelByteLobby to specify the flow between functions when creating a Lobby menu.

  2. Add the AccelByte header to ensure the functions work correctly for AGS Lobby:

    . . .  
    #include "OnlineIdentityInterfaceAccelByte.h"
    . . .
  3. Create a new function called SetLobbyNotificationDelegate() to notify the player when the Lobby connection is updated. Once completed, put all the lobby-related delegates inside it. These delegates include:

    DelegatesResponse
    AddAccelByteOnConnectLobbyCompleteDelegate_HandleLobby connect attemp completed
    AddAccelByteOnLobbyConnectionClosedDelegate_HandleLobby disconnected
    AddAccelByteOnLobbyReconnectingDelegate_HandleLobby reconnecting
    note

    Although you make reference to AGS Lobby, it is not yet connected. For now, you will just add debug logs to notify you of any updates after calling the references.

    After you have added the delegates, the function should look like this:

      void UAccelByteLobby::SetLobbyNotificationDelegate()
    {
    IdentityInterface->AddAccelByteOnConnectLobbyCompleteDelegate_Handle(USER_LOCAL_NUM
    , FAccelByteOnConnectLobbyCompleteDelegate::CreateLambda([](int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FOnlineErrorAccelByte& Error)
    {
    if (bWasSuccessful)
    {
    UE_LOG(LogTemp, Log, TEXT("Successfully Connected to Lobby"));
    }
    else
    {
    UE_LOG(LogTemp, Error, TEXT("Failed Connect to Lobby. Code: %s, Reason: %s"), *Error.ErrorCode, *Error.ErrorMessage.ToString());
    }
    }));

    IdentityInterface->AddAccelByteOnLobbyConnectionClosedDelegate_Handle(USER_LOCAL_NUM
    , FAccelByteOnLobbyConnectionClosedDelegate::CreateLambda([](int32 LocalUserNum, const FUniqueNetId& UserId, int32 StatusCode, const FString& Reason, bool bWasClean)
    {
    UE_LOG(LogTemp, Error, TEXT("Lobby disconnected. Code: %d, Message: %s"), StatusCode, *Reason);
    }));

    IdentityInterface->AddAccelByteOnLobbyReconnectingDelegate_Handle(USER_LOCAL_NUM
    , FAccelByteOnLobbyReconnectingDelegate::CreateLambda([](int32 LocalUserNum, const FUniqueNetId& UserId, int32 StatusCode, const FString& Reason, bool bWasClean)
    {
    UE_LOG(LogTemp, Error, TEXT("Lobby is reconnecting"));
    }));

    IdentityInterface->AddAccelByteOnLobbyReconnectedDelegate_Handle(0
    , FAccelByteOnLobbyReconnectedDelegate::CreateLambda([]()
    {
    UE_LOG(LogTemp, Error, TEXT("Lobby reconnected"));
    }));
    }
  4. Create a new function called ConnectToLobby(). This will be triggered after login is successful and will open a WebSocket that sets delegates to receive responses after requesting a connection to Lobby by calling SetLobbyNotificationDelegate().

    void UAccelByteLobby::ConnectToLobby()
    {
    SetLobbyNotificationDelegate();
    IdentityInterface->ConnectAccelByteLobby(USER_LOCAL_NUM);
    }
  5. Test your code and ensure there are no errors.

  6. After successfully connecting to AGS Lobby, you can move to AccelByteAuth.cpp and add a call to the IdentityInterface->ConnectAccelByteLobby() function on the success delegate from LoginWithUsername(). You can find LoginWithUsername() inside OnLoginButtonClicked().

  7. Test the function by triggering OnLoginButtonClicked().

    void UAccelByteAuth::OnLoginButtonClicked()
    {
    ...
    FVoidHandler::CreateWeakLambda(this, []()
    {
    UE_LOG(LogTemp, Log, TEXT("Login success"));

    IdentityInterface->ConnectAccelByteLobby(USER_LOCAL_NUM);
    if (IdentityInterface->GetApiClient(USER_LOCAL_NUM)->Lobby.IsConnected())
    {
    UE_LOG(LogTemp, Log, TEXT("Lobby is connected!"));
    }
    else
    {
    UE_LOG(LogTemp, Log, TEXT("Lobby is not connected!"));
    }
    }),
    ...
    }

    If you have configured your code correctly, you will see the message Lobby is connected! after logging in.

    note

    If Lobby is not connected successfully, check your game project configuration and make sure the user is logged in. As long as Lobby is connected, you can use all the features available in AGS Lobby.

  8. OPTIONAL: By default, if you quit your application, you are automatically disconnected from Lobby. To manually disconnect without closing the application, use the OnLogoutButtonClicked() function in AccelByteAuth.cpp.

    void UAccelByteAuth::OnLogoutButtonClicked()
    {
    ...
    IdentityInterface->GetApiClient(USER_LOCAL_NUM)->Lobby.Disconnect()
    ...
    }

Congratulations! You have successfully connected to Lobby.

Continue with this article for a step-by-step guide for UI and code implementation. Otherwise, proceed to AGS Friends implementation for AGS Shared Cloud tier.

Step-by-step guide

Follow these step-by-step instructions to implement AGS Lobby for AGS Shared Cloud tier.

Implement the user interface

note

The following steps are optional, as AGS Lobby can run without any widgets implemented.

In this section, you will implement the heads-up display (HUD) to get the Lobby class reference. This means you need to create a Lobby Menu widget, but for now, you can leave it empty.

  1. Create an empty widget blueprint class called WB_LobbyMenu.

  2. Assign AccelByteLobby as the parent class of the WB_LobbyMenu.

Implement the code

In this section, you will change between the Login and Lobby widgets using a HUD.

TutorialMenuHUD

  1. Create a HUD Blueprint class called BP_TutorialHUD.

  2. Create a HUD C++ class called TutorialMenuHUD and add the class as a parent class for BP_TutorialHUD.

  3. Open the TutorialMenuHUD class and add the following headers at the top of the class:

    ...
    #include "AccelByte/Authentication/AccelByteAuth.h"
    #include "OnlineIdentityInterfaceAccelByte.h"
  4. Add the BeginPlay() function to check and set all of your class pointers.

    void ATutorialMenuHUD::BeginPlay()
    {
    Super::BeginPlay();

    APlayerController* PlayerController = GetOwningPlayerController();

    check(LoginMenuClass != nullptr);
    check(LobbyMenuClass != nullptr);

    LoginMenu = CreateWidget<UAccelByteAuth>(PlayerController, LoginMenuClass.Get());
    LobbyMenu = CreateWidget<UAccelByteLobby>(PlayerController, LobbyMenuClass.Get());
    }
  5. Add the Login and Lobby's C++ class in the BP_TutorialHUD under the pointer variables you declared in the BeginPlay() function.

    note

    You will need to repeat Step 5 in order to add additional UI-related user widgets for in-game stores, inventories, leaderboards, etc.

  6. Add const functions that will act as getters for the widgets.

    /**
    * @brief Getter for Login Menu widget
    */
    UAccelByteAuth* GetLoginMenu() const {return LoginMenu; }

    /**
    * @brief Getter for Lobby Menu widget
    */
    UAccelByteLobby* GetLobbyMenu() const {return LobbyMenu; }
  7. Add some functions that will be called to change to the desired menu page. Since you only have the Login and Lobby widgets, create two new functions to open these widgets:

    void ATutorialMenuHUD::OpenLoginMenu()
    {
    LoginMenu->AddToViewport();
    }

    void ATutorialMenuHUD::OpenLobbyMenu()
    {
    LobbyMenu->SetVisibility(ESlateVisibility::Visible);
    }
  8. Add another function that will be used to destroy the widgets when you close the page.

     void ATutorialMenuHUD::CloseLobbyMenu()
    {
    LobbyMenu->SetVisibility(ESlateVisibility::Collapsed);
    }
  9. Inside the BeginPlay() function, check the current session's userId. If it is empty, call the OpenLoginMenu() function.

    void ATutorialMenuHUD::BeginPlay()
    {
    ...
    if (IdentityInterface->GetApiClient(USER_LOCAL_NUM)->CredentialsRef->GetUserId().IsEmpty())
    {
    OpenLoginMenu();
    }
    }
  1. Open the AccelByteAuth class and header file and add the TutorialMenuHUD class.

    ...
    #include "TutorialProject/TutorialMenuHUD.h"
  2. In AccelByteAuth.cpp, define the TutorialMenuHUD's pointer in the NativeConstruct() function.

    void UAccelByteAuth::NativeConstruct()
    {
    ...
    TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetWorld()->GetFirstPlayerController()->GetHUD());
    ...
    }
  3. Inside the LoginSuccess() function, call the ConnectToLobby() function with the Lobby getter.

    void UAccelByteAuth::LoginSuccess()
    {
    ...
    if(GetOwningPlayer() && GetOwningPlayer()->GetHUD())
    {
    TutorialMenuHUD->GetLobbyMenu()->ConnectToLobby();
    this->RemoveFromParent();
    }
    }
  4. Add the Lobby Disconnect function inside the OnLogoutButtonClicked() function in AccelByteAuth.cpp.

    void UAccelByteAuth::OnLogoutButtonClicked()
    {
    ...

    if (IdentityInterface->GetApiClient(USER_LOCAL_NUM)->Lobby.IsConnected())
    {
    IdentityInterface->GetApiClient(USER_LOCAL_NUM)->Lobby.Disconnect()
    }
    }

Congratulations! You have now fully implemented AGS Lobby, which is the gateway to all other services in AGS.

You are ready to move on to implementing AGS Friends for AGS Shared Cloud.

Full Code for reference

AccelByteLobby.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "AccelByteLobby.generated.h"

/**
* Component for Join to AccelByte Gaming Services (AGS) Lobby.
* This code covers AGS including :
*
* - Join Lobby
* - Leave Lobby
*/
UCLASS()
class TUTORIALPROJECT_API UAccelByteLobby : public UUserWidget
{
GENERATED_BODY()

#pragma region Utilities

public:

/**
* @brief Connect to AccelByte lobby.
*/
UFUNCTION()
void ConnectToLobby();

/**
* @brief Set Lobby services notification delegates.
*/
void SetLobbyNotificationDelegate();

#pragma endregion
};
AccelByteLobby.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "OnlineIdentityInterfaceAccelByte.h"

#pragma region Utilities

void UAccelByteLobby::ConnectToLobby()
{
SetLobbyNotificationDelegate();
IdentityInterface->ConnectAccelByteLobby(USER_LOCAL_NUM);;
}

void UAccelByteLobby::SetLobbyNotificationDelegate()
{
IdentityInterface->AddAccelByteOnConnectLobbyCompleteDelegate_Handle(USER_LOCAL_NUM
, FAccelByteOnConnectLobbyCompleteDelegate::CreateLambda([](int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FOnlineErrorAccelByte& Error)
{
if (bWasSuccessful)
{
UE_LOG(LogTemp, Log, TEXT("Successfully connected to Lobby."));
}
else
{
UE_LOG(LogTemp, Error, TEXT("Failed to connect to Lobby. Code: %s, Reason: %s"), *Error.ErrorCode, *Error.ErrorMessage.ToString());
}
}));

IdentityInterface->AddAccelByteOnLobbyConnectionClosedDelegate_Handle(USER_LOCAL_NUM
, FAccelByteOnLobbyConnectionClosedDelegate::CreateLambda([](int32 LocalUserNum, const FUniqueNetId& UserId, int32 StatusCode, const FString& Reason, bool bWasClean)
{
UE_LOG(LogTemp, Error, TEXT("Lobby disconnected. Code: %d, Message: %s"), StatusCode, *Reason);
}));

IdentityInterface->AddAccelByteOnLobbyReconnectingDelegate_Handle(USER_LOCAL_NUM
, FAccelByteOnLobbyReconnectingDelegate::CreateLambda([](int32 LocalUserNum, const FUniqueNetId& UserId, int32 StatusCode, const FString& Reason, bool bWasClean)
{
UE_LOG(LogTemp, Error, TEXT("Lobby is reconnecting..."));
}));

IdentityInterface->AddAccelByteOnLobbyReconnectedDelegate_Handle(0
, FAccelByteOnLobbyReconnectedDelegate::CreateLambda([]()
{
UE_LOG(LogTemp, Error, TEXT("Lobby reconnected"));
}));
}

#pragma endregion
TutorialMenuHUD.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "TutorialMenuHUD.generated.h"

class UAccelByteAuth;
class UAccelByteLobby;

/**
* Menu Widget Controller. All Widget functionality control here.
*/
UCLASS()
class TUTORIALPROJECT_API ATutorialMenuHUD : public AHUD
{
GENERATED_BODY()

protected:

virtual void BeginPlay() override;

public:

/**
* @brief Shows the Login Menu on screen
*/
void OpenLoginMenu();

/**
* @brief Shows the Lobby Menu, which adds the Party Menu in Sb_Party and destroys Main Menu
*/
UFUNCTION()
void OpenLobbyMenu();

/**
* @brief Destroys the Lobby Menu widget and shows the Main Menu
*/
UFUNCTION()
void CloseLobbyMenu();

protected:

/**
* @brief Login Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteAuth> LoginMenuClass;

/**
* @brief Lobby Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteLobby> LobbyMenuClass;

public:

/**
* @brief Getter for Login Menu widget
*/
UAccelByteAuth* GetLoginMenu() const {return LoginMenu; }

/**
* @brief Getter for Lobby Menu widget
*/
UAccelByteLobby* GetLobbyMenu() const {return LobbyMenu; }

private:

/**
* @brief Login Menu widget pointer
*/
UPROPERTY()
UAccelByteAuth* LoginMenu;

/**
* @brief Lobby Menu widget pointer
*/
UPROPERTY()
UAccelByteLobby* LobbyMenu;
};
TutorialMenuHUD.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "TutorialMenuHUD.h"
#include "AccelByte/Authentication/AccelByteAuth.h"
#include "OnlineIdentityInterfaceAccelByte.h"

void ATutorialMenuHUD::BeginPlay()
{
Super::BeginPlay();

APlayerController* PlayerController = GetOwningPlayerController();

check(LoginMenuClass != nullptr);
check(LobbyMenuClass != nullptr);

LoginMenu = CreateWidget<UAccelByteAuth>(PlayerController, LoginMenuClass.Get());
LobbyMenu = CreateWidget<UAccelByteLobby>(PlayerController, LobbyMenuClass.Get());

if (IdentityInterface->GetApiClient(USER_LOCAL_NUM)->CredentialsRef->GetUserId().IsEmpty())
{
OpenLoginMenu();
}
}

void ATutorialMenuHUD::OpenLoginMenu()
{
LoginMenu->AddToViewport();
}

void ATutorialMenuHUD::OpenLobbyMenu()
{
LobbyMenu->SetVisibility(ESlateVisibility::Visible);
}

void ATutorialMenuHUD::CloseLobbyMenu()
{
LobbyMenu->SetVisibility(ESlateVisibility::Collapsed);
}
AccelByteAuth.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "AccelByteAuth.generated.h"

class UVerticalBox;
class UEditableTextBox;
class UButton;
class ATutorialMenuHUD;
class UTextBlock;

/**
* Component for logging in a user with the AGS backend, as well as methods for grabbing information relating to that user.
* This code covers AGS features including:
*
* - Login with username
*/

UCLASS()
class TUTORIALPROJECT_API UAccelByteAuth : public UUserWidget
{
GENERATED_BODY()

protected:

virtual void NativeConstruct() override;

/**
* @brief Editable Text Box for Usernames inside the MainMenu Widget.
*/
UPROPERTY(meta = (BindWidget))
UEditableTextBox* Etb_Username;

/**
* @brief Editable Text Box for Passwords inside the MainMenu Widget.
*/
UPROPERTY(meta = (BindWidget))
UEditableTextBox* Etb_Password;

/**
* @brief Take Button Login inside MainMenu Widget.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Login;

/**
* @brief Text Block to display Login Status
*/
UPROPERTY(meta = (BindWidget))
UTextBlock* T_LoginStatus;

/**
* @brief Instantiate all casting to the main menu HUD
*/
UPROPERTY()
ATutorialMenuHUD* TutorialMenuHUD;

/**
* @brief Delegate on login complete
*/
FDelegateHandle OnLoginCompleteDelegateHandle;

/**
* @brief Delegate on logout complete
*/
FDelegateHandle OnLogoutCompleteDelegateHandle;

public:

/**
* @brief Log in an account using the AGS Game SDK. This is executed automatically on component construction unless
* otherwise configured.
*/
UFUNCTION()
void OnLoginButtonClicked();

/**
* @brief Logout a session using the AGS Game SDK. This is executed automatically on component construction unless
* otherwise configured.
*/
UFUNCTION()
void OnLogoutButtonClicked();

private:

/**
* @brief Function behavior when login is successful. This function, which is called from inside AccelByte Login OnSuccess,
* delegates inside lambda. How the login behavior works inside this function can be changed.
*/
void LoginSuccess();

/**
* @brief Function behavior when login fails. This function, which is called from inside AccelByte Login OnFailed,
* delegates inside lambda. How the login behavior works inside this function can be changed.
*
* @param ErrorMessage error message HTTP request. e.g., Unauthorized.
* @param ErrorMessage error message HTTP request. e.g., Unauthorized.
* @param ErrorJson error message for OAuth
* @param ErrorMessage error message HTTP request. e.g., Unauthorized.
* @param ErrorJson error message for OAuth
*/
void LoginFailed(const FString& ErrorMessage);

/**
* @brief Function behavior when logout is successful. This function, which is called from inside AccelByte Logout OnSuccess,
* delegates inside lambda. How the login behavior works inside this function can be changed.
*/
void LogoutSuccess();

/**
* @brief Function behavior when logout fails. This function, which is called from inside AccelByte Logout OnFailed,
* delegates inside lambda. How the login behavior works inside this function can be changed.
*/
void LogoutFailed();
};
AccelByteAuth.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelByteAuth.h"

#include "OnlineIdentityInterfaceAccelByte.h"
#include "OnlineSubsystemUtils.h"
#include "Components/Button.h"
#include "Components/EditableTextBox.h"
#include "Components/TextBlock.h"

#define USER_LOCAL_NUM 0

void UAccelByteAuth::NativeConstruct()
{
Super::NativeConstruct();

UE_LOG(LogTemp, Log, TEXT("Log in with username"));

T_LoginStatus->SetText(FText::FromString("Please log in"));
Btn_Login->OnClicked.AddUniqueDynamic(this, &UAccelByteAuth::OnLoginButtonClicked);
}

void UAccelByteAuth::OnLoginButtonClicked()
{
T_LoginStatus->SetText(FText::FromString("Logging in..."));

const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
ensure(Subsystem != nullptr);

const FOnlineIdentityAccelBytePtr IdentityInterface = StaticCastSharedPtr<FOnlineIdentityAccelByte>(Subsystem->GetIdentityInterface());
ensure(IdentityInterface.IsValid());

OnLoginCompleteDelegateHandle = IdentityInterface->AddOnLoginCompleteDelegate_Handle(USER_LOCAL_NUM, FOnLoginCompleteDelegate::CreateLambda([this, IdentityInterface]
(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error)
{
IdentityInterface->ClearOnLoginCompleteDelegate_Handle(USER_LOCAL_NUM, OnLoginCompleteDelegateHandle);

if (bWasSuccessful)
{
LoginSuccess();
}
else
{
LoginFailed(Error);
}
}));

const FString& Username = Etb_Username->GetText().ToString();
const FString& Password = Etb_Password->GetText().ToString();
const FOnlineAccountCredentialsAccelByte OnlineAccountCredentialsAccelByte
{EAccelByteLoginType::AccelByte, Username, Password};

IdentityInterface->Login(USER_LOCAL_NUM, OnlineAccountCredentialsAccelByte);
}

void UAccelByteAuth::OnLogoutButtonClicked()
{
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
ensure(Subsystem != nullptr);

const FOnlineIdentityAccelBytePtr IdentityInterface = StaticCastSharedPtr<FOnlineIdentityAccelByte>(Subsystem->GetIdentityInterface());
ensure(IdentityInterface.IsValid());

OnLogoutCompleteDelegateHandle = IdentityInterface->AddOnLogoutCompleteDelegate_Handle(USER_LOCAL_NUM, FOnLogoutCompleteDelegate::CreateLambda([this, IdentityInterface](int32 LocalUserNum, bool bWasSuccessful)
{
IdentityInterface->ClearOnLogoutCompleteDelegate_Handle(USER_LOCAL_NUM, OnLogoutCompleteDelegateHandle);

if (bWasSuccessful)
{
LogoutSuccess();
}
else
{
LogoutFailed();
}
}));

IdentityInterface->Logout(USER_LOCAL_NUM);

if (IdentityInterface->GetApiClient(USER_LOCAL_NUM)->Lobby.IsConnected())
{
IdentityInterface->GetApiClient(USER_LOCAL_NUM)->Lobby.Disconnect()
}
}

void UAccelByteAuth::LoginSuccess()
{
UE_LOG(LogTemp, Log, TEXT("Login success"));
T_LoginStatus->SetText(FText::FromString("Login successful"));
}

void UAccelByteAuth::LoginFailed(const FString& ErrorMessage)
{
UE_LOG(LogTemp, Error, TEXT("Login failed : %s"), *ErrorMessage);
T_LoginStatus->SetText(FText::FromString(FString::Printf(TEXT("Login failed : %s"), *ErrorMessage)));
}

void UAccelByteAuth::LogoutSuccess()
{
UE_LOG(LogTemp, Log, TEXT("Logout success"));
}

void UAccelByteAuth::LogoutFailed()
{
UE_LOG(LogTemp, Error, TEXT("Logout failed"));
}