Appearance
Time and Ticks
This page describes how time advances in the server, the player model, and other models in general.
Appearance
This page describes how time advances in the server, the player model, and other models in general.
Ticks are the main method the Metaplay SDK uses to keep track of time and for time-related mechanics.
The player model keeps track of time using PlayerModel.CurrentTime
, that is initially set by the server on session start. From this point onward, advancing the clock is the responsibility of the client, but this is done automatically, and you don't need to add anything to your game-specific code.
The client advances this clock by running Ticks, where every Tick moves the time a bit forward by a set amount. If the client pauses (if the app is put on the background, for example), it will not execute any code and will not update the Ticks. From the server's point of view, is like the client's time has stopped.
When the game returns from the paused state, the client will attain the time difference from the device's time and catch up on the Ticks it missed. The server will check for client activity periodically and kick the client if the game is paused for too long. Usually this means a minute or two, but this can be changed. If you want to pause the game for longer without being kicked, you can use the SDK's OnApplicationAboutToBePaused
API.
Long Pauses
The client is expected to keep the model's time relatively close to the true world time. If the client is paused for too long or doesn't update Ticks frequently enough, the server will kick the client and end the session. To allow the client to pause for a longer duration, you may call MetaplaySDK.OnApplicationAboutToBePaused()
to hint to the server a long pause is to be expected. This is useful for example when the client is about to show a fullscreen ad or a login window that pauses the game.
The rate of ticking for each model can be configured with Model.TicksPerSecond
. Higher Tick rate can improve the fidelity of simple physics simulations or the responsivity of time based logic. For example, if a progress bar of an unlockable item is updated on the UI every Tick, a low Tick rate will result in the bar moving in a jumpy and unsmooth manner. A high Tick rate, however, increases the CPU, memory usage, and network traffic for both the client and the server, and shouldn't be used except in specialized cases. Generally, the Tick rate of 10 or less is sufficient for most needs. To avoid janky and jumpy UI like in the loading bar example mentioned above, you should consider interpolationg the UI components that have time-related state. This find the time length between the model's last Tick and the current time you can use MetaTime.Now - Model.CurrentTime
.
Initially, when the session starts, the server updates the PlayerModel.CurrentTime
to the current server time and calls Model.GameFastForwardTime()
to update the model's state. To preserve resources, a model's Ticks are not run when a player is offline. When a player connnects to the server or has their player model inspected on the dashboard, the server uses GameFastForwardTime
to run the Ticks equivalent to the time elapsed since the end of the last session.
Game logic may implement the method to update any time related changes, such as unlocking time-gated features. As long as there is an ongoing session, the client will automatically advance its clock by running Ticks, and every Tick moves the time a bit forward. For each Tick, Model.GameTick()
is called to update any time-related game logic.
When the session ends, the player actor is subsequently shut down. When the player actor is shut down, it will update one last time to have it's CurrentTime
match the server's, and again call Model.GameFastForwardTime
. As with the session start, this is to allow the game logic to perform any time related operations correctly.
As an example, let's say the player has a treasure box that can be claimed only after some time has passed, or by using a premium currency like gold. We'll store the contents and timestamp for the unlock and implement the following:
[ModelAction(1001)]
public class SpeedUpAction : PlayerAction
{
public override MetaActionResult Execute(PlayerModel player, bool commit)
{
int price = ...; // todo: compute price for speed up
if (player.NumGold < price)
return ActionResult.NotEnoughGold;
if (commit)
{
player.NumGold -= price;
player.TreasureBoxUnlocksAt = player.CurrentTime;
}
return ActionResult.Success;
}
}
[ModelAction(1002)]
public class ClaimTreasureAction : PlayerAction
{
public override MetaActionResult Execute(PlayerModel player, bool commit)
{
if (commit)
{
player.TreasureBox.Consume(player, null);
}
return ActionResult.Success;
}
}
class PlayerModel
{
[MetaMember(110)] public MetaPlayerReward<PlayerModel> TreasureBox;
[MetaMember(111)] public MetaTime TreasureBoxUnlocksAt;
protected override void GameTick(IChecksumContext checksumCtx)
{
}
protected override void GameFastForwardTime(MetaDuration elapsedTime)
{
}
}
Notably in this example we didn't need any timer update logic in GameTick()
or GameFastForwardTime()
. We used a timestamp for the unlock instead of counting down a duration, and since the claiming is a user-triggered action, no time based logic is needed either. If we wanted to make the Treasure Box to be automatically claimed when the timer reached 0, we would remove the ClaimTreasureAction
action and update the logic as follows:
class PlayerModel
{
protected override void GameTick(IChecksumContext checksumCtx)
{
// On each Tick, check if the treasure box is open
UpdateTreasureBox();
}
protected override void GameFastForwardTime(MetaDuration elapsedTime)
{
// On every time we fast forward time, also check if the treasure box is open
UpdateTreasureBox();
}
void UpdateTreasureBox()
{
if (TreasureBox != null && CurrentTime >= TreasureBoxUnlocksAt)
{
TreasureBox.Consume(this, null);
TreasureBox = null;
}
}
}