Skip to main content

Implement Subsystem - Login Queue - (Unreal Engine module)

Last updated on June 10, 2024

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

  1. 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;
  2. 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;
  3. Next, open the ULoginQueueSubsystem_Starter CPP file and implement the OnLoginQueued function. As the name suggest, this will be the callback when a player is queued after login attempt. Here, change the PlayerNum received from the OSS's delegate to PlayerController to simplify the code on the UI side and trigger the OnLoginQueuedDelegates.

    void ULoginQueueSubsystem_Starter::OnLoginQueued(const int32 PlayerNum, const FAccelByteModelsLoginQueueTicketInfo& TicketInfo) const
    {
    OnLoginQueuedDelegates.Broadcast(GetPlayerControllerByLocalUserNum(PlayerNum), TicketInfo);
    }
  4. 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 the PlayerNum to PlayerController and trigger the OnLoginTicketStatusUpdatedDelegates.

    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);
    }
    info

    The polling request itself is done by the OSS with the delay based on the FAccelByteModelsLoginQueueTicketInfo::PlayerPollingTimeInSeconds ± up to 10 seconds from the received response.

  5. 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);
    }
  6. Unbind the delegates in the Deinitialize function.

    void ULoginQueueSubsystem_Starter::Deinitialize()
    {
    Super::Deinitialize();

    IdentityInterface->AccelByteOnLoginQueuedDelegates->RemoveAll(this);
    IdentityInterface->AccelByteOnLoginTicketStatusUpdatedDelegates->RemoveAll(this);
    }

Implement Cancel Login

  1. 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;
  2. 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;
  3. Open the ULoginQueueSubsystem_Starter CPP file and implement the CancelLoginQueue function. Convert the PlayerController 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));
    }
  4. Still in the CPP file, implement the callback function, OnLoginQueueCancelCompleted. This function will trigger the OnLoginQueueCancelCompletedDelegates 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);
    }
  5. 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);
    }
  6. Lastly, unbind the previous delegate in the Deinitialize function.

    void ULoginQueueSubsystem_Starter::Deinitialize()
    {
    ...
    IdentityInterface->AccelByteOnLoginTicketStatusUpdatedDelegates->RemoveAll(this);
    IdentityInterface->AccelByteOnLoginQueueCancelCompleteDelegates->RemoveAll(this);
    }
  7. Build the AccelByteWars project and make sure there is no compile errors.

Resources