Game Instance Tasks #

Game Instance features are built and customized using a combination of calls and plugin methods. This page describes common game instance operations, along with descriptions of SDK actions and related plugin methods.

Tasks include:

For a full list of all available Game Instance SDK and plugin functions, see Game Instance SDK Reference and Game Instance Backend Reference.

Initialize Game Instance SDK (Required) #

Before you can use the Game Instance API, you must initialize the Game Instance SDK using the Initialize method. Calling Initialize will sync the game server cache with the game instance data from the Pragma Engine backend.

Initialize() can only be called after login. Upon logout, the service is un-initialized and the On* action-handlers are unbound. You must call initialize() on the service when you log back in.
Player->GameInstanceApi().Initialize(
  const FOnCompleteDelegate& OnComplete
);
Player.GameInstanceApi.Initialize(
  CompleteDelegate onComplete
);

After you receive a successful response, you can begin using the Game Instance API.

While the Pragma SDK constantly attempts to stay in sync with the Pragma Engine backend, there may be instances where the two components become out of sync due to connection issues. To forcibly synchronize the player client’s game instance data with the platform’s game instance data, call the Game Instance API ForceSync function.

Start a game instance #

Generate from matchmaking #

New game instances are created when the Matchmaking service determines that a set of parties should be sent to a game instance together (see Match Parties). The Matchmaking service creates a NewGameInstance object, which includes party and player information, as well as extra game information that will be used to create the game instance.

Add initial players #

When the Matchmaking service creates a NewGameInstance object, players in the associated parties are automatically added to the game instance. All game instance players will receive the OnAddedToGameInstance event with the GameInstance object. See Add more players to see how to add players to existing game instances.

When the game instance is linked with an allocated game server, the players receive the OnHostConnectionDetailsReceived event, which includes host information.

Set initial data and allocate servers #

The Game Instance Plugin’s handleBackendCreateByMatchmakingRequest() method is automatically called when a game instance is created out of matchmaking. Use this method to perform tasks that need to happen as soon as a game instance is created, such as establishing ExtData (see Create ExtData), or allocating a game server for the game instance (see Allocate a dedicated game server). By default, handleBackendCreateByMatchmakingRequest() calls allocateGameServer() with a default ExtAllocateGameServer payload, as shown below:

interface GameInstancePlugin {
  suspend fun handleBackendCreateByMatchmakingRequest(
      gameInstance: GameInstance.GameInstance
  ) {
      gameInstance.allocateGameServer(ExtAllocateGameServer.getDefaultInstance())
  }
}

Build custom data #

Manage data on the game instance and game players

Using the Game Instance DataStore and PlayerSync features, you can create and manage custom data about a game instance and its players. You can handle data requests coming from the player, game server, or backend separately.

Define ExtData message #

You define custom game instance and game player details in the ExtData message in your gameInstanceExt.proto file.

Example usage:

In the following example, ExtData includes information on game phase and player points. Because ExtData holds data for both game instances and players, we’re using the oneof keyword to ensure only one value is set at a time. We expect the gameInstance.dataStore to use the game_phase field, and the gameplayer.dataStore to use the player_points field.

message ExtData {
  oneof my_data {
    string game_phase = 1;
    int32 player_points = 2;
  }
}

Create ExtData #

Use the the DataStore create() method to create ExtData for a game instance or game player. Create() accepts the following parameters:

  • key - unique string used to reference the data

  • data - data to be stored

  • playerSync - how the data should be synced with players in the game instance, if at all:

    • Hidden: Data is not synced with any player/party/team and is accessible only from the GameInstancePlugin
    • Player: Data is sent to a specific player client in the game instance
    • Party: Data is sent to player clients for all players in a specific party in the game instance
    • Team: Data is sent to player clients for all players on a specific team in the game instance
    • Public: Data is sent to all players clients in the game instance
interface DataStore {
    fun create(
        key: String,
        data: ExtData,
        playerSync: PlayerSync = PlayerSync.Hidden
    ): ExtData
}

The data store create() method can be called by any game instance plugin method.

Example usage:

As an example, say we want to set a game phase and a player point value as soon as a game instance is created from matchmaking. We can use the ExtData message defined above in the Game Instance Plugin’s handleBackendCreateByMatchmakingRequest() method, which is called automatically from the Matchmaking service when a game instance is created.

In our example, we set the game instance game phase to “loading” and each game player’s player points to 0. Notice that the game phase value is available to everyone (PlayerSync.Public), while the player points value is only available to the specific game player (PlayerSync.Player).

interface GameInstancePlugin {
  
  suspend fun handleBackendCreateByMatchmakingRequest(
      gameInstance: GameInstance.GameInstance
  ) {
    
    val startingGamePhaseExtData = ExtData.newBuilder().setGamePhase("loading").build()
    val startingPlayerPointsExtData = ExtData.newBuilder().setPlayerPoints(0).build()

    gameInstance.dataStore.create(
      "gamePhase", startingGamePhaseExtData, PlayerSync.Public)
    gameInstance.players.forEach {
      it.dataStore.create(
        "playerPoints", startingPlayerPointsExtData, PlayerSync.Player(it.playerId))
    }

  }
}

Get custom data #

Use the DataStore get() method to fetch data by key value:

interface DataStore{
  fun get(key: String): ExtData?
}

Update custom data #

Use the DataStore updateData() method to update ExtData for a game instance or game player. UpdateData() accepts a key identifying the value to update, as well as the ExtData itself.

interface DataStore{
  fun updateData(key: String, data: ExtData)
}

The data store updateData() method can be called any game instance plugin method. The following examples call updateData() from the plugin’s handlePlayerUpdateRequest() and handleBackendUpdateRequest() methods, allowing player clients, game clients, and backend processes to request updates to ExtData values.

Update from a player client #

Update ExtData from player client

Player client updates ExtData using Game Instance Api

To request game instance data updates from the player client, use the Game Instance API’s Update() SDK method with the ExtPlayerUpdateRequest payload, which describes how the game instance data should be updated. Players can only request updates for games instances they are in.

Player->GameInstanceApi()->Update(
  const FPragma_GameInstance_ExtPlayerUpdateRequest& RequestExt, 
  const FOnCompleteDelegate& OnComplete
)
Player.GameInstanceApi.Update(
  ExtPlayerUpdateRequest requestExt, 
  CompleteDelegate onComplete
)

When called, the GameInstanceApi.Update() SDK method invokes the Game Instance Plugin’s handlePlayerUpdateRequest() method.

Example usage:

We can customize handlePlayerUpdateRequest() to update the ExtData with the ExtPlayerUpdateRequest:

suspend fun handlePlayerUpdateRequest(
    gameInstance: GameInstance.GameInstance,
    requestingPlayer: GameInstance.GamePlayer,
    requestExt: ExtPlayerUpdateRequest
) {

    val currentPlayerPointsExtData: ExtData = requestingPlayer.dataStore.get("playerPoints")
    requestingPlayer.dataStore.updateData(
      "playerPoints", currentPlayerPointsExtData.playerPoints + requestExt.pointsToAdd)
}

Update from game server or backend #

Update ExtData from backend

Requests from game servers or backed Pragma processes are handled with the Game Instance Plugin’s handleBackendUpdateRequest() method. We can customize this method to update the ExtData with the ExtBackendUpdateRequest provided by MatchApi.UpdateGameInstance() (game server method) or the GameInstanceApi.kt Kotlin class’s update() method (Pragma backend method):

From game server:

To request game instance updates from the game server, use the Match API’s UpdateGameInstance() SDK method with the ExtBackendUpdateRequest payload, which describes how the game instance data should be updated.

Server->MatchApi()->UpdateGameInstance(
  const FString& GameInstanceId,
  const FPragma_GameInstance_ExtBackendUpdateRequest& RequestExt,
  const FOnCompleteDelegate& OnComplete
);
Server.MatchApi.UpdateGameInstance(
  PragmaId gameInstanceId
  ExtBackendUpdateRequest requestExt,
  CompleteDelegate onComplete
);

From backend:

The GameInstanceApi.kt backend class is the entrypoint to the game instance service from other backend services or plugins. To request game instance data store updates through these channels, use the GameInstanceApi.kt update() method.

First, create an instance of the GameInstanceApi.kt Kotlin class in a Pragma plugin or custom service:

private val gameInstanceApi: GameInstanceApi = GameInstanceApi(service)

Then, call the class’s update() method with the game instance ID and ExtBackendUpdateRequest payload.

class GameInstanceApi(
    private val service: Service,
) {
    suspend fun update(
        gameInstanceId: GameInstanceId,
        backendUpdateRequest: ExtBackendUpdateRequest,
    ): PragmaResult<Unit, PragmaFailure> 
}

Customize handleBackendUpdateRequest:

Both the and the MatchApi.UpdateGameInstance() and the GameInstanceApi class’s update() method invoke the Game Instance Plugin’s handleBackendUpdateRequest() method. We can customize this method to update the ExtData with the ExtBackendUpdateRequest.

Example usage:

For example, say you want the game server to be able to advance the game instance’s game phase. You can call the game instance’s DataStore updateData() method from the Game Instance Plugin’s handleBackendUpdateRequest() method. The game server will send the new game phase data on the requestExt.

interface GameInstancePlugin {

  suspend fun handleBackendUpdateRequest(
    gameInstance: GameInstance.GameInstance,
    requestExt: ExtBackendUpdateRequest
  ) {
      gameInstance.dataStore.updateData(
        "gamePhase", requestExt.newGamePhase)
  }
}

If the data is synced to a player, creating or updating the data store will trigger the GameInstanceApi OnGameInstanceUpdated event on the player’s game client once the plugin completes, along with the created data.

Update sync #

In addition to updating the ext data on the data store, you can update the PlayerSync value to change data visibility. To do so, use the DataStore updateSync() method with the ext key value and new sync option (Hidden, Player, Party, Public):

interface DataStore{
  fun updateSync(
      key: String,
      playerSync: PlayerSync
  )
}

Update game instances in matchmaking #

If the game instance being updated is currently in matchmaking, all of the matchmaking ext payloads for the game instance will be recalculated and sent to matchmaking to ensure the matchmaking representation of the game instance is up to date. See Add more players for more information about which plugins are invoked during this operation.

Related events:

Related errors:

Delete custom data #

Use the DataStore delete() method to delete data from the data store by key value:

interface DataStore{
  fun delete(key: String)
}

Add more players #

Add more players to your existing game instance

Add more players

Game instances can enter matchmaking to accept more players. When a game instance enters matchmaking, the Game Instance Matchmaking Plugin builds customized data about the game instance (ExtMatchmakingGameInstance), the parties within it (ExtMatchmakingGameParty), and the players within it (ExtMatchmakingGamePlayer).

suspend fun buildExtMatchmakingGameInstance(
    gameInstance: GameInstance.GameInstance
): ExtMatchmakingGameInstance
  
suspend fun buildExtMatchmakingGameParty(
    gameInstance: GameInstance.GameInstance,
    party: GameInstance.GameParty
): ExtMatchmakingGameParty

suspend fun buildExtMatchmakingGamePlayer(
    gameInstance: GameInstance.GameInstance,
    party: GameInstance.GameParty,
    player: GameInstance.GamePlayer
): ExtMatchmakingGamePlayer

The data in these ext payloads will be made available to the Matchmaking Plugin’s matchPartiesWithGame() function via the Matchmaking.GameInstance object when comparing game instances. (See Match parties with an active game instance).

If, after creating a Game Instance Update object, you want the Matchmaking Game Instance to leave matchmaking, use GameInstanceUpdate.stopMatchmaking. See Leave matchmaking as a game instance for specifics.

Related events

Related errors:

Reconnect players #

Reconnect disconnected players to the same game instance

Pragma allows players to reconnect to their game instance after disconnecting from the engine. This feature can be configured via the GameInstanceServiceConfig.reconnect config value:

valuedescription
OFF(Default) When the player disconnects from Pragma Engine, the player is removed from their game instance.
OPTIONALWhen the player disconnects from Pragma Engine, they remain in the game instance. When the player reconnects to the engine, they have the option to call the DeclineReconnect() SDK method, which will remove them from the game instance.
REQUIREDWhen the player disconnects from Pragma Engine, they remain in the game instance. When the player reconnects to the engine, they don’t have the option of calling the DeclineReconnect() SDK method.

To check if a reconnected player is still in a game instance, use the GameInstanceApi.GetGameInstanceCache() SDK method. This method will also return host connection details so the player client can connect to the appropriate game server.

If the player is reconnecting to the engine due to logging out, call GetGameInstanceCache() immediately after GameInstanceApi.Initialize() to check if the player is still in a game instance.

Related events

Related errors:

Remove players #

Remove one or more players from the current game instance

Remove players from game

The game server can call MatchApi.removePlayers to remove one or more players from an active game instance. Game results and metrics can be stored in the ExtRemovePlayersRequest payload (for game-specific data) and the ExtRemovePlayer payload in the list of players to remove (for player-specific data).

Server->MatchApi()->RemovePlayers(
  const FString& GameInstanceId,
  const TArray<FPragma_GameInstance_PlayerToRemove>& Players,
  const FPragma_GameInstance_ExtRemovePlayersRequest& Ext,
  const FOnCompleteDelegate& OnComplete
)
Server.MatchApi.RemovePlayers(
  PragmaId gameInstanceId, 
  IEnumerable<PlayerToRemove> players,
  ExtRemovePlayersRequest ext, 
  CompleteDelegate onComplete
)
{
  "requestId": 19,
  "type": "gameInstanceRpc.RemovePlayersV1Request",
  "payload": {
    "game_instance_id": "1",
    "players": {},
    "ext":{}
  }
}

MatchApi.RemovePlayers implements the Game Instance Plugin’s onRemovePlayers() method. This method is used to prepare any custom data (ExtRemovedFromGame) to send to the removed players. By default, onRemovePlayers() includes each removed player in the returned map.

suspend fun onRemovePlayers(
    gameInstance: GameInstance.GameInstance,
    playersToRemove: List<PlayerToRemove>,
    requestExt: ExtRemovePlayersRequest
): Map<PlayerId, ExtRemovedFromGame> {
    return playersToRemove.associate { it.playerId to ExtRemovedFromGame.getDefaultInstance() }
}

Removed players will receive the onRemovedFromGameInstance event with the ExtRemovedFromGame payload.

Upon removal, the removed players will appear in the removedPlayers list on the GameInstance.GameInstance. If the same players are added back into the game instance via matchmaking, they will be removed from the removedPlayers list and appear within the players list as a part of the new party that they joined the game with.

MatchApi.RemovePlayers also invokes the Party Plugin’s returnFromGameInstance method and provides a list of the specific players being removed from the game. See Party Service Tasks: Return from a game instance for more information.

Players removed from the game instance before the game ends can still be included in end game processes and receive end game data.

Related events:

Related errors:

End a game instance #

End a game instance for all players

End game

Game instances are not ended until the Game Instance interface’s end() method is called. This method can be called directly from any Game Instance plugin implementation, or indirectly via the Match API.

interface GameInstance {
  fun end(
    extMap: Map<PlayerId, ExtGameEnded>
  )
}

When end of game information has been successfully processed, and the game instance has ended, players will receive the onGameEnded event with the ExtGameEnded payload that relates to them.

Example 1: End a game instance when all players are removed

In default implementations of the GameInstancePlugin, the plugin’s onEndGame() method calls GameInstance.end(). However, you may want to end a game through other means. For example, to end a game instance as soon as all players have been removed, call GameInstance.end() from the onRemovePlayers() plugin method:

suspend fun onRemovePlayers(
    gameInstance: GameInstance.GameInstance,
    playersToRemove: List<PlayerToRemove>,
    requestExt: ExtRemovePlayersRequest
): Map<PlayerId, ExtRemovedFromGame> {

    //if last player in game
    gameInstance.end(mapOf())

    return mapOf()
}

Example 2: End a game instance through the MatchApi

To facilitate ending a game instance from the game server side, the game server can call MatchApi.EndGame() using the SDK for Unreal or Unity. MatchApi.EndGame() invokes GameInstancePlugin.onEndGame(), which, by default, calls GameInstance.end().

Server->MatchApi()->EndGame(
  const FString& GameInstanceId,
  const TArray<FPragma_GameInstance_PlayerGameResult>& PlayerGameResults,
  const FPragma_GameInstance_ExtEndGameRequest& Ext,
  const FOnCompleteDelegate& OnComplete
)
Server.MatchApi.EndGame(
  PragmaId gameInstanceId, 
  IEnumerable<PlayerGameResult> playerGameResults,
  ExtEndGameRequest ext, 
  CompleteDelegate onComplete
)
{
  "requestId": 20,
  "type": "gameInstanceRpc.EndGameV1Request",
  "payload": {
    "game_instance_id": "1",
    "player_game_results": {},
    "ext":{}
  }
}

Related events:

Related errors:

Set game instance expiration #

Set a game instance to automatically expire

You can configure game instances to automatically expire after a certain duration (measured from the last time the game instance is accessed). Game instances will be removed after they have not been accessed for the full duration of the expiration. This includes actions such as player/partner client synchronization operations.

By default, the expiration feature is enabled with an expiration time of one day. If you change the configuration value, any existing game instances that have not been accessed during the expiration duration will be removed. You can disable expiration functionality or change the time limit in the GameInstanceServiceConfig block of your yaml file:

game:
  serviceConfigs:
    GameInstanceServiceConfig:
      enableStaleGameInstanceExpiration: true
      staleGameInstanceExpirationMinutes: 1440
Once enabled, it could take up to five minutes for the configuration to take effect.