Use the OSS to upgrade account - In-game registration - (Unreal Engine module)
Unwrap the Subsystem
This tutorial will show you how to upgrade a headless account to a full AccelByte Gaming Services (AGS) account using the AGS Game SDK Online Subsystem (OSS). In the Byte Wars project, there is already a game instance subsystem created named RegisterUserInGameSubsystem
. This subsystem contains the completed implementation to upgrade an account. However, in this tutorial, you will use a starter version of that subsystem so you can implement the functions from scratch.
What's in the Starter Pack
To follow this tutorial, a starter subsystem class named RegisterUserInGameSubsystem_Starter
has been prepared for you. This class is available in the Resources section and consists of the following files:
- Header file:
/Source/AccelByteWars/TutorialModules/Access/RegisterUserInGame/RegisterUserInGameSubsystem_Starter.h
- CPP file:
/Source/AccelByteWars/TutorialModules/Access/RegisterUserInGame/RegisterUserInGameSubsystem_Starter.cpp
The RegisterUserInGameSubsystem_Starter
class also has several helpers:
AGS OSS
UserInterface
interface declaration. You will use this interface to query the new full account information.protected:
// ...
FOnlineUserAccelBytePtr GetUserInterface() const;
There is also a model file in the /Source/AccelByteWars/TutorialModules/Access/RegisterUserInGame/RegisterUserInGameModels.h
file. This file contains the following helpers:
A delegate declaration to handle the response from the backend.
DECLARE_DELEGATE_ThreeParams(FOnUpgradeAndVerifyAccountComplete, bool bWasSuccessful, const FString& ErrorMessage, const FAccountUserData& NewFullAccount);
DECLARE_DELEGATE_TwoParams(FOnSendUpgradeAccountVerificationCodeComplete, bool bWasSuccesful, const FString& ErrorMessage);A string constant to print logs and messages.
#define SEND_VERIFICATION_CODE_MESSAGE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Send Verification Code Message", "Sending verification code")
#define RESEND_VERIFICATION_CODE_MESSAGE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Send Verification Code Message", "Re-send Verification Code")
#define UPGRADE_ACCOUNT_MESSAGE NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Message", "Upgrading and verifying account")
#define EMPTY_REQUIRED_FIELDS_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Empty Required Fields Error", "Required fields cannot be empty")
#define EMPTY_VERIFICATION_CODE_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Empty Verification Code Error", "Verification code cannot be empty")
#define EMAIL_ALREADY_USED_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Email Already Used Error", "E-mail is already used")
#define EMAIL_INPUT_VIOLATION_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Email Input Violation Error", "E-mail format is invalid")
#define USERNAME_ALREADY_USED_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Username Already Used Error", "Username is already used")
#define USERNAME_INPUT_VIOLATION_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Username Input Validation Error", "Username must be {0}-{1} characters and cannot contain whitespaces nor special characters")
#define PASSWORD_INPUT_VIOLATION_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Password Input Violation Error", "Password must be {0}-{1} characters, must contains a mix of upercase and lowercase letters, and cannot contain special characters except dash and underscore")
#define PASSWORD_NOT_MATCH_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Password Not Match Error", "Password does not match, retype the password")
#define UPGRADE_ACCOUNT_UNKNOWN_ERROR NSLOCTEXT(ACCELBYTEWARS_LOCTEXT_NAMESPACE, "Upgrade Account Unknown Error", "Unknown error occurred")inline const FText GetUpgradeAccountErrorMessage(const EUpgradeAccountErrorTypes ErrorCode)
{
switch ((EUpgradeAccountErrorTypes)ErrorCode)
{
case EUpgradeAccountErrorTypes::EmailAlreadyUsed:
return EMAIL_ALREADY_USED_ERROR;
break;
case EUpgradeAccountErrorTypes::UsernameAlreadyUsed:
case EUpgradeAccountErrorTypes::UniqueDisplayNameAlreadyUsed:
return USERNAME_ALREADY_USED_ERROR;
break;
case EUpgradeAccountErrorTypes::UsernameInputViolation:
return FText::Format(USERNAME_INPUT_VIOLATION_ERROR, MIN_USERNAME_LEN, MAX_USERNAME_LEN);
break;
case EUpgradeAccountErrorTypes::PasswordInputViolation:
return FText::Format(PASSWORD_INPUT_VIOLATION_ERROR, MIN_PASSWORD_LEN, MAX_PASSWORD_LEN);
break;
default:
return UPGRADE_ACCOUNT_UNKNOWN_ERROR;
break;
}
}
Implement requesting verification codes
In this section, you will implement functions to send an email verification code request.
Open the
RegisterUserInGameSubsystem_Starter
class Header file and declare the helper variable below. This variable will be used to store the email address, preventing multiple verification code requests to the same email.private:
// ...
TSet<FString> LastVerificationCodeTargetEmails;Next, declare the following function to send an email verification code request.
public:
// ...
void SendUpgradeAccountVerificationCode(
const FString& EmailAddress,
const bool bForceResend = false,
const FOnSendUpgradeAccountVerificationCodeComplete& OnComplete = FOnSendUpgradeAccountVerificationCodeComplete());Now, open the
RegisterUserInGameSubsystem_Starter
class CPP file and define the function above. This function sends a request to the backend to send a verification code to the target email. If thebForceResend
is disabled, then it won't send a new request to the same email address.void URegisterUserInGameSubsystem_Starter::SendUpgradeAccountVerificationCode(
const FString& EmailAddress,
const bool bForceResend,
const FOnSendUpgradeAccountVerificationCodeComplete& OnComplete)
{
if (!bForceResend && LastVerificationCodeTargetEmails.Contains(EmailAddress))
{
UE_LOG_REGISTERUSERINGAME(Log, TEXT("Verification code already sent to the same e-mail."));
OnComplete.ExecuteIfBound(true, TEXT(""));
return;
}
AccelByte::FRegistry::User.SendUpgradeVerificationCode(
EmailAddress,
AccelByte::FVoidHandler::CreateWeakLambda(this, [this, EmailAddress, OnComplete]()
{
UE_LOG_REGISTERUSERINGAME(Log, TEXT("Success to send upgrade to full account verification code to the registered e-mail."));
LastVerificationCodeTargetEmails.Add(EmailAddress);
OnComplete.ExecuteIfBound(true, TEXT(""));
}),
FErrorHandler::CreateWeakLambda(this, [this, OnComplete](int32 ErrorCode, const FString& ErrorMessage)
{
UE_LOG_REGISTERUSERINGAME(Warning, TEXT("Failed to send upgrade to full account verification code to the registered e-mail. Error %d: %s"), ErrorCode, *ErrorMessage);
OnComplete.ExecuteIfBound(false, GetUpgradeAccountErrorMessage((EUpgradeAccountErrorTypes)ErrorCode).ToString());
})
);
}Next, find the predefined
Deinitialize()
function, which is called when the subsystem is deinitialized. In this function, add the code below to clear the value of the helper variable.void URegisterUserInGameSubsystem_Starter::Deinitialize()
{
Super::Deinitialize();
LastVerificationCodeTargetEmails.Empty();
}
Implement upgrade and verify account
In this section, you will implement functions to upgrade and verify the account using the verification code.
First, create a helper function to check whether the account is already a full account or not. Open the
RegisterUserInGameSubsystem_Starter
class Header file and declare the following function:public:
// ...
bool IsCurrentUserIsFullAccount();Next, open the CPP file for the
RegisterUserInGameSubsystem_Starter
class and implement the function described above. This function checks whether the currently logged-in account is a full account by verifying if an email address is registered and verified.bool URegisterUserInGameSubsystem_Starter::IsCurrentUserIsFullAccount()
{
const bool bHasLinkedEmail = !AccelByte::FRegistry::CredentialsRef->GetUserEmailAddress().IsEmpty();
const bool bIsEmailVerified = AccelByte::FRegistry::CredentialsRef->GetAccountUserData().EmailVerified;
return bHasLinkedEmail && bIsEmailVerified;
}Now, open the
RegisterUserInGameSubsystem_Starter
class Header file again and declare the following functions:public:
// ...
void UpgradeAndVerifyAccount(
const int32 LocalUserNum,
const FUniqueNetIdPtr UserId,
const FString& Username,
const FString& EmailAddress,
const FString& Password,
const FString& VerificationCode,
const FOnUpgradeAndVerifyAccountComplete& OnComplete = FOnUpgradeAndVerifyAccountComplete());protected:
void OnUpgradeAndVerifyAccountSuccess(
const FAccountUserData& NewFullAccount,
const int32 LocalUserNum,
const FUniqueNetIdRef UserId,
const FOnUpgradeAndVerifyAccountComplete OnComplete);Next, open the
RegisterUserInGameSubsystem_Starter
class CPP file and define theUpgradeAndVerifyAccount()
function. This function sends a request to upgrade the account by using the verification code received via email. Once the request completes, the callback will be handled by theOnUpgradeAndVerifyAccountSuccess()
function.void URegisterUserInGameSubsystem_Starter::UpgradeAndVerifyAccount(
const int32 LocalUserNum,
const FUniqueNetIdPtr UserId,
const FString& Username,
const FString& EmailAddress,
const FString& Password,
const FString& VerificationCode,
const FOnUpgradeAndVerifyAccountComplete& OnComplete)
{
if (!UserId.IsValid())
{
UE_LOG_REGISTERUSERINGAME(Warning, TEXT("Failed to upgrade headless account to full account. User id is null."));
OnComplete.ExecuteIfBound(false, UPGRADE_ACCOUNT_UNKNOWN_ERROR.ToString(), FAccountUserData());
return;
}
FUpgradeAndVerifyRequest Request;
Request.Username = Username;
Request.DisplayName = Username;
Request.UniqueDisplayName = Username;
Request.EmailAddress = EmailAddress;
Request.Password = Password;
Request.Code = VerificationCode;
AccelByte::FRegistry::User.UpgradeAndVerify2(
Request,
THandler<FAccountUserData>::CreateUObject(this, &ThisClass::OnUpgradeAndVerifyAccountSuccess, LocalUserNum, UserId.ToSharedRef(), OnComplete),
FErrorHandler::CreateWeakLambda(this, [this, OnComplete](int32 ErrorCode, const FString& ErrorMessage)
{
UE_LOG_REGISTERUSERINGAME(Warning, TEXT("Failed to upgrade headless account to full account. Error %d: %s"), ErrorCode, *ErrorMessage);
OnComplete.ExecuteIfBound(false, GetUpgradeAccountErrorMessage((EUpgradeAccountErrorTypes)ErrorCode).ToString(), FAccountUserData());
})
);
}Now, define the
OnUpgradeAndVerifyAccountSuccess()
by adding the code below. This code re-queries the user account info to update the user account cache in the AGS OSS.void URegisterUserInGameSubsystem_Starter::OnUpgradeAndVerifyAccountSuccess(
const FAccountUserData& NewFullAccount,
const int32 LocalUserNum,
const FUniqueNetIdRef UserId,
const FOnUpgradeAndVerifyAccountComplete OnComplete)
{
UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>();
if (!StartupSubsystem)
{
UE_LOG_REGISTERUSERINGAME(Warning, TEXT("Failed to handle on upgrade headless account success. Startup subsystem is null."));
return;
}
// Query new user info to update account user cache on OSS.
StartupSubsystem->QueryUserInfo(
LocalUserNum,
TPartyMemberArray{ UserId },
FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, LocalUserNum, UserId, NewFullAccount, OnComplete](
const FOnlineError& Error,
const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
{
if (!Error.bSucceeded || UsersInfo.IsEmpty())
{
UE_LOG_REGISTERUSERINGAME(
Warning,
TEXT("Failed to upgrade headless account to full account. Error %s: %s"),
*Error.ErrorCode,
*Error.ErrorMessage.ToString());
OnComplete.ExecuteIfBound(false, UPGRADE_ACCOUNT_UNKNOWN_ERROR.ToString(), FAccountUserData());
return;
}
UE_LOG_REGISTERUSERINGAME(Log, TEXT("Success to upgrade headless account to full account: %s"), *NewFullAccount.DisplayName);
// Update account user cache on SDK.
AccelByte::FRegistry::Credentials.SetAccountUserData(NewFullAccount);
OnComplete.ExecuteIfBound(true, TEXT(""), NewFullAccount);
}));
}
Resources
The files used in this tutorial section are available in the Unreal Byte Wars GitHub repository.
- AccelByteWars/Source/AccelByteWars/TutorialModules/Access/RegisterUserInGame/RegisterUserInGameSubsystem_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Access/RegisterUserInGame/RegisterUserInGameSubsystem_Starter.cpp
- AccelByteWars/Source/AccelByteWars/TutorialModules/Access/RegisterUserInGame/RegisterUserInGameModels.h