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:
- Initializing and starting a game instance
- Building, updating, or deleting custom DataStore data for game instance and game players
- Adding more players, reconnecting players, or removing players from a game instance
- Ending a game instance or setting an expiration policy
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 theOn*
action-handlers are unbound. You must callinitialize()
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 #
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.
Create an instance of the
GameInstanceApi.kt
class in a Pragma plugin or custom service:private val gameInstanceApi: GameInstanceApi = GameInstanceApi(service)
Call the
GameInstanceApi.kt
class’screate()
method with anExtBackendCreateRequest
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 #
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:
- Match parties in the matchmaking queue for details on generating a
NewGameInstance
object and adding parties. - Match parties with an active game instance for details on generating a
GameInstanceUpdate
objects and adding parties.
From the backend #
Players can be added directly to an existing game instance via the GameInstanceApi.kt
backend class’s addPlayers()
method:
- Create an instance of the
GameInstanceApi.kt
Kotlin class in a Pragma plugin or custom service:
private val gameInstanceApi: GameInstanceApi = GameInstanceApi(service)
- 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:
- GameInstanceService_UnknownGameInstanceId
- GameInstanceService_GameInstanceAlreadyInMatchmaking
- PartyService_FailedToEnterMatchmaking
- GameInstanceService_FailedToLeaveMatchmaking
- GameInstanceService_PlayerJoinFailed
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 datadata
- data to be storedplayerSync
- 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 theGameInstancePlugin
Player
: Data is sent to a specific player client in the game instanceParty
: Data is sent to player clients for all players in a specific party in the game instanceTeam
: Data is sent to player clients for all players on a specific team in the game instancePublic
: 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 #
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 #
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.
- Create an instance of the
GameInstanceApi.kt
Kotlin class in a Pragma plugin or custom service:
private val gameInstanceApi: GameInstanceApi = GameInstanceApi(service)
- Call the class’s
update()
method with the game instance ID andExtBackendUpdateRequest
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 #
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.
- Create an instance of the
GameInstanceApi.kt
class in a Pragma plugin or custom service:
private val gameInstanceApi: GameInstanceApi = GameInstanceApi(service)
- 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:
- GameInstanceService_UnknownGameInstanceId
- GameInstanceService_PlayerNotInGameInstance
- GameInstanceService_LeaveFailed
End a game instance #
End a game instance for all players
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.