Skip to main content

AGS OSS for Unreal Engine

Last updated on March 28, 2024

Overview

The AccelByte Gaming Services (AGS) Online Subsystem (OSS) is the high-level bridge between Unreal Engine (UE) and AGS that comprises interfaces that access AGS and its features.

The AGS OSS is designed to handle higher-level logic with asynchronous communication and delegates. It's also designed to be modular by grouping similar service-specific APIs that support features together.

This enables:

  • Game developers to use the features provided by the Online Services provider and combine them with first-party platform services.
  • Service developers to develop features by creating implementation that uses multiple APIs together.

Using AGS OSS compared to the Game SDK

The AGS OSS and Game SDK have some overlapping functions, but have very distinct characteristics:

  • The OSS is a higher-level API layer used to integrate AGS to games where complex flows are handled by the OSS. The OSS cam be used out of the box and doesn't need to implement the flow again.
  • The Game SDK is a low-level API layer only used to call backend APIs like HTTP and Websocket, and only has small logic inside it. When used directly, the SDK needs wrappers on top of the SDK layer.

They can be differentiated further by the following:

OSS

  • Pros

    • Provides an out-of-the-box solution
    • Wraps game console functionalities
    • Provides the ability to access the SDK layer by using ApiClient
  • Cons

    • Has higher footprint/overhead
    • Does not have Blueprints support

SDK

  • Pros

    • Has smaller footprint/overhead
    • Has Blueprints support
  • Cons

    • Requires wrappers to implement complex features
    • Requires additional code and plugins to implement game console functionalities

Prerequisites

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

  • Download the AGS Unreal Engine SDK that comprises APIs for the game client and game server to send requests to AGS. See the following table for information about the UE versions supported by AGS OSS:

    AGS OSS versionUE 4.26UE 4.27UE 5.0UE 5.1UE 5.2
    Versions up to 0.5.9
    Version 0.6.0 up to 0.7.9
    Version from 0.8.0 up to 0.11.4
    Version from 0.11.5 and later
  • Download the AGS Network Utilities library that serves as a network functionalities library to communicate between game clients for peer-to-peer (P2P) networking.

Configuration

  1. Add the following required plugins to the .uproject file:

    "Plugins": [
    {
    "Name": "AccelByteUe4Sdk",
    "Enabled": true
    },
    {
    "Name": "OnlineSubsystemAccelByte",
    "Enabled": true
    },
    {
    "Name": "AccelByteNetworkUtilities",
    "Enabled": true
    },
    ...
    ]
  2. Add public dependency modules to the Build.cs file:

    PublicDependencyModuleNames.AddRange(new string[] { "AccelByteUe4Sdk"
    , "AccelByteNetworkUtilities"
    , "OnlineSubsystemAccelByte"
    });
  3. Add the following module to the Target.cs file:

    ExtraModuleNames.AddRange( new string[] { "AccelByteUe4Sdk"
    , "OnlineSubsystemAccelByte"
    , "AccelByteNetworkUtilities"
    });
  4. Edit the DefaultEngine.ini file to include the following settings:

    [OnlineSubsystem]
    DefaultPlatformService=AccelByte
    ; Specifies the name of the platform-specific OSS
    NativePlatformService=

    [OnlineSubsystemAccelByte]
    bEnabled=true
    ; Specifies to automatically connect to Lobby WebSocket
    bAutoLobbyConnectAfterLoginSuccess=true
    [/Script/AccelByteNetworkUtilities.IpNetDriverAccelByte]
    NetConnectionClassName=AccelByteNetworkUtilities.IpConnectionAccelByte
  5. In the same DefaultEngine.ini file, enable the AGS NetDriver:

    [/Script/Engine.GameEngine]
    !NetDriverDefinitions=ClearArray
    +NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="AccelByteNetworkUtilities.IpNetDriverAccelByte",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
    +NetDriverDefinitions=(DefName="DemoNetDriver",DriverClassName="/Script/Engine.DemoNetDriver",DriverClassNameFallback="/Script/Engine.DemoNetDriver")
  6. Edit your platform-specific config.ini file located inside your platform's folder. For example, Config/Windows/WindowsEngine.ini.

    // Set the OSS configs for Windows to use the AccelByte OSS as the default subsystem, and Steam as the native
    [OnlineSubsystem]
    NativePlatformService=Steam

    [OnlineSubsystemSteam]
    bEnabled=true
    bUseSteamNetworking=false
    SteamDevAppId=<game_app_id>

LocalUserNum

The AGS OSS is able to handle multiple local users playing the same game instance by identifying the controller index of the players. The same controller index is defined as LocalUserNum in most OSS interfaces. The AGS OSS stores a user's online information using LocalUserNum as the key.

UniqueNetId

UniqueNetId is the abstraction of the user's profile in online services. In AGS OSS, it is derived as AGS Composite UniqueNetId and consists of AGS userId, originating platform, and originating platform userId data fields.

Interfaces

The AGS OSS expands upon the predefined interfaces of UE's OSS, giving developers the ability to further extend and integrate AGS online services into their games. These interfaces wrap APIs in asynchronous calls so your game can still run as usual.

Online identity interface

Online Identity Interface is used for player authentication and profiles.

Login

The AGS OSS offers several different login types based on your native platform, or with other platforms by providing the type and credentials in the FOnlineAccelByteAccountCredentials class. There are several different login types available in the OSS. These login types are categorized in enumerations in EAccelByteLoginType. The types include:

  • DeviceId
  • AccelByte
  • Xbox
  • PS4
  • PS5
  • Launcher
  • Steam
  • RefreshToken
const UWorld* World = GameEngine->GetGameWorld();
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(World, ACCELBYTE_SUBSYSTEM);
FOnlineIdentityAccelBytePtr IdentityInterface = StaticCastSharedPtr<FOnlineIdentityAccelByte>(OnlineSubsystem->GetIdentityInterface());

if (IdentityInterface.IsValid())
{
FOnlineAccelByteAccountCredentials Credentials{ EAccelByteLoginType::AccelByte
, Username, Password };
// Login
IdentityInterface->AddOnLoginCompleteDelegate_Handle(LocalUserNum
, FOnLoginCompleteDelegate::CreateLambda([](int32 LocalUserNum, bool bLoginWasSuccessful, const FUniqueNetId& UserId, const FString& LoginError)
{
if (bLoginWasSuccessful)
{
// Do something when player successfully logged in
}
else
{
// Do something when player failed to log in
}
})
);
IdentityInterface->Login(LocalUserNum, Credentials);
}

After successfully logging in, a player will need to connect to the Lobby service to be able to use social-related interfaces, such as Friends, Party, and Presence. You can connect to these manually by calling ConnectAccelByteLobby, or by setting bAutoLobbyConnectAfterLoginSuccess to true in the DefaultEngine.ini file.

const UWorld* World = GameEngine->GetGameWorld();
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(World, ACCELBYTE_SUBSYSTEM);
FOnlineIdentityAccelBytePtr IdentityInterface = StaticCastSharedPtr<FOnlineIdentityAccelByte>(OnlineSubsystem->GetIdentityInterface());

if (IdentityInterface.IsValid())
{
// Connect to AccelByte Lobby Websocket, can be automatically called after
// successful login by configuring bAutoLobbyConnectAfterLoginSuccess to true
// in DefaultEngine.ini
IdentityInterface->ConnectAccelByteLobby(LocalUserNum);
}

Logout

When the player is finished playing, log out using this code:

const UWorld* World = GameEngine->GetGameWorld();
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(World, ACCELBYTE_SUBSYSTEM);
FOnlineIdentityAccelBytePtr IdentityInterface = StaticCastSharedPtr<FOnlineIdentityAccelByte>(OnlineSubsystem->GetIdentityInterface());

if (IdentityInterface.IsValid())
{
// Logout
IdentityInterface->AddOnLogoutCompleteDelegate_Handle(LocalUserNum
, FOnLogoutCompleteDelegate::CreateLambda([](int32 LocalUserNum, bool bLogoutWasSuccessful)
{
if (bLogoutWasSuccessful)
{
// Do something when player successfully logged out
}
else
{
// Do something when player failed to log out
}
})
);
IdentityInterface->Logout(LocalUserNum);
}

Online Agreement Interface

The Online Agreement Interface is used to query or accept the legal agreement policies of players.

User not complied

After a player successfully logs in, the Online Agreement Interface will check whether the player has complied with all the required legal policies. If the player has complied, it will trigger OnLoginCompleteDelegates and the player login flow will be complete. However, if a player has not complied with any of the required legal policies, it will trigger OnUserNotCompliedDelegates and the player will be unable to use the services.

const UWorld* World = GameEngine->GetGameWorld();
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(World, ACCELBYTE_SUBSYSTEM);
const FOnlineSubsystemAccelByte* ABSubsystem = static_cast<const FOnlineSubsystemAccelByte*>(Subsystem);
FOnlineAgreementAccelBytePtr AgreementInterface = ABSubsystem->GetAgreementInterface();

if (AgreementInterface.IsValid())
{
AgreementInterface->AddOnUserNotCompliedDelegate_Handle(LocalUserNum
, FOnUserNotCompliedDelegate::CreateLambda([]()
{
// Do something when player doesn't comply with the legal agreements
})
);
}

Query eligible agreements

After getting OnUserNotCompliedDelegates, the player will receive a list of eligible agreement policies. The player can use the QueryEligibleAgreements function to get the full list of the eligible policies, or they can filter to see only the agreement policies that have not yet been accepted. By default, this function will attempt to retrieve the list from the cache if it exists, and only request it from the service when it can't be found on the cached list. However, the player can also force it to always request it from the service without looking on the cached list. Once completed, the function will trigger OnQueryEligibilitiesCompletedDelegates.

const UWorld* World = GameEngine->GetGameWorld();
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(World, ACCELBYTE_SUBSYSTEM);
const FOnlineSubsystemAccelByte* ABSubsystem = static_cast<const FOnlineSubsystemAccelByte*>(Subsystem);
FOnlineAgreementAccelBytePtr AgreementInterface = ABSubsystem->GetAgreementInterface();

if (AgreementInterface.IsValid())
{
AgreementInterface->AddOnQueryEligibilitiesCompletedDelegate_Handle(LocalUserNum
, FOnQueryEligibilitiesCompletedDelegate::CreateLambda([](int32 LocalUserNum, bool bWasSuccessful, const TArray<FAccelByteModelsRetrieveUserEligibilitiesResponse>& Response, const FString& ErrMessage)
{
UE_LOG(LogTemp, Log, TEXT("List of mandatory policies that need to accepted by user."));
for (auto Policy : Response)
{
if (Policy.IsMandatory && !Policy.IsAccepted)
{
UE_LOG(LogTemp, Log, TEXT("%s\n"), *Policy.PolicyId);
}
}
})
);

// To get only the not yet accepted policies
bool bNotAcceptedOnly = true;

// To always request it to service instead of get it from cached list
bool bAlwaysRequestToService = true;

AgreementInterface->QueryEligibleAgreements(LocalUserNum
, bNotAcceptedOnly
, bAlwaysRequestToService);
}

Get localized policy contents

After retrieving the list of required policies, the player needs to be able to read the policy contents. Retrieve the localized policy content by using GetLocalizedPolicyContent with the policy ID and their locale code. Like other queries, GetLocalizedPolicyContent will first check on the cached contents by default, and if it does not exist yet, will request it from the service. Once completed, the function will trigger OnGetLocalizedPolicyContentCompletedDelegates.

const UWorld* World = GameEngine->GetGameWorld();
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(World, ACCELBYTE_SUBSYSTEM);
const FOnlineSubsystemAccelByte* ABSubsystem = static_cast<const FOnlineSubsystemAccelByte*>(Subsystem);
FOnlineAgreementAccelBytePtr AgreementInterface = ABSubsystem->GetAgreementInterface();

if (AgreementInterface.IsValid())
{
AgreementInterface->AddOnGetLocalizedPolicyContentCompletedDelegate_Handle(LocalUserNum
, FOnGetLocalizedPolicyContentCompletedDelegate::CreateLambda([](int32 LocalUserNum, bool bWasSuccessful, const FString& Response, const FString& Error)
{
if (bWasSuccessful)
{
UE_LOG(LogTemp, Log, TEXT("Document: %s"), *Response);
}
})
);

// To always request it to service instead of get it from cached documents
bool bAlwaysRequestToService = true;
FString LocaleCode = "en";

// You should be able to get the policy id from the eligible agreement list
FString PolicyId = "";

AgreementInterface->GetLocalizedPolicyContent(LocalUserNum
, PolicyId
, LocaleCode
, bAlwaysRequestToService);
}

Accept agreement policies

After the player has read the policy content, they will be able to accept the policy if it has not yet been accepted. Accept policies in bulk by inputting the list by policy ID and locale code. Once completed, the function will trigger OnAcceptAgreementPoliciesCompletedDelegates. If there are any mandatory documents on the list, it will also trigger the login and refresh the user token so the player will be able to use the services immediately after accepting the policies.

const UWorld* World = GameEngine->GetGameWorld();
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(World, ACCELBYTE_SUBSYSTEM);
const FOnlineSubsystemAccelByte* ABSubsystem = static_cast<const FOnlineSubsystemAccelByte*>(Subsystem);
FOnlineAgreementAccelBytePtr AgreementInterface = ABSubsystem->GetAgreementInterface();

if (AgreementInterface.IsValid())
{
AgreementInterface->AddOnAcceptAgreementPoliciesCompletedDelegate_Handle(LocalUserNum
, FOnAcceptAgreementPoliciesCompletedDelegate::CreateLambda([](int32 LocalUserNum, bool bWasSuccessful, const FString& Error)
{
if (!bWasSuccessful)
{
UE_LOG(LogTemp, Log, TEXT("Error: %s"), *Error);
}
})
);

TArray<FOnlineAgreementAccelByte::FABAcceptAgreementPoliciesRequest> DocumentToAccept;
FString LocaleCode = "en";

// You should be able to get the policy id from the eligible agreement list
FString PolicyId = "";

DocumentToAccept.Add({ PolicyId, LocaleCode });

AgreementInterface->AcceptAgreementPolicies(LocalUserNum, DocumentToAccept);
}