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.

Create a game instance #

Create a game instance

Create a game instance from a player client, matchmaking, or custom service

You can create a new game instance in the following ways:

  • from the player client SDK for Unreal/Unity
  • automatically from the Matchmaking service when a suitable match is found
  • directly from the Pragma backend, bypassing the Matchmaking service

After a new game instance is created, you can set data and allocate servers.

From player client #

Player clients can create a game instance directly from the Game Instance API SDK for Unreal and Unity, making custom game creation easy.

A player can only be in one game instance at a time.
Player->GameInstanceApi()->Create(
  const FPragma_GameInstance_ExtPlayerCreateRequest& RequestExt, 
  const FOnCompleteDelegate& OnComplete
)
Player.GameInstanceApi.Create(
  ExtPlayerCreateRequest requestExt, 
  CompleteDelegate onComplete
)

Handle player create requests:

The SDK method invokes the Game Instance Plugin’s handlePlayerCreateRequest() method, which you can customize to handle game instance and player data. To prevent unnecessary or unintended game instance creation, the method prevents a player from creating a game instance by default.

Example usage:

suspend fun handlePlayerCreateRequest(
    gameInstanceSnapshot: GameInstance.GameInstance,
    requestingPlayerId: PlayerId,
    requestExt: ExtPlayerCreateRequest
) {
    gameInstanceApplicationError(
        PragmaError.GameInstanceService_CreateFailed,
        "Player $requestingPlayerId cannot create game instance.")
}

From matchmaking #

When the Matchmaking service determines that a set of parties should be sent to a game instance together, it 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. See Match parties in the matchmaking queue for more information on customizing matchmaking logic and generating a new game instance.

From the backend #

Backend services can create a game instance via the GameInstanceApi.kt backend class’s create() method. This workflow bypasses the matchmaking process.

  1. Create an instance of the GameInstanceApi.kt class in a Pragma plugin or custom service:

    private val gameInstanceApi: GameInstanceApi = GameInstanceApi(service)
    
  2. Call the GameInstanceApi.kt class’s create() method with an ExtBackendCreateRequest payload, players to add to the game instance, a game server version, and game server zone.

    suspend fun create(
        requestExt: ExtBackendCreateRequest,
        playersToAdd: Map<PlayerId, ExtBackendAddPlayer>,
        gameServerVersion: GameServerVersion,
        gameServerZone: GameServerZone,
    ): PragmaResult<GameInstanceId, PragmaFailure>
    

Handle backend create requests:

The GameInstanceApi class’s create() method invokes the Game Instance Plugin’s handleBackendCreateRequest() method. 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, the provided players are added to the game instance and a server is allocated to host the game instance.

Example usage:

  suspend fun handleBackendCreateRequest(
      gameInstanceSnapshot: GameInstance.GameInstance,
      requestExt: ExtBackendCreateRequest,
      playersToAdd: Map<PlayerId, ExtBackendAddPlayer>
  ) {
      playersToAdd.forEach { (playerId, _) ->
          gameInstanceSnapshot.addPlayer(playerId)
      }

      gameInstanceSnapshot.allocateGameServer(ExtAllocateGameServer.getDefaultInstance())
  }

To learn how to handle game server allocation failures, see Handle Allocation Failures.

Add players #

Add players to a game instance

Add players to a game instance via a player client, matchmaking service, or custom service

You can add players to a game instance in the following ways:

  • from the player client SDK for Unreal/Unity
  • from the Matchmaking service when a new game instance is created
  • from the Matchmaking service when an existing game instance enters matchmaking to receive more players
  • directly from the Pragma backend, bypassing the Matchmaking service

When players are added to a game instance, you can forward player data to the game instance.

A player can only be in one game instance at a time.

From player client #

Player clients can join an existing game instance using the Game Instance API Join() method with the game instance ID and a ExtPlayerJoinGameInstanceRequest payload.

Player->GameInstanceApi()->Join(
  const FString& GameInstanceId, 
  const FPragma_GameInstance_ExtPlayerJoinGameInstanceRequest& RequestExt,
  const FOnCompleteDelegate& OnComplete
)
Player.GameInstanceApi.Join(
  PragmaId gameInstanceId, 
  ExtPlayerJoinGameInstanceRequest requestExt,
  CompleteDelegate onComplete
)

Handle player client join requests #

The SDK method invokes the Game Instance Plugin’s handlePlayerJoinRequest() method, which you can customize to handle player data. To prevent unnecessary or unintended player additions, the method prevents a player from joining the game instance by default.

suspend fun handlePlayerJoinRequest(
    gameInstanceSnapshot: GameInstance.GameInstance,
    requestingPlayerId: PlayerId,
    requestExt: ExtPlayerJoinGameInstanceRequest
) {
    gameInstanceApplicationError(
        PragmaError.GameInstanceService_PlayerJoinFailed,
        "Player $requestingPlayerId cannot join game instance ${gameInstanceSnapshot.id}.")
}

From matchmaking #

When the Matchmaking service creates a NewGameInstance or a GameInstanceUpdate object, players in the associated parties are automatically added to the game instance. See the following matchmaking tasks for more information:

From the backend #

Players can be added directly to an existing game instance via the GameInstanceApi.kt backend class’s addPlayers() method:

  1. Create an instance of the GameInstanceApi.kt Kotlin class in a Pragma plugin or custom service:
private val gameInstanceApi: GameInstanceApi = GameInstanceApi(service)
  1. Call the GameInstanceApi.kt class’s addPlayers() method with the game instance ID, ExtBackendAddPlayersRequest payload, and list of players to add to the game instance.
suspend fun addPlayers(
    gameInstanceId: GameInstanceId,
    requestExt: ExtBackendAddPlayersRequest,
    playersToAdd: Map<PlayerId, ExtBackendAddPlayer>
): PragmaResult<Unit, PragmaFailure>

Handle backend add players requests #

When adding players to a game instance from the backend, you have a chance to perform tasks such as verifying players or forwarding player data to the game instance using the Game Instance Plugin’s handleBackendAddPlayersRequest() method.

Example usage:

For example, say you know exactly what players should be added to a specific existing game instance, and you need to forward their character selections to the game instance. You can customize the handleBackendAddPlayersRequest() method to use the player’s ExtBackendAddPlayersRequest to populate their game instance ExtData.

override suspend fun handleBackendAddPlayersRequest(
    gameInstanceSnapshot: GameInstance.GameInstance,
    requestExt: ExtBackendAddPlayersRequest,
    playersToAdd: Map<PlayerId, ExtBackendAddPlayer>
) {

    playersToAdd.forEach { (playerId, playerExt) ->
        val gamePlayer = gameInstanceSnapshot.addPlayer(playerId)

        val playerCharacterExtData = ExtData.newBuilder().setCharacter(
            playerExt.selectedCharacter
        ).build()

        gamePlayer.dataStore.create(
            "character", playerCharacterExtData,
            PlayerSync.Player(playerId)
        )
    }
}

See Connect players in the Game Server Tasks topic to learn how to connect players to their game instance’s host server.

Related events

Related errors:

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 a custom service. We can use the ExtData message defined above in the Game Instance Plugin’s handleBackendCreateRequest() method, which is invoked when a game instance is created from a backend service.

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).

override suspend fun handleBackendCreateRequest(
    gameInstanceSnapshot: GameInstance.GameInstance,
    requestExt: ExtBackendCreateRequest,
    playersToAdd: Map<PlayerId, ExtBackendAddPlayer>
) {

    val startingGamePhaseExtData = ExtData.newBuilder().setGamePhase(
        "loading"
    ).build()

    val startingPlayerPointsExtData = ExtData.newBuilder().setPlayerPoints(
        0
    ).build()

    gameInstanceSnapshot.dataStore.create(
        "gamePhase", startingGamePhaseExtData, PlayerSync.Public
    )

    playersToAdd.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 by 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.

From 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(
    gameInstanceSnapshot: GameInstance.GameInstance,
    requestingPlayer: GameInstance.GamePlayer,
    requestExt: ExtPlayerUpdateRequest
) {

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

From game server or backend #

Update ExtData from backend

Game server and backend services update ExtData using MatchApi SDK or backend GameInstanceApi class, and Game Instance plugin

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:

Backed processes and services can request to update game data via the GameInstanceApi.kt backend class’s update() method.

  1. Create an instance of the GameInstanceApi.kt Kotlin class in a Pragma plugin or custom service:
private val gameInstanceApi: GameInstanceApi = GameInstanceApi(service)
  1. Call the class’s update() method with the game instance ID and ExtBackendUpdateRequest payload:
suspend fun update(
    gameInstanceId: GameInstanceId,
    backendUpdateRequest: ExtBackendUpdateRequest,
): PragmaResult<Unit, PragmaFailure> 

Customize handleBackendUpdateRequest:

Both the MatchApi.UpdateGameInstance() method 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(
    gameInstanceSnapshot: GameInstance.GameInstance,
    requestExt: ExtBackendUpdateRequest
  ) {
      gameInstanceSnapshot.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)
}

Remove players #

Remove players from a game instance

Remove players from a game instance via a player client, custom service, or game server

You can remove players from a game instance in the following ways:

  • from the player client SDK for Unreal/Unity
  • from the game server SDK for Unreal/Unity
  • directly from the Pragma backend
A game instance does not automatically end when all players are removed.

From player client #

Players can request to leave a game instance using the Game Instance API’s leave() method.

Server->GameInstanceApi()->Leave(
  const FPragma_GameInstance_ExtPlayerLeaveRequest& RequestExt, 
  const FOnCompleteDelegate& OnComplete
)
Server.GameInstanceApi.Leave(
  ExtPlayerLeaveRequest requestExt, 
  CompleteDelegate onComplete
)

Handle player leave requests

The Game Instance API’s Leave() method invokes the Game Instance Plugin’s handlePlayerLeaveRequest() method. To prevent unintended removal, the method does not remove the player from the game instance by default.

  suspend fun handlePlayerLeaveRequest(
      gameInstanceSnapshot: GameInstance.GameInstance,
      requestingPlayer: GameInstance.GamePlayer,
      requestExt: ExtPlayerLeaveRequest
  ) {
      gameInstanceApplicationError(
          PragmaError.GameInstanceService_LeaveFailed,
          "Player ${requestingPlayer.playerId} cannot leave game instance.")
  }

From the game sever #

To remove one or more players from an active game instance as the game server, call MatchApi.removePlayers(). Game results and metrics can be stored in the ExtBackendRemovePlayersRequest payload.

Server->MatchApi()->RemovePlayers(
  const FString& GameInstanceId,
  const TArray<FPragma_GameInstance_BackendPlayerToRemove>& Players,
  const FPragma_GameInstance_ExtBackendRemovePlayersRequest& Ext,
  const FOnCompleteDelegate& OnComplete
)
Server.MatchApi.RemovePlayers(
  PragmaId gameInstanceId,
  IEnumerable<BackendPlayerToRemove> players,
  ExtBackendRemovePlayersRequest ext,
  CompleteDelegate onComplete
)

MatchApi.RemovePlayers() implements the Game Instance Plugin’s handleBackendRemovePlayersRequest() method.

From the backend #

Players can be removed from a game instance via the GameInstanceApi.kt backend class’s removePlayers() method.

  1. Create an instance of the GameInstanceApi.kt class in a Pragma plugin or custom service:
private val gameInstanceApi: GameInstanceApi = GameInstanceApi(service)
  1. Call the class’s removePlayers() method with the game instance ID, ExtBackendRemovePlayersRequest payload, and list of players to remove from the game instance.
suspend fun removePlayers(
    gameInstanceId: GameInstanceId,
    requestExt: ExtBackendRemovePlayersRequest,
    playersToRemove: Map<PlayerId, ExtBackendRemovePlayer>
): PragmaResult<Unit, PragmaFailure>

The GameInstanceApi class’s removePlayers() method invokes the Game Instance Plugin’s handleBackendRemovePlayersRequest() method, which you can customize to handle player data. By default, the provided players are removed from the game instance.

Example usage:

For example, say you want to let a player know why they are being removed from the game instance. Use the handleBackendRemovePlayersRequest() method to forward the “reason” information on the ExtBackendRemovePlayerRequest to the player’s ExtRemovedFromGame.

override suspend fun handleBackendRemovePlayersRequest(
    gameInstanceSnapshot: GameInstance.GameInstance,
    requestExt: ExtBackendRemovePlayersRequest,
    playersToRemove: Map<PlayerId, ExtBackendRemovePlayer>
) {

    playersToRemove.forEach { (playerId, playerExt) ->
        val ext = ExtRemovedFromGame.newBuilder().setReason(
          requestExt.reason
        ).build()

        gameInstanceSnapshot.removePlayer(playerId, ext)
    }
}

Related events:

Related errors:

End a game instance #

End a game instance for all players

End game

Game server uses API to end a game instance (default implementation)

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. By default, the GameInstancePlugin’s onEndGame() method calls GameInstance.end().

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

To end a game instance as soon as all players have been removed, call GameInstance.end() from the handleBackendRemovePlayersRequest() plugin method:

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

    if (gameInstanceSnapshot.players.isEmpty()) {
        gameInstanceSnapshot.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 reading or modifying game instance state or game player state, and 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.