Appearance
Appearance
MetaPlayerReward
s implemented - If you have not implemented MetaPlayerReward
s in your game yet, have a look at Implementing MetaRewards.In-Game Mail refers to messages and resources you can send players through the LiveOps Dashboard.
The Metaplay SDK Mail system consists of PlayerMailItem
s, representing the Mail containers, MetaInGameMail
s, representing Mail contents. The base container implementation keeps track of whether the mail has been consumed or read, and the contents consist of localized title and body strings, and a list of rewards to be given to the receiving player.
Both the container and contents can be extended or overridden with code. This way, you can give some Mails special characteristics, like being able to be consumed multiple times. You can also keep more than one implementation of each, and chose what type of mail to send on a case-by-case basis, using the dashboard.
Each Mail has a Consume
method that you can call using the PlayerConsumeMail
player action, which is part of the core PlayerModel
.
For most games, you will probably want to implement a "minimum viable" version of sending and consuming In-Game Mails first. This basic implementation lets you understand how Mails work in Metaplay and immediately use them to gift resources to players using the LiveOps Dashboard.
After this implementation, you will be able to:
Metaplay's default PlayerModel
ships with an inbox that can hold PlayerMailItem
s. You can test that it works (and that your custom MetaPlayerReward
s are implemented correctly) by accessing a player in the LiveOps Dashboard and using the "Send Mail" action.
Now that you can create Mails with the payload you want, you need to be able to consume them on the client. The easiest way to do so is to add a small piece of code in your game's update loop that checks if any Mails are available and call the PlayerConsumeMail
accordingly.
Here's an example implementation:
// If there's any Mail...
if (MetaplayClient.PlayerModel.MailInbox.Count > 0)
{
var mailItem = MetaplayClient.PlayerModel.MailInbox[0]; // Pick the first one
switch (mailItem.Contents) // Check the Mail contents type
{
// Sanity checking that we know what the Mail contents is and
// thus know what to do with it.
case SimplePlayerMail mail:
// Log the Mail title to the console
Debug.Log("Received mail with title: " + mail.Title.Localize());
// Consume and delete the Mail with actions
MetaplayClient.PlayerContext.ExecuteAction(new PlayerConsumeMail(mail.Id));
MetaplayClient.PlayerContext.ExecuteAction(new PlayerDeleteMail(mail.Id));
break;
default:
// Unknown Mail type -> log errors?
break;
}
}
This code would immediately attempt to consume any Mails as soon as they become available. Good enough for, say, a soft launch where you just need a quick way to give resources to players!
A small but high bang-for-buck addition would be to implement a custom Mail screen in your game to display the Mail before the player claims it.
Looking at the above code snippet, we could replace the ExecuteAction()
call with your own OpenMailWindow(mail)
or a similar function to display the Mail and a 'claim' button to consume it accordingly.
The SimplePlayerMail
content type included with Metaplay SDK implements the message title and body as LocalizedString
, a special string container with multiple localizations of a string. Mails that arrive on the client are, by default, already localized on the server so that the client can call LocalizedString.Localize()
to turn it into a string.
string mailTitle = mail.Title.Localize();
string mailBody = mail.Body.Localize();
🚨 Be Careful!
Just using the Localize()
method will return a string in the game's default language, not necessarily the player's. To have it return the player's selected language, use Localize(MetaplayClient.PlayerModel.Language.LanguageId)
.
The PlayerMailItem
Mail wrapper tracks if a Mail has been read and if it has been consumed. Instead of consuming and deleting individual Mails as they arrive, you could implement a fully-featured inbox GUI that lists the current Mails in the inbox and uses the provided PlayerConsumeMail
and PlayerToggleMailIsRead
actions for modifying the state of Mail items in the inbox.
Metaplay has a default implementation of the abstract MetaInGameMail
class called SimplePlayerMail
that contains
Title
localized string.Body
localized string.MetaPlayerRewardBase
objects as attachments.This implementation is a good starting point for simple communication needs, but providing your own Mail content format is also fully supported. The recommended way of doing this is to introduce a class that derives from MetaInGameMail
as part of your game logic code.
Add a class to the project anywhere under Assets/SharedCode/
. In this example, we'll create an even simpler Mail format that has just a string field:
[MetaSerializableDerived(2)] // Inform the serialization system about this class
[MetaReservedMembers(0, 100)] // Reserve tagIds 0..99 for this
public class MyPlayerMail : MetaInGameMail
{
[MetaValidateRequired]
[MetaMember(1)] public string MyMailString { get; private set; }
public override string Description => MyMailString; // Description to show in the dashboard
[MetaDeserializationConstructor]
public MyPlayerMail(string myMailString) { MyMailString = myMailString; }
}
See Deep Dive: Data Serialization for a description of the serialization attributes used here. The [MetaValidateRequired]
attribute is used in the dashboard to tell the form that this field is required before the Mail can be sent.
To add rewards to your Mail, you can override the base class's ConsumableRewards
property, an IEnumerable
of type MetaPlayerRewardBase
. Adding your rewards like this makes it so that when the player consumes the Mail, its ConsumableRewards
are consumed automatically. Take a look at Implementing MetaRewards if you need a refresher on implementing your custom reward types. In this Mail content type, we also make the rewards optional by overriding the MustBeConsumed
property and returning false.
[MetaSerializableDerived(3)] // Inform the serialization system about this class
[MetaReservedMembers(0, 100)] // Reserve tagIds 0..99 for this
public class MyPlayerMailWithRewards : MetaInGameMail
{
[MetaValidateRequired]
[MetaMember(1)] public string MyMailString { get; private set; }
[MetaMember(2)] public List<MetaPlayerRewardBase> Rewards { get; private set; }
public override string Description => MyMailString; // Description to show in the dashboard
public override IEnumerable<MetaPlayerRewardBase> ConsumableRewards => Rewards; // Return rewards list
public override bool MustBeConsumed => false; // Make the rewards optional
// An empty or deserialization constructor (see Deep Dive: Data Serialization for more info) is needed
public MyPlayerMailWithRewards() { }
}
Open the LiveOps Dashboard and click on the “Send Mail” button to bring up the Mail form. Fill in the fields and click “Send” to send off your Mail.
After sending, ensure that the new Mail appears correctly in the player’s inbox.
If you don’t like the look of the automatically generated form or the inbox item, you can override both with custom dashboard components. Learn more about the generated forms system at Working with Generated Dashboard UI.
A player Mail consists of the Mail contents, deriving from MetaInGameMail
, which is contained inside an envelope, deriving from PlayerMailItem
. In addition, the wrapper includes information about whether the Mail has been read or consumed, as well as MetaTime
fields recording when those actions have happened. Most customization can be done by introducing new content types, but sometimes one might want to customize the Mail wrapper itself.
Add a class to the project anywhere under Assets/SharedCode/
. Let’s make a Mail item type with rewards that a player can consume multiple times.
[MetaSerializableDerived(1)]
[MetaReservedMembers(0, 100)]
public sealed class MyCustomMailItem : PlayerMailItem
{
public new MyPlayerMailWithRewards Contents => (MyPlayerMailWithRewards) base.Contents;
[MetaMember(1)] public int TimesConsumed;
public override void ConsumeRewards(IPlayerModelBase player)
{
foreach (MetaPlayerRewardBase consumable in Contents.ConsumableRewards)
consumable.InvokeConsume(player, rewardSourceProvider.DeclareMailRewardSource(Contents));
TimesConsumed++;
if (TimesConsumed >= Contents.MaxConsumedAmount)
{
HasBeenConsumed = true;
ConsumedAt = player.CurrentTime;
}
}
public MyCustomMailItem() { }
public MyCustomMailItem(MyPlayerMailWithRewards contents, MetaTime sentAt) : base(contents, sentAt) { }
}
In this Mail type, we’ve added a new TimesConsumed
property, which keeps track of how many times the Mail has been consumed. In addition, we’ve specified the content type to be MyPlayerMailWithRewards
and added a new integer property called MaxConsumedAmount
.
To create our new MyCustomMailItem
s, we need to override the default Mail creation logic. This can be done by overriding the InGameMailIntegration
class and creating our Mail type in the overridden MakePlayerMailItem
method.
public class MyInGameMailIntegration : InGameMailIntegration
{
public override PlayerMailItem MakePlayerMailItem(MetaInGameMail contents, MetaTime sentAt)
{
if (contents is MyPlayerMailWithRewards rewardsContent)
return new MyCustomMailItem(rewardsContent, sentAt); // return our custom Mail item for our own contents type
// otherwise use default implementation
return base.MakePlayerMailItem(contents, sentAt);
}
}
Next, we can add custom handling for the custom Mail format in the update loop:
// If there's any Mail...
if (MetaplayClient.PlayerModel.MailInbox.Count > 0)
{
var mailItem = MetaplayClient.PlayerModel.MailInbox[0]; // Pick the first one
if(mailItem is MyCustomMailItem customMailItem)
{
// Do something cool here...
// Delete Mail
MetaplayClient.PlayerContext.ExecuteAction(new PlayerDeleteMail(customMailItem.Id));
}
else // mailItem is DefaultPlayerMailItem
{
switch (mailItem.Contents) // Check the Mail contents type
{
// Sanity checking that we know what the Mail contents is and
// thus know what to do with it.
case SimplePlayerMail simpleMail:
// Log the Mail title to the console
Debug.Log("Received mail with title: {Title}", simpleMail.Title.Localize());
// Consume and delete the Mail with actions
MetaplayClient.PlayerContext.ExecuteAction(new PlayerConsumeMail(simpleMail.Id));
MetaplayClient.PlayerContext.ExecuteAction(new PlayerDeleteMail(simpleMail.Id));
break;
case MyPlayerMail myMail:
// Log my string and delete
Debug.Log("Received my mail with string: {Content}", myMail.MyMailString);
MetaplayClient.PlayerContext.ExecuteAction(new PlayerDeleteMail(myMail.Id));
default:
// Unknown Mail type -> log errors?
break;
}
}
}
And we’re done!
You can find information on how to use the broadcasts system to share content through Mail on Managing Broadcasts.
If you’re interested in customizing the generated dashboard forms and views, take a look at Working with Generated Dashboard UI.