Unreal Engine 用 AGS OSS
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 version UE 4.26 UE 4.27 UE 5.0 UE 5.1 UE 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
Add the following required plugins to the
.uproject
file:"Plugins": [
{
"Name": "AccelByteUe4Sdk",
"Enabled": true
},
{
"Name": "OnlineSubsystemAccelByte",
"Enabled": true
},
{
"Name": "AccelByteNetworkUtilities",
"Enabled": true
},
...
]Add public dependency modules to the
Build.cs
file:PublicDependencyModuleNames.AddRange(new string[] { "AccelByteUe4Sdk"
, "AccelByteNetworkUtilities"
, "OnlineSubsystemAccelByte"
});Add the following module to the
Target.cs
file:ExtraModuleNames.AddRange( new string[] { "AccelByteUe4Sdk"
, "OnlineSubsystemAccelByte"
, "AccelByteNetworkUtilities"
});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.IpConnectionAccelByteIn the same
DefaultEngine.ini
file, enable the AGSNetDriver
:[/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")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);
}