In-game mailboxes are common mechanisms for game developers to communicate with their players. You can use them to tell players about new in-game events, gift players items and currency, or to help retain players in the game.
When a player loads the scene for the first time, they see an inbox with a list of messages waiting for them to read. On subsequent loads, the inbox is either in the state they left it, or an updated state due to messages expiring between sessions.
Players can interact with the messages, claim their attachments, delete messages, or reset the inbox to a brand new state.
To see this use case in action:
- In the Unity Editor Project window, select Assets > Use Case Samples > In-Game Mailbox, and then double-click
InGameMailboxSample.unity
to open the sample scene. - Enter Play Mode to interact with the use case.
When the scene loads, the InGameMailboxSceneManager
script performs the following initialization steps:
- Initializes Unity Gaming Services.
- Signs in the player anonymously using the Authentication service. If you’ve previously initialized any of the other sample scenes, Authentication will use your cached Player ID instead of creating a new one.
- Refreshes the Economy configuration data. If new Economy items were created since the last time the player opened the app, this will initialize those items in the player's configuration.
- Retrieves and updates currency balances from the Economy service for that authenticated user.
- Uses the sprite addresses stored in the Economy item configuration's custom data to load all possible currency and inventory item sprites from Addressables.
- Retrieves the updated message info for the player's inbox:
- Downloads the list of all possible messages from Remote Config.
- Retrieves the player's current inbox data from Cloud Save.
- Checks whether any of the messages already saved in the player's inbox are expired, and deletes them if so.
- Checks for any new messages that were downloaded from Remote Config and not yet added to the player's inbox.
- Saves the updated inbox state for the player in Cloud Save.
- Displays the updated list of inbox messages in the scene.
The left panel in the scene displays the list of messages in the player's inbox. This list updates over time as messages expire, or based on player interaction.
Below the list, a counter displays how many messages are in the inbox and the max number of messages that can be in the inbox at any given time. When a player loads the scene for the first time, the inbox is full.
When all messages have been deleted from the inbox (either through player interaction or message expiration), a popup appears prompting the player to reset the inbox.
Note: This popup is a usability feature of the sample, and would not be an expected interaction in a real-world implementation.
When you select a message from the list, the following occurs:
- The full details of the message appears on the right side of the scene.
- The message is marked as read, and that status is saved to Cloud Save.
If the message has an attachment, there is also an indication of which Economy items are attached, along with a button to claim them.
When you press the Claim button, the client code calls the InGameMailbox_ClaimAttachment.js
Cloud Code script with the selected message's ID included as a parameter. The following occurs on the backend:
- The client requests the Cloud Save inbox data, and locates the message with the supplied ID.
- The script checks whether the message has an unclaimed attachment.
- Assuming the message does have an unclaimed attachment, the script makes an Economy
makeVirtualPurchase
call using the Virtual Purchase ID from themessage.messageInfo.attachment
field. - If the purchase processes successfully, the
message.metadata.hasUnclaimedAttachment
field is set tofalse
and saved in Cloud Save, so that the player cannot claim the attachment again.
When you press the delete button for a message:
- The message is removed from the local list of inbox messages.
- If the inbox was previously full, such that deleting the message created space for a new message, the client rechecks the list of all possible messages downloaded from Remote Config against the message ID of the last message saved in Cloud Save to see if there are any new messages to add to the inbox. If there are, it adds as many of those messages to the inbox as will fit.
- The updated list of inbox messages is then saved in Cloud Save.
- Finally, the view refreshes to show the updated list. If the deleted message was previously selected, this also updates the UI to not display the deleted message's detail view.
When you press the Claim All button, the client code makes a call to the InGameMailbox_ClaimAllAttachments.js
Cloud Code script. The following occurs on the backend:
- The client requests inbox data from Cloud Save.
- Cloud Save returns a list of inbox messages filtered to only show messages with
message.metadata.hasUnclaimedAttachment
set totrue
. - For each message in this filtered list:
- The script makes an Economy
makeVirtualPurchase
call using the Virtual Purchase ID from themessage.messageInfo.attachment
field. - If the purchase processes successfully,
message.metadata.hasUnclaimedAttachment
is set tofalse
, andmessage.metadata.isRead
is set totrue
.
- The script makes an Economy
- Once all message attachments are claimed, the updated message list is saved in Cloud Save.
Note: Saving changes in Cloud Save after each attachment is claimed would make the process more fault tolerant. However, it would also require more server calls and therefore be less efficient than the selected approach. It is up to the developer which advantage to prioritize.
When you press the Delete Read button:
- The script loops through the list of inbox messages, and removes each message that has both
message.metadata.isRead
set totrue
andmessage.metadata.hasUnclaimedAttachment
set tofalse
. This implementation does not delete messages with unclaimed attachments, to prevent players from accidentally deleting messages with available attachments. - If the inbox was previously full, such that deleting the message created space for a new message, the client rechecks the list of all possible messages downloaded from Remote Config against the message ID of the last message saved in Cloud Save to see if there are any new messages to add to the inbox. If there are, it adds as many of those messages to the inbox as will fit.
- The updated list of inbox messages is then saved in Cloud Save.
- Finally, the view refreshes to show the updated list. If the deleted message was previously selected, this also updates the UI to not display the deleted message's detail view.
At the bottom of the scene, you can reset the inbox while impersonating a particular audience:
Default
All Spenders
Unengaged Players
French Speakers
New Players
Each of the non-default audiences adds a message to the message list that is specific to that particular audience. These messages are determined by Game Overrides.
When you reset the inbox for the given audience, the following occurs:
- The scene resets, clearing the selected message field and deleting the Cloud Save inbox data.
- The client queries Remote Config with the specified audience, to retrieve the list of potential messages that includes any audience specific ones that were previously omitted.
- The maximum number of messages are added to the inbox from the Remote Config data, and saved in Cloud Save.
- The view refreshes to show the new list of messages.
When you press the inventory bag icon, the following occurs:
- The client calls
EconomyService.Instance.PlayerInventory.GetInventoryAsync()
to refresh the player's list of owned Economy inventory items. - A pop-up window displays the resulting list of inventory items.
To replicate this use case, you need the following Unity packages in your project:
Package | Role |
---|---|
Addressables | Allows developers to retrieve an asset by using its address. In this sample, the service looks up Economy item sprites based on the information stored in the Economy item's custom data. |
Authentication | Automatically signs in the user anonymously to keep track of their data server-side. |
Cloud Code | Stores important validation logic server-side. In this use case it is used to validate that message attachments haven't already been claimed, and to process the virtual purchase that stores the attachment rewards. |
Cloud Save | Stores the player's inbox state, including the message info downloaded from Remote Config, and the player-specific message metadata like whether the message has been read yet. |
Economy | Maintains the player's wallet and inventory, and the virtual purchase info associated with a given message's attachment. |
Game Overrides* | Defines the audience grouping and message data for those messages that we want to only send to a certain audience. |
Remote Config | Provides key-value pairs where the value that is mapped to a given key can change on the server side, either manually or based on specific Game Overrides. In this sample, we store the message info in Remote Config, and for messages that should only be sent to a particular audience, we store them as blank messages (they'll get filled in by Game Overrides). |
Deployment | The Deployment package provides a cohesive interface to deploy assets for Cloud Services. |
* Note that though it is listed as a package and requires separate dashboard configuration, Game Overrides doesn't actually have an SDK to install from Package Manager. It is a server side offering that affects values returned from other services, like Remote Config.
To use these services in your game, activate each service for your Organization and project in the Unity Dashboard.
To replicate this sample scene's setup in your own Unity project, we need to configure the following items:
- Cloud Code scripts
- Economy items
- Remote Config values
- Remote Config Game Overrides
There are two main ways of doing this, either by using the Deployment package, or by manually entering them using the Dashboard. We recommend the usage of the Deployment package since it will greatly accelerate this process.
Here are the steps to deploy configuration using the Deployment package:
- Open the Deployment window
- Check in
Common
andIn-Game Mailbox
- Click
Deploy Selection
This will deploy the following items:
- Cloud Code scripts
- Economy items
- Remote Config values
The following items are not managed by the Deployment package at this time:
- Remote Config Game Overrides
To configure them, please refer to the next section.
The Dashboard enables you to edit manually all your services configuration by project and environment. Here are the details necessary for the configuration of the current sample.
Configure the following Overrides in the LiveOps dashboard:
Details | Name the Override “Messages All Spenders Overrides”. |
---|---|
Targeting | Select JEXL with the following JEXL code: user.audience == "AllSpenders" (See Note, below) |
Content | Select Choose content type > Config Overrides, then enter override values for the following keys:
"title" : "Thank you for supporting us!","content" : "This message specifically targets Spender players, thanks to Game Overrides that enables other Unity services to target pre-defined or custom audiences.","attachment" : "","expiration" : "0.00:03:00.00" |
Scheduling | Set the following start and end dates:
|
Status | After finishing creating the Game Override, click Enable |
Details | Name the Override “Messages French Speaker Overrides”. |
---|---|
Targeting | Select JEXL with the following JEXL code: user.audience == "FrenchSpeakers" (See Note, below) |
Content | Select Choose content type > Config Overrides, then enter override values for the following keys:
"title" : "Oh oui, le message!","content" : "Et oui, ce message est en français car il cible les joueurs dont la langue du jeu est paramétrée en français. Ceci est rendu possible grâce à Game Overrides qui permet à certains services de Unity de cibler des audiences pré-définies ou personnalisées.","attachment" : "","expiration" : "0.00:03:00.00" |
Scheduling | Set the following start and end dates:
|
Status | After finishing creating the Game Override, click Enable |
Details | Name the Override “Messages New Players Overrides”. |
---|---|
Targeting | Select JEXL with the following JEXL code: user.audience == "NewPlayers" (See Note, below) |
Content | Select Choose content type > Config Overrides, then enter override values for the following keys:
"title" : "Welcome to the game!","content" : "This message specifically targets New Players, thanks to Game Overrides that enables other Unity services to target pre-defined or custom audiences.","attachment" : "MESSAGE_003_GIFT_NEW_PLAYERS","expiration" : "0.00:10:00.00" |
Scheduling | Set the following start and end dates:
|
Status | After finishing creating the Game Override, click Enable |
Details | Name the Override “Messages Unengaged Players Overrides”. |
---|---|
Targeting | Select JEXL with the following JEXL code: user.audience == "UnengagedPlayers" (See Note, below) |
Content | Select Choose content type > Config Overrides, then enter override values for the following keys:
"title" : "Welcome back to the game!","content" : "This message specifically targets Unengaged players, thanks to Game Overrides that enables other Unity services to target pre-defined or custom audiences.","attachment" : "MESSAGE_004_GIFT_UNENGAGED","expiration" : 0.00:10:00.00" |
Scheduling | Set the following start and end dates:
|
Status | After finishing creating the Game Override, click Enable |
Note: This sample determines which Game Override data should be returned based on a JEXL match with the audience value specified in the client. This is so that the use case can fake a player being in different audiences on demand. In a real app, the Game Overrides would likely be set up to use built-in or custom-defined Analytics audiences for targeting (i.e. during Game Override's targeting step, choose Stateful (Audiences)
and check the appropriate analytics audience from the list or click Build a new Audience
).
Publish the following scripts in the LiveOps dashboard:
Script | Parameters | Description | Location in project |
---|---|---|---|
InGameMailbox_ClaimAttachment |
messageId STRING The id of the message that owns the attachment the player wants to claim. |
Fetches the appropriate attachment for the given message, validates that the attachment hasn't already been claimed, calls Economy's process purchase method for that Virtual Purchase, and marks the attachment as claimed. | Assets/Use Case Samples/In-Game Mailbox/Config as Code/InGameMailbox_ClaimAttachment.js |
InGameMailbox_ClaimAllAttachments |
none | Gets the list of messages in a player's inbox, finds all messages that have an unclaimed attachment, and calls Economy's process purchase method for each Virtual Purchase, marking each message as read and attachment as claimed. | Assets/Use Case Samples/In-Game Mailbox/Config as Code/InGameMailbox_ClaimAllAttachments.js |
Configure the following resources in the LiveOps dashboard:
Resource type | Resource item | ID | Custom Data | Description |
---|---|---|---|---|
Currency | Gem | GEM |
{ "spriteAddress": "Sprites/Currency/Gem" } |
A premium currency gifted by some messages. |
Currency | Coin | COIN |
{ "spriteAddress": "Sprites/Currency/Coin" } |
A soft currency gifted by some messages. |
Inventory Item | Sword | SWORD |
{ "spriteAddress": "Sprites/Inventory/Sword" } |
An inventory item gifted by some messages. |
Inventory Item | Shield | SHIELD |
{ "spriteAddress": "Sprites/Inventory/Shield" } |
An inventory item gifted by some messages. |
In addition, configure the following virtual purchases:
Item name | ID | This purchase rewards | This purchase costs |
---|---|---|---|
Message 003 Gift for New Players | MESSAGE_003_GIFT_NEW_PLAYERS |
Sword (1), Shield (1), Coin (100) | none |
Message 004 Gift for Unengaged Players | MESSAGE_004_GIFT_UNENGAGED |
Gem (50) | none |
Message 005 Gift | MESSAGE_005_GIFT |
Coin (50) | none |
Message 008 Gift | MESSAGE_008_GIFT |
Coin (100), Gem (50) | none |
Message 010 Gift | MESSAGE_010_GIFT |
Gem (50) | none |
Message 011 Gift | MESSAGE_011_GIFT |
Sword (1) | none |
* There is no cost associated with these Virtual Purchases because they are all gifts to the player reading the message.
Set up the following config values in the LiveOps dashboard:
Key | Type | Description | Value |
---|---|---|---|
MESSAGES_ALL |
JSON | The JSON list of all possible message ids that exist in the Remote Config dashboard, listed in the order they should be downloaded in. | {"messageList" : [
"MESSAGE_002", "MESSAGE_003", "MESSAGE_004", "MESSAGE_005", "MESSAGE_006", "MESSAGE_007", "MESSAGE_008", "MESSAGE_009", "MESSAGE_010", "MESSAGE_011" |
MESSAGE_001 |
JSON | One of the messages that a player could receive. | {"title" : "","content" : "","attachment" : "","expiration" : "0.00:03:00.00" |
MESSAGE_002 |
JSON | One of the messages that a player could receive. | {"title" : "","content" : "","attachment" : "","expiration" : "0.00:03:00.00" |
MESSAGE_003 |
JSON | One of the messages that a player could receive. | {"title" : "","content" : "","attachment" : "","expiration" : "0.00:03:00.00" |
MESSAGE_004 |
JSON | One of the messages that a player could receive. | {"title" : "","content" : "","attachment" : "","expiration" : "0.00:03:00.00" |
MESSAGE_005 |
JSON | One of the messages that a player could receive. | {"title" : "Got new Use Case sample ideas?","content" : "We’d love to hear your suggestions about what kind of new samples you would like us to deliver. Let us know at [email protected]","attachment" : "MESSAGE_005_GIFT","expiration" : "0.00:10:00.00" |
MESSAGE_006 |
JSON | One of the messages that a player could receive. | {"title" : "New update coming soon","content" : "Our next update will be released soon and will require a client update from the App Store or Google Play Store. No, that’s a joke. Our samples are only available on Github for now ;)","attachment" : "","expiration" : "0.00:03:00.00" |
MESSAGE_007 |
JSON | One of the messages that a player could receive. | {"title" : "There's cake at the end","content" : "The cake is a lie.","attachment" : "","expiration" : "0.00:03:00.00" |
MESSAGE_008 |
JSON | One of the messages that a player could receive. | {"title" : "The new update is here!!","content" : "We hope you will enjoy the new content the team has prepared for you. On the menu: this brand new In-Game messaging sample and an updated version of the Idle Clicker Game sample that now showcases merging and evolving inventory items.","attachment" : "MESSAGE_008_GIFT","expiration" : "0.00:10:00.00" |
MESSAGE_009 |
JSON | One of the messages that a player could receive. | {"title" : "Where is my mind?","content" : "This is a very uninspired and uninspiring message. Our apologies.","attachment" : "","expiration" : "0.00:03:00.00" |
MESSAGE_010 |
JSON | One of the messages that a player could receive. | {"title" : "Server Downtime","content" : "Our servers were offline for 2 hours yesterday for unexpected reasons. Please accept our apologies and this gift as a compensation. Of course, this is only for illustration purposes; UGS Use Case samples are never offline!","attachment" : "MESSAGE_010_GIFT","expiration" : "0.00:10:00.00" |
MESSAGE_011 |
JSON | One of the messages that a player could receive. | {"title" : "It's dangerous to go alone","content" : "Take this!","attachment" : "MESSAGE_011_GIFT","expiration" : "0.00:10:00.00" |