メインコンテンツまでスキップ

Implement Subsystem - Login Queue - (Unreal Engine module)

Last updated on March 24, 2025

Unwrap the subsystem

Byte Wars uses the Game Instance Subsystem named LoginQueueSubsystem_Starter to act as the wrapper for the AccelByte Gaming Services (AGS) OSS. It will only use the FOnlineIdentityAccelByte provided by the AGS OSS, derived from the Identity Interface.

The AGS OSS's login queue AsyncTask does not enable timeout. The reason is that the login queue wait time might exceed the AsyncTask's timeout duration. Thus, the operation will wait until the player tries to log in or if the player cancels the login queue.

The diagram below describes how the Login Queue feature works with the OSS.

What's in the Starter Pack

The starter class LoginQueueSubsystem_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 the DefaultPlatformService under [OnlineSubsystem] in the Engine.ini file is 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 LoginQueueSubsystem_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 LoginQueueSubsystem_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 the queue, not that the request has failed.
    OnLoginTicketStatusUpdatedDelegates.Broadcast(GetPlayerControllerByLocalUserNum(PlayerNum), TicketInfo, Error);
    }
    備考

    The polling request itself is done by the OSS with the delay based on the Polling Time configuration in the Admin Portal (Login Queue Configuration). If somehow this is set to 0, OSS will use the EstimatedWaitingTimeInSeconds that comes from the backend + random number between -10 to 10 seconds, which is capped to minimum 3 seconds and maximum 40 seconds.

  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 code below.

    void ULoginQueueSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    // ...
    IdentityInterface->AccelByteOnLoginQueuedDelegates->AddUObject(this, &ThisClass::OnLoginQueued);
    IdentityInterface->AccelByteOnLoginTicketStatusUpdatedDelegates->AddUObject(this, &ThisClass::OnLoginTicketStatusUpdated);
    }
  6. Unbind the delegates in the Deinitialize() function.

    void ULoginQueueSubsystem_Starter::Deinitialize()
    {
    // ...
    IdentityInterface->AccelByteOnLoginQueuedDelegates->RemoveAll(this);
    IdentityInterface->AccelByteOnLoginTicketStatusUpdatedDelegates->RemoveAll(this);
    }

Implement Cancel Login

  1. Open the LoginQueueSubsystem_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 LoginQueueSubsystem_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 code below.

    void ULoginQueueSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    // ...
    IdentityInterface->AccelByteOnLoginQueueCancelCompleteDelegates->AddUObject(this, &ThisClass::OnLoginQueueCancelCompleted);
    // ...
    }
  6. Lastly, unbind the previous delegate in the Deinitialize() function.

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

Resources