To help you get started learning about our services and their implementation, you can download Light Fantastic. Light Fantastic is our sample game which shows off a wide range of AccelByte services. By looking at our sample game and this guide, you’ll be able to see how our services can be implemented in your own game.
Modify GAME_VERSION (and DS_TARGET_VERSION) in Assets\Scripts\AccelByte\LightFantasticConfig.cs. If there are no changes in the gameplay and server version, DS_TARGET VERSION doesn't have to be updated.
In the Unity Editor, open the File menu and select BuildSettings. Select PC, Mac & Linux Standalone as the platform, then select your architecture from the dropdown list. Ensure that Server Build is cleared and that Development Build is selected.
Ensure the Assets/Resources/AccelByteServerSDKConfig.json fields are all correct, especially the ClientId and ClientSecret.
{
"PublisherNamespace":"accelbyte",
"Namespace":"lightfantastic",
"BaseUrl":"https://demo.accelbyte.io",
"DSMServerUrl":"http://justice-dsm-service/dsm",
"IamServerUrl":"https://demo.accelbyte.io/iam",
"ClientId":"CONTACT_US",
"ClientSecret":"CONTACT_US",
"RedirectUri":"http://127.0.0.1"
}
In the Unity Editor, open the File menu and select BuildSettings. Select PC, Mac & Linux Standalone as the platform, then choose Linux from the Target Platform dropdown list. Ensure that Server Build is selected.
In the Unity Editor, open the File menu and select BuildSettings. Select PC, Mac & Linux Standalone as the platform, then choose Windows from the Target Platform dropdown list. Ensure that Server Build is selected.
Click Build.
To run the server, add the parameter localds when running the executable.
In Light Fantastic, you can create a player account to play the game. As a developer you can work with accounts in several ways, including assigning different accounts different roles and permissions.
Our User Account Management service includes many ways of authenticating a user, such as headless auth via unique identifiers, or login with third parties such asSteam, Epic, Xbox, or Playstation.
In this guide, we’ll focus on the conventional email registration, verification, and authentication method.
One helpful feature of the AccelByte SDK is that it manages its own Unity GameObject instance. As such, you don’t need to create an empty GameObject to manage AccelByte services. When you call an AccelByte value or function, the SDK will create and manage itself in the active scene:
Here’s what happens when a player opens our game:
using AccelByte.Api;
using AccelByte.Models;
using AccelByte.Core;
public class AccelByteAuthenticationLogic : MonoBehaviour
The first thing we do is initialize our plugin in the scene, using the GetUser function. This will retrieve an empty user object, which we can then log into to input the necessary data. However, before we log a user in we must first register that user.
Registration requires just two functions. First is the registration code:
public void Register()
{
System.DateTime dob = new System.DateTime(int.Parse(UIHandlerAuthComponent.registerDobYear.text),
This code is hooked up to a few input fields and dropdown menus. First, we convert the date of birth text fields to System.DateTime format. Then, we call the User.Register function, which sends the player’s information to the server. By default, to register a player we need:
A valid email address
The player’s desired password
The player’s desired display name
Their current region in ISO 3166–1 alpha-2 format
Their date of birth in DateTime format
Callback to receive the data
The user registration form in Light Fantastic looks like this:
Our Register() function is tied to the Register button’s OnClick EventListener. When the event is fired, it sends the registration data to the server and the server sends back a response. We need a callback to interpret the data sent back from the server, as seen below:
Our registration callback takes one generic class of Result which we populate with various other member classes depending on the task at hand. In this case, the result will be populated with UserData. If there are any errors in the user data, information about the error is displayed for the player. Several errors are possible, such as their email address already being registered or one of the fields containing invalid characters. If the request was successful, the registration flow continues. By default, all new players must verify their email address to complete registration. Newly registered players will receive the email seen below:
Before we can call our Verify() function we must log our player in so they are authorized to make more calls against the service. You can see in our Register code that if a login is successful, we take the username and password from the registration UI fields and make a LoginWithUsername call.
Then the Registration panel fades out and lets our Login Callback handle things. In this case, the Login Callback checks for errors and attempts to GetUserDetails:
We use the abUserData object to store and reference the relevant UserData that the server sends back. In this case, we display a UserId and SessionId and then check that the player has verified their email. If they have, we send them to the Main Menu of the game; if not we send them to the Verification panel:
To verify we simply copy the verification code from the email we received into the box and click Verify, which is hooked to our Verify() function via the button’s OnClick EventListener. There are two verification functions: the event that sends the code to the server, and an event that requests a new code be emailed to a player:
We check for errors, and if there are none, progress the player to our main menu where we have finished the registration process. Next time the player logs in, we’ll take their entered email and password from the UI fields, call the Login function, and send the email and password to the server.
public void Login()
{
if (string.IsNullOrEmpty(UIHandlerAuthComponent.loginEmail.text))
{
if (string.IsNullOrEmpty(UIHandlerAuthComponent.loginPassword.text))
ShowErrorMessage(true, "Please fill your email address and password");
else
ShowErrorMessage(true, "Please fill your email address");
}
else if (string.IsNullOrEmpty(UIHandlerAuthComponent.loginPassword.text))
{
ShowErrorMessage(true, "Please fill your password");
Player profiles can store custom attributes, which we use here to store the equipment used by the player. This utilizes Assets/Scripts/Accelbyte/AccelByteEntitlementLogic.cs
public void UpdateMine(UpdateUserProfileRequest request, ResultCallback<UserProfile> callback)
UIHandlerEntitlementComponent.abUserProfileLogic.UpdateMine(savedEquipment, result =>
{
if (result.IsError)
{
Debug.Log("Failed to save current equipment!");
}
else
{
Debug.Log("Current equipment saved!");
}
});
}
UpdateUserProfileRequest in Assets/Accelbyte/Models/BasicModels.cs contains the data model for player profiles. Here, we want to save the equipment settings to customAttributes.
public class UpdateUserProfileRequest
{
[DataMember] public string firstName { get; set; }
[DataMember] public string lastName { get; set; }
[DataMember] public string language { get; set; }
[DataMember] public string avatarSmallUrl { get; set; }
[DataMember] public string avatarUrl { get; set; }
[DataMember] public string avatarLargeUrl { get; set; }
[DataMember] public string timeZone { get; set; }
[DataMember] public string dateOfBirth { get; set; }
[DataMember] public object customAttributes { get; set; }
}
Now every time a player equips gear from their inventory, their selection will be saved to their user profile. This gear will appear on the player’s character during a match.
AccelByte offers a full suite of Social features to help you build community between players of your game, including Friends, Groups, Chat, Notifications, and more. You can see how our Friends service works in Light Fantastic.
We use Websocket to run our social services, which allows us to send and receive messages along an established connection with a lot less overhead than a standard REST request. Websocket also allows for asynchronous communication, which is what allows features like chat and notifications to work.
// TODO: use coroutine to day the call to avoid spam
Debug.LogWarning("Not Connected To Lobby. Attempting to Connect...");
ConnectToLobby();
}
}
}
First thing we do is initialize our Lobby with the AccelBytePlugin.GetLobby function. This is the object we’ll use to manage our lobby connection, which we need for friends to work.
Next is our ConnectToLobby function, which handles the initial websocket connection. If this connection fails Social services will be unavailable, so upon failure we’ll try to reconnect until a connection is established. In production, it would be best to put the retry on a timer so that the service isn’t hit with too many requests.
Once we’re connected,the friends list inside AccelByteFriendsLogic.cs will load.
Request:
for (int i = 0; i < result.Value.friendsId.Length; i++)
{
Debug.Log(result.Value.friendsId[i]);
if (!friendList.ContainsKey(result.Value.friendsId[i]))
{
friendList.Add(result.Value.friendsId[i], new FriendData(result.Value.friendsId[i], "Loading...", new DateTime(2000, 12, 30), "0"));
lastFriendUserId = result.Value.friendsId[i];
}
}
isLoadFriendDisplayName = true;
}
}
LoadFriendsList returns an array of Friend IDs, which is all the game’s code needs, but it’s helpful for players to show their friends’ usernames as well. For each friendId we receive, we can get their info using GetUserByUserId.
Request:
public void GetFriendInfo(string friendId, ResultCallback<UserData> callback)
First, we need to clean up the previous instance of our friends list. This involves deleting all Child UI objects in our ScrollContent, seen below:
Next we check for errors in retrieving our friend’s list. If there are any errors a message is shown, if not the Reset Value is assigned to our FriendsStatus object and the UI is refreshed.
internal void RefreshFriendsUI()
{
ClearFriendsUIPrefabs();
foreach (KeyValuePair<string, FriendData> friend in friendList)
{
int lastSeen = System.DateTimeOffset.Now.Subtract(friend.Value.LastSeen).Days;
For every friend we have in our FriendStatus array we do the following:
Calculate the days since they were last seen
Instantiate a prefab friend tile gameobject (see above)
Set the parent of that friend tile to scroll view
If the days since the friend was last seen is 0, then the hours and minutes will also be checked. If all values are 0, the friend is shown as Online. If the days, hours, or minutes since they were last seen is greater than 0, the amount of time since they’ve been online will be displayed.
For this we instantiate a prefab of our friend tile, to which the FriendPrefab script is assigned.
We store the userId for later use with Chat, Party, or Matchmaking services. We also store the username, isonline and last seen values to display to the player.
Lastly, the SetupFriendUI function assigns the string values passed in from the ListFriendsStatus response to the actual Unity UI components.
With this our friends list is displayed! Now that we can display our friends list, we need to be able to invite other players to be our friend.
First, we need to clean up the previous instance of our friends list. This involves deleting all Child UI objects in our ScrollContent.
Then we check for errors, if none are found we instantiate a prefab search result UI tile as a child of the Search UI’s ScrollContent object. Similar to our friend UI tile, the search result is populated with a Display Name and UserId.
The script on the search result is a little different however, as the search result object is responsible for sending the friend request:
Here’s what the script looks like:
public class SearchFriendPrefab : MonoBehaviour
{
[SerializeField]
private Text usernameText;
[SerializeField]
private Button addFriendButton;
private string userId;
public void SetupFriendPrefab(string username, string id)
As you can see, it’s quite similar to the FriendPrefab in that it holds a player’s info and is responsible for updating its own UI text objects. This prefab, however, also contains a button for sending a friend request.
The AddFriend function is hooked to the button’s EventTrigger in the Unity Editor. When clicked, it triggers SendFriendRequest, which takes a userId and the callback to send the response.
The script manages its own response in the SendFriendRequestCallback function. We check for errors, and if there are none we change the button’s UI Text to say the request was sent and mark it as unclickable.
We need to clean up any existing UI Prefabs in our UI, then check for errors. For every friend in our outgoing friend requests, we instantiate an Invitation prefab, which consists of a Username and a Sent indicator.
Similar to before, each of these prefabs has its own script to manage the UI tile’s instance, since they each have some level of interaction.
Next we have Incoming Invites, where we can choose to either accept or decline a friend:
Debug.Log("AcceptFriendRequest sent successfully.");
Destroy(this.gameObject);
}
}
}
Similar to previous Prefab scripts, we want to set up our Invitation tile, but the only identifying information we have for the players is their User ID. We need easily identifiable names to show our end users. To get these we’ll use the GetFriendInfo function, which is just GetUserByUserId by a different name.
In OnGetFriendInfoRequest we check for errors, and if there are none we update the UI text element with the player’s Display Name.
Next we have AcceptInvite and DeclineInvite and their callbacks. These are hooked up to the Unity UI button’s event trigger:
When fired, these will add the player to the friends list, or remove the invite from the pending invites list.
With that completed, our new friend afif7 is now added to our overall friends list:
For player afif7, the invited player afif1 also shows up in their friends list.
Now that we’ve enabled friends, let’s set up parties. Parties allow players to play Light Fantastic together. The main actions players can take here are Creating a Party, Inviting Players to a Party, Kicking Players from a Party, and Leaving a Party.
Whoever creates a party will become the party leader. Only the party leader can invite other players to the party.
Party Leader
Target Player
Other Party Members
CreateParty()
-
-
First, we need to make sure that the player is already logged-in and in the lobby. Most of the interaction between party members will be located in AccelbytePartyLogic.cs.
Then implement the CreateParty lobby function along with the callback that contains PartyInfo as it’s parameter. PartyInfo contains PartyID, leaderID, members, invitees, and invitationToken.
Debug.Log("OnPartyCreated Party successfully created with party ID: " + result.Value.partyID);
abPartyInfo = result.Value;
SetIsLocalPlayerInParty(true);
isReadyToInviteToParty = true;
}
}
The CreateParty function can also be implemented right after the player presses the Invite To Party button in their friend list and before calling InviteToParty function. It will check if the inviter already has a party, and if not a new party will be created.
The party leader is the only one that can invite other players to the party via their friend list. Only friends that are online can be invited to join a party. In Light Fantastic, the party slots are shown in the bottom-right corner of the main menu of the game. Empty party slots have plus signs in them, and when clicked the player can add a friend to their party.
public void InviteToParty(string id, ResultCallback callback)
// If the player already in party then notify the user
PopupManager.Instance.ShowPopupWarning("Invite to Party Failed", " " + result.Error.Message, "OK");
}
else
{
Debug.Log("OnInviteParty Succeded on Inviting player to party");
PopupManager.Instance.ShowPopupWarning("Invite to Party Success", "Waiting for invitee acceptance", "OK");
}
}
The callback, OnInviteParty(), located in the FriendPrefab, is assigned to each friend in the friend list. For invited players, the InvitedToParty event callback that needs to be set up.
public void SetupPartyCallbacks()
{
lobbyLogic.abLobby.InvitedToParty += result => OnInvitedToParty(result);
The invitation arrives to the invited player through the InvitedToParty event. The PartyInvitation should be cached to a variable so it can be used with the JoinParty function in the next step. The abPartyInfo variable will be used to store information about the party that will be useful to update our UI later on.
The From value in PartyInvitation is a user ID, so we need to call GetUserData in order to get the displayname to show who the invitation is from. After the popup is shown to the player they’ll be given two options, to either Accept or Decline the invitation.
Declining the invitation closes the popup invitation by calling SetActive to false on the popup’s gameobject. Accepting the invitation will call the JoinParty function and the callback will update the player’s current party with the latest info. The UI party slot will also be updated once the partyInfo is retrieved successfully. Below you can see how to assign the AcceptPartyButton OnClick event to OnAcceptPartyClicked.
And here is what it looks like when the button is clicked.
// On joined should change the party slot with newer players info
Debug.Log("OnJoinedParty Joined party with ID: " + result.Value.partyID + result.Value.leaderID);
SetIsLocalPlayerInParty(true);
abPartyInfo = result.Value;
ClearPartySlots();
GetPartyInfo();
PopupManager.Instance.ShowPopupWarning("Join a Party", "You are just joined a party!", "OK");
}
}
The party slot displays all of the party members from abPartyInfo.members visually. It also records the player’s userId, display name, and email. Below are the player slots as they appear in Light Fantastic (in the bottom-right corner of the screen) and in Unity:
Of the four slots, there is one PlayerButton and three AddFriendButtons. To use the AddFriendButtons, first we must reference them with a transform array.
public class UILobbyLogicComponent : MonoBehaviour
{
public Transform[] partyMemberButtons;
}
Next, create a partyMemberList using the PartyData.
After the UI is updated, the party members will receive the JoinedParty event. Just like InvitedToParty, we need to set up the listener for this event.
Too add the listener to the event:
public void SetupPartyCallbacks()
{
lobbyLogic.abLobby.InvitedToParty += result => OnInvitedToParty(result);
lobbyLogic.abLobby.JoinedParty += result => OnMemberJoinedParty(result);
The OnMemberJoinedParty function updates the party info variable in the AccelbyteLobbyLogic, and updates the UI party slot once the partyInfo has been retrieved successfully.
Party leaders can kick members from their party. Upon being kicked, the player will receive a notification.
When the party leader hovers over a player’s party slot, their profile will appear as a popup along with the Kick From Party button. Here’s what the popup looks like in Unity:
There are a few grouped game objects, such as:
LocalLeaderCommand (will be shown when the playerButton triggers the onMouseHover event if the local player is a party leader)
LocalMemberCommand (will be shown when the playerButton triggers the onMouseHover event if the local player is a party member)
MemberCommand (will be shown when any AddFriendButton triggers the onMouseHover event if the local player is a party leader)
ShowPlayerProfile is located in AccelbyteLobbyLogic.cs.It will show the party controls in the lobby menu.
public void ShowPlayerProfile(PartyData memberData, bool isLocalPlayerButton = false)
{
// If visible then toggle it off to refresh the data
if (UIHandlerLobbyComponent.popupPartyControl.gameObject.activeSelf)
PopupManager.Instance.ShowPopupWarning("Kick a Party Member", "You are just kicked one of the party member!", "OK");
}
}
The kicked player will receive the KickedFromParty notification right after being kicked by the party leader. The callback of this event will be used to clear the party slot.
Register KickedFromParty as the listener:
public void SetupPartyCallbacks()
{
lobbyLogic.abLobby.InvitedToParty += result => OnInvitedToParty(result);
lobbyLogic.abLobby.JoinedParty += result => OnMemberJoinedParty(result);
lobbyLogic.abLobby.KickedFromParty += result => OnKickedFromParty(result);
Debug.Log("OnKickedFromParty party with ID: " + result.Value.partyID);
isMemberKickedParty = true;
MainThreadTaskRunner.Instance.Run(delegate
{
PopupManager.Instance.ShowPopupWarning("Kicked from The Party", "You are just kicked from the party!", "OK");
});
}
}
All the other members will receive the LeaveFromParty event when a player is kicked. This updates the PartyInfo and the UI party slot to reflect that the kicked player is gone.
The LeaveFromParty even registration:
public void SetupPartyCallbacks()
{
lobbyLogic.abLobby.InvitedToParty += result => OnInvitedToParty(result);
lobbyLogic.abLobby.JoinedParty += result => OnMemberJoinedParty(result);
lobbyLogic.abLobby.KickedFromParty += result => OnKickedFromParty(result);
lobbyLogic.abLobby.LeaveFromParty += result => OnMemberLeftParty(result);
Any party member, including the leader, can leave the party. Just like when a player is kicked, a member leaving calls the LeaveParty function, while other members receive the LeaveFromParty event.
Here is the LeaveParty function. After this function is called, the callback function clears the party slots, marking that the player has left the party.
private void OnLeavePartyButtonClicked()
{
if (accelByteManager.AuthLogic.GetUserData().userId == abPartyInfo.leaderID)
PopupManager.Instance.ShowPopupWarning("Leave The Party", "You are just left the party!", "OK");
}
}
After the event, the remaining party members will have their PartyInfo updated just as if a member had been kicked. If the member that left was the party leader, the leadership will be transferred to the second player on the PartyInfo.members[] list.
In Light Fantastic, a player can start matchmaking right away for a 1v1 match or after making a party for 4 player free for all (FFA). StartMatchmaking is called to initiate the matchmaking process. The code related to matchmaking can be found in AccelByteMatchmakingLogic.cs.
Here’s what happens when the Find Match button is clicked:
rivate void FindMatchButtonClicked()
{
if (!lobbyLogic.partyLogic.GetIsLocalPlayerInParty())
MatchmakingCompleted occurs when a match has been found. The event carries MatchmakingNotif that contains the match status (i.e. Start, Cancel, Done) and the matchId.
Registering a callback on MatchmakingCompleted:
public void SetupMatchmakingCallbacks()
{
lobbyLogic.abLobby.MatchmakingCompleted += result => OnFindMatchCompleted(result);
// if the player is in a party and the party leader cancel the current matchmaking
else if (result.Value.status == MatchmakingNotifStatus.cancel.ToString())
{
MainThreadTaskRunner.Instance.Run(delegate
{
ShowMatchmakingBoard(false);
});
}
}
}
Start occurs when the party leader starts the matchmaking process, Cancel indicates that the party leader has canceled the matchmaking process, and Done occurs when the matchmaking process is finished.
ReadyForMatchConfirmation is an event that allows players to choose whether they want to join the match or not. In Light Fantastic, matches are automatically accepted when MatchmakingNotif returns Done as its status. Below is the callback for match confirmation:
A player can use cancel matchmaking by clicking on the Cancel button in the Finding Match panel. The cancel matchmaking command is stackable on the backend, so be sure that it’s only called once when the matchmaking starts. If Cancel is clicked twice, the second click will negate the next start matchmaking command.
When the matchmaking is done, all of the players will receive status updates from the Distributed Server (DS). The first status will be Creating, which indicates that the DS is being created. When the DS is ready to use a second callback will change the status from Creating to Ready. This notification will also include the IP and Port that the game client can use to connect to the DS. When the DS has accepted the players, its status will be changed to Busy.
If the players are connected to a local DS (connectToLocal), the DS status will always be Busy.
Register the callback to the DSUpdated event. Upon callback, you will need to connect the player to the DS using the IP and Port information received from DsNotif.
Registering the event:
public void SetupMatchmakingCallbacks()
{
lobbyLogic.abLobby.MatchmakingCompleted += result => OnFindMatchCompleted(result);
lobbyLogic.abLobby.ReadyForMatchConfirmed += result => OnGetReadyConfirmationStatus(result);
lobbyLogic.abLobby.DSUpdated += result => OnSuccessMatch(result);
Player statistics is a feature that lets players see their records in the game. In Light Fantastic, the player statistics available include the total number of wins, losses, matches, and distance traveled when playing the game.
In Light Fantastic, there are four player statistics that are set by the game server after a match is finished. These include number of wins (total-win), number of losses (total-lose), number of matches (total-match), and total distance traveled (total-distance). Because this information is managed by the game server, it can be found in AccelByteServerLogic.cs.
Leaderboards contain information about the player ranking system, based on information made available by our Statistics service.
To retrieve the leaderboard data from the AccelByte SDK in Light Fantastic, use the code below. All leaderboard logic is managed by AccelByteLeaderboardLogic.cs.
First, run the Leaderboard service and hold its reference.
// class that handles all shorts of functions on UI
private UIElementHandler UIElementHandler;
public struct RankData
{
public string userId;
public string rank;
public string playerName;
public float winStats;
public RankData(string userId, string rank, string playerName, float winStats)
{
this.userId = userId;
this.rank = rank;
this.playerName = playerName;
this.winStats = winStats;
}
}
public void Init()
{
if (abLeaderboard == null) abLeaderboard = AccelBytePlugin.GetLeaderboard();
playerRankList = new Dictionary<string, RankData>();
RefreshUIHandler();
}
Then, retrieve the top 10 ranking from the leaderboard by using QueryAllTimeLeaderboardRankingData. The parameters are the leaderboardCode that we received when we created the leaderboard, the start and end query list of players whose data we want to retrieve, and the callback.
for (int i = 0; i < result.Value.data.Length; i++)
{
if (!playerRankList.ContainsKey(result.Value.data[i].userId))
{
var playerResult = result.Value.data[i];
string playerRankResult = (i + 1).ToString();
if (i < 9)
{
playerRankResult = "0" + playerRankResult;
}
playerRankList.Add(playerResult.userId, new RankData(playerResult.userId, playerRankResult, "Loading . . .", playerResult.point));
lastPlayerRank = playerResult.userId;
}
}
isLeaderboardUpdate = true;
RefreshLeaderboardUIPrefabs();
}
}
On the callback, we need to sort the data and update the UI. After the userID data is retrieved from the leaderboard, we need to retrieve each user’s display name by calling GetUserByUserId().
In Light Fantastic, player entitlements appear when a player opens up their inventory from the main menu. Here, they can see the items they have and equip any of those items to their avatar. They also appear during gameplay, where player avatars are seen wearing the equipped items.
While entitlements are private information, items currently equipped by a player are saved to their UserProfile, which has public attributes. Other players can see what items the player has equipped when looking at their profile or playing in a match with them.
The entitlements logic are stored inside AccelByteEntitlementLogic.cs. To begin, retrieve the entitlement from the reference and store it in abEntitlements.
public class AccelByteEntitlementLogic : MonoBehaviour
{
public delegate void GetEntitlementCompletion(bool inMenum, Error error);
public event GetEntitlementCompletion OnGetEntitlementCompleted;
The GetEntitlement function includes the inMenu parameter, which indicates whether the entitlement information is needed for the player’s inventory in the main menu or in-game. The variable allItemInfo contains all of the item data available in the store and retrieved from the entitlement service. After the items’ data has been retrieved, proceed to QueryUserEntitlements.
The first parameter of QueryUserEntitlements is the entitlement name which in Light Fantastic is left empty, as it is optional. The second parameter is item ID which is also optional (and also empty here), and the third one is offset, which refers to the offset of the list that has been sliced based on the limit parameter. The value for the offset in Light Fantastic is 0. The fourth and last parameter is limit, which has a value of 99 in Light Fantastic. This means that in Light Fantastic, users can have a maximum of 99 entitlements.
The callback for retrieving entitlements for the inventory is different than the one used for in-game. As mentioned above, we use the User Profile service to save a player’s equipped items.
The in-game callback is used to display the equipped items on the player’s avatar while they play. Like getting entitlements for the inventory, this also relies on the User Profile service to determine which entitlements are equipped.
When the player’s entitlements have been retrieved BasePlayerPawn.cs is triggered and the OnGetSelfEntitlementCompleted function will equip the items to the player’s avatar.