Implement Subsystem - Login Queue - (Unreal Engine module)
Unwrap the subsystem
Byte Wars uses the Game Instance Subsystem named ULoginQueueSubsystem_Starter
to act as the wrapper for the AccelByte's OSS. It will only use the FOnlineIdentityAccelByte
provided by AccelByte's OSS, derived from the Identity Interface.
The diagram below describes how the Login Queue feature works with the OSS.
What's in the Starter Pack
The starter class ULoginQueueSubsystem_Starter
for you to modify is provided for you in the Resources section and consists of:
- A Header file:
/Source/AccelByteWars/TutorialModules/Access/LoginQueue/LoginQueueSubsystem_Starter.h
- A CPP file:
/Source/AccelByteWars/TutorialModules/Access/LoginQueue/LoginQueueSubsystem_Starter.cpp
- A Model file:
/Source/AccelByteWars/TutorialModules/Access/LoginQueue/LoginQueueModel.h
The starter class includes the following functionalities:
Include the AccelByte's Online Identity Interface and a pointer to it in the Header file:
#include "OnlineIdentityInterfaceAccelByte.h"
protected:
FOnlineIdentityAccelBytePtr IdentityInterface;A code to get that Online Identity pointer in the CPP file:
void ULoginQueueSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
// Get Online Subsystem and make sure it's valid.
FOnlineSubsystemAccelByte* Subsystem = static_cast<FOnlineSubsystemAccelByte*>(Online::GetSubsystem(GetWorld()));
if (!ensure(Subsystem))
{
UE_LOG_LOGIN_QUEUE(Warning, TEXT("The online subsystem is invalid. Please make sure OnlineSubsystemAccelByte is enabled and DefaultPlatformService under [OnlineSubsystem] in the Engine.ini set to AccelByte."));
return;
}
// Grab the reference of AccelByte Identity Interface and make sure it's valid.
IdentityInterface = StaticCastSharedPtr<FOnlineIdentityAccelByte>(Subsystem->GetIdentityInterface());
if (!ensure(IdentityInterface.IsValid()))
{
UE_LOG_LOGIN_QUEUE(Warning, TEXT("Identity interface is not valid."));
return;
}
...
}Multicast delegates declaration to connect the game's logic to the Identity Interface's delegates in the Model file:
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnLoginQueueCancelCompleted, const APlayerController*, const FOnlineError&)
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnLoginQueued, const APlayerController*, const FAccelByteModelsLoginQueueTicketInfo& TicketInfo)
DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnLoginTicketStatusUpdated, const APlayerController*, const FAccelByteModelsLoginQueueTicketInfo&, const FOnlineError&)Helper functions to get local player number from Player Controller as vice versa. These functions were made to simplify the interaction between UI and Identity Interface. The game UI has a
GetOwningPlayer
function which will return the owning Player Controller while the Identity Interface accepts local player number.int32 ULoginQueueSubsystem_Starter::GetLocalUserNumFromPlayerController(const APlayerController* PlayerController) const
{
if (!PlayerController)
{
return INDEX_NONE;
}
const ULocalPlayer* LocalPlayer = PlayerController->GetLocalPlayer();
if (!LocalPlayer)
{
return INDEX_NONE;
}
return LocalPlayer->GetControllerId();
}
APlayerController* ULoginQueueSubsystem_Starter::GetPlayerControllerByLocalUserNum(const int32 LocalUserNum) const
{
APlayerController* MatchedPC = nullptr;
for (FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
{
if (!It->IsValid())
{
continue;
}
if (APlayerController* PC = It->Get(); PC)
{
if (LocalUserNum == GetLocalUserNumFromPlayerController(PC))
{
MatchedPC = PC;
break;
}
}
}
return MatchedPC;
}
Implement Login Queue callbacks
Open the
ULoginQueueSubsystem_Starter
Header file and add these two delegates. These delegates will be the connection point between the OSS's delegates and Byte Wars' UIs.public:
FOnLoginQueued OnLoginQueuedDelegates;
FOnLoginTicketStatusUpdated OnLoginTicketStatusUpdatedDelegates;Still in the Header file, add the following functions to be bound to the OSS's delegates.
protected:
void OnLoginQueued(const int32 PlayerNum, const FAccelByteModelsLoginQueueTicketInfo& TicketInfo) const;
void OnLoginTicketStatusUpdated(
const int32 PlayerNum,
bool bWasSuccessful,
const FAccelByteModelsLoginQueueTicketInfo& TicketInfo,
const FOnlineErrorAccelByte& Error) const;Next, open the
ULoginQueueSubsystem_Starter
CPP file and implement theOnLoginQueued
function. As the name suggest, this will be the callback when a player is queued after login attempt. Here, change thePlayerNum
received from the OSS's delegate toPlayerController
to simplify the code on the UI side and trigger theOnLoginQueuedDelegates
.void ULoginQueueSubsystem_Starter::OnLoginQueued(const int32 PlayerNum, const FAccelByteModelsLoginQueueTicketInfo& TicketInfo) const
{
OnLoginQueuedDelegates.Broadcast(GetPlayerControllerByLocalUserNum(PlayerNum), TicketInfo);
}Still in the CPP file, implement the
OnLoginTicketStatusUpdated
function. This function will be the callback when the polling response is received. Just like the previous function, this function will simply change thePlayerNum
toPlayerController
and trigger theOnLoginTicketStatusUpdatedDelegates
.void ULoginQueueSubsystem_Starter::OnLoginTicketStatusUpdated(
const int32 PlayerNum,
bool bWasSuccessful,
const FAccelByteModelsLoginQueueTicketInfo& TicketInfo,
const FOnlineErrorAccelByte& Error) const
{
// bWasSuccessful false means the player still in queue, not that the request has failed.
OnLoginTicketStatusUpdatedDelegates.Broadcast(GetPlayerControllerByLocalUserNum(PlayerNum), TicketInfo, Error);
}infoThe polling request itself is done by the OSS with the delay based on the
FAccelByteModelsLoginQueueTicketInfo::PlayerPollingTimeInSeconds
± up to 10 seconds from the received response.Still in the CPP file, bind the functions you just implemented to the OSS's delegate. Navigate to the
Initialize
function and add the highlighted code below.void ULoginQueueSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
...
if (!ensure(IdentityInterface.IsValid()))
{
UE_LOG_LOGIN_QUEUE(Warning, TEXT("Identity interface is not valid."));
return;
}
IdentityInterface->AccelByteOnLoginQueuedDelegates->AddUObject(this, &ThisClass::OnLoginQueued);
IdentityInterface->AccelByteOnLoginTicketStatusUpdatedDelegates->AddUObject(this, &ThisClass::OnLoginTicketStatusUpdated);
}Unbind the delegates in the
Deinitialize
function.void ULoginQueueSubsystem_Starter::Deinitialize()
{
Super::Deinitialize();
IdentityInterface->AccelByteOnLoginQueuedDelegates->RemoveAll(this);
IdentityInterface->AccelByteOnLoginTicketStatusUpdatedDelegates->RemoveAll(this);
}
Implement Cancel Login
Open the
ULoginQueueSubsystem_Starter
Header file, add a function to trigger the cancel function and a delegate as a callback for the response.public:
void CancelLoginQueue(const APlayerController* PlayerController) const;
FOnLoginQueueCancelCompleted OnLoginQueueCancelCompletedDelegates;Still in the Header file, declare the following function as the callback that will be bound to the OSS delegate.
protected:
void OnLoginQueueCancelCompleted(const int32 PlayerNum, bool bWasSuccessful, const FOnlineErrorAccelByte& Error) const;Open the
ULoginQueueSubsystem_Starter
CPP file and implement theCancelLoginQueue
function. Convert thePlayerController
parameter to the associate local player index and call the cancel login functionality.void ULoginQueueSubsystem_Starter::CancelLoginQueue(const APlayerController* PlayerController) const
{
IdentityInterface->CancelLoginQueue(GetLocalUserNumFromPlayerController(PlayerController));
}Still in the CPP file, implement the callback function,
OnLoginQueueCancelCompleted
. This function will trigger theOnLoginQueueCancelCompletedDelegates
that you created during the Implement Cancel Login step. Note that, in addition to this,OnLoginComplete
will also be triggered right after this callback is triggered.void ULoginQueueSubsystem_Starter::OnLoginQueueCancelCompleted(
const int32 PlayerNum,
bool bWasSuccessful,
const FOnlineErrorAccelByte& Error) const
{
// OnLoginComplete will also be triggered after this is triggered.
FOnlineError OutError = Error;
OutError.bSucceeded = bWasSuccessful;
OnLoginQueueCancelCompletedDelegates.Broadcast(GetPlayerControllerByLocalUserNum(PlayerNum), OutError);
}Now that you've implemented the function, you need to bind the callback to the OSS's delegate. Still in the CPP file, navigate to the
Initialize
function and add the highlighted code below.void ULoginQueueSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
...
IdentityInterface->AccelByteOnLoginTicketStatusUpdatedDelegates->AddUObject(this, &ThisClass::OnLoginTicketStatusUpdated);
IdentityInterface->AccelByteOnLoginQueueCancelCompleteDelegates->AddUObject(this, &ThisClass::OnLoginQueueCancelCompleted);
}Lastly, unbind the previous delegate in the
Deinitialize
function.void ULoginQueueSubsystem_Starter::Deinitialize()
{
...
IdentityInterface->AccelByteOnLoginTicketStatusUpdatedDelegates->RemoveAll(this);
IdentityInterface->AccelByteOnLoginQueueCancelCompleteDelegates->RemoveAll(this);
}Build the AccelByteWars project and make sure there is no compile errors.
Resources
- The files used in this tutorial section are available in the Byte Wars Unreal GitHub repository.