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.
Game instance initialization
Game server tasks
- Allocate a server for a game instance
- Connect players to a game server
- Send custom data to the game server
- Update game instance data
Player management
End of game
For a full list of all available Game Instance SDK and plugin functions, see Game Instance 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.
Start a game instance #
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.
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. By default, handleBackendCreateByMatchmakingRequest
calls allocateGameServer()
with a default ExtAllocateGameServer
payload.
interface GameInstancePlugin {
suspend fun handleBackendCreateByMatchmakingRequest(
gameInstance: GameInstance.GameInstance
) {
gameInstance.allocateGameServer(ExtAllocateGameServer.getDefaultInstance())
}
}
Send custom game data to players #
When a player is added to a game instance, their player client SDK OnAddedToGame
event triggers with the ExtAddedToGame
payload. The buildExtAddedToGame()
function allows for customization of ExtAddedToGame
data to send to the player when they are added to a game. This data will be sent to the players immediately, even if a game server is still preparing to host the game instance.
suspend fun buildExtAddedToGame(
gameInstance: GameInstance.GameInstance,
player: GameInstance.GamePlayer
): ExtAddedToGame
See the Matchmaking Tasks topic for more information on adding parties to game instances.
Related events:
Allocate a dedicated game server #
Allocate a game server to host the game instance
Game server allocation is initiated by the Game Instance interface allocateGameServer()
method.
interface GameInstance {
fun allocateGameServer(ext: ExtAllocateGameServer)
}
GameInstance.allocateGameServer()
can be called from any Game Instance Plugin method. When allocateGameServer()
is called, your Game Server Provider Plugin implementation’s startAllocationForGameInstance()
method is invoked.
suspend fun startAllocationForGameInstance(
gameInstanceId: GameInstanceId,
ext: ExtAllocateGameServer,
clientTokenFunction: () -> PartnerClientTokens,
gameServerVersion: GameServerVersion,
gameServerZone: GameServerZone
)
The plugin implementation and method functionality is determined by your fleet management solution.
Allocate using third-party solution #
If you are using a third-party fleet management solution (such as GameLift, Hathora, or Multiplay), implement the startAllocationForGameInstance
method as follows:
- Send the game instance ID to the game server so the game server can link back to the correct game instance
- Send data from the
ExtAllocateGameServer
payload to the game server. This should be information that the game server needs to allocate the correct server. For example, data may include preferred region or version, or data the game server needs on start, such as a map or game mode. - Send connection tokens to the game server using the
clientTokenFunction
parameter, as shown in the following example. Each game server requires a set of partner tokens, regardless of how many game instances that game server can run at once.
For example:
suspend fun startAllocationForGameInstance(gameInstanceId: GameInstanceId, ext: ExtAllocateGameServer, clientTokenFunction: () -> PartnerClientTokens) {
val token = clientTokenFunction()
val dispatchRequest = mapOf(
META to mapOf(
...
PARTNER_GAME_AUTH_TOKEN to token.gameToken,
PARTNER_SOCIAL_AUTH_TOKEN to token.socialToken,
)
)
}
Allocate using Pragma Fleet service #
Use the FleetGameServerProviderPlugin
implementation of the Game Server Provider Plugin. The ExtAllocateGameServer
is passed to the Fleet service to aid in server pool selection. To do so, edit the GameInstanceService.gameServerProviderPlugin
block of the appropriate config file.
game:
pluginConfigs:
GameInstanceService.gameServerProviderPlugin:
class: "pragma.fleet.FleetGameServerProviderPlugin"
Allocate for local development #
If you are running both Pragma Engine and a game server on a local machine, use the LocalProcessGameServerProviderPlugin
, which is an implementation that will run a game server executable on the local machine. To do so, edit the GameInstanceService.gameServerProviderPlugin
block of your local-dev.yml
file. Include a path to your locally build game server.
game:
pluginConfigs:
GameInstanceService.gameServerProviderPlugin:
class: "pragma.gameinstance.LocalProcessGameServerProviderPlugin"
config:
serverExecutablePath: "C:/path/to/game/server/executable.exe"
serverLogDirectory: "."
Related events:
Related errors:
- GameInstanceService_GameServerAlreadyAllocated
- GameInstanceService_UnknownGameInstanceId
- GameInstanceService_CapacityRequestFailed
Connect players #
Verify and add players to a game instance.
After server allocation, the game server calls MatchApi.ConnectPlayers()
to indicate that it’s ready for players to join the game instance.
Players receive a OnHostConnectionDetailsReceived
notification, which includes host connection details such as host name, port, game instance ID, the ExtPlayerConnectionDetails
payload (which can be used to define custom game server host information), and a unique connection token.
Server->MatchApi()->ConnectPlayers(
const FString& GameInstanceId,
const TArray<FPragma_GameInstance_PlayerConnectionDetails>& PlayerConnectionDetails,
const FString& Hostname,
const int Port,
UPragmaGameInstancePartnerServiceRaw::FConnectPlayersV1Delegate Delegate
);
Server.MatchApi.ConnectPlayers(
PragmaId gameInstanceId,
IEnumerable<PlayerConnectionDetails> playerConnectionDetails,
string hostname,
int port,
Action<Result<ConnectPlayersV1Response>> callback
);
Verify players #
Pragma Engine can use the player connection tokens to verify the correct players join a game instance. The player verification process is optional, but recommended for competitive games.
The player client needs to provide the connection token to the game server upon joining the game instance. The game server then sends the connection token to the engine via MatchApi.VerifyPlayer
so the engine can validate the connection token. If the engine determines the connection token is valid, players can join the game instance.
Server->MatchApi()->VerifyPlayer(
const FString& GameInstanceId,
const FString& PlayerId,
const FString& ConnectionToken,
UPragmaGameInstancePartnerServiceRaw::FVerifyPlayerPartnerV1Delegate Delegate
);
Server.MatchApi.VerifyPlayer(
PragmaId gameInstanceId,
PragmaId playerId,
string connectionToken,
Action<Result<VerifyPlayerPartnerV1Response>> callback
);
A player who disconnects and reconnects to the same game instance can fetch the same connection token with GetGameInstanceCache()
.
Related events:
Related errors:
- GameInstanceService_UnknownGameInstanceId
- GameInstanceService_MissingPlayerConnectionDetails
- GameInstanceService_PlayerNotInGameInstance
Send custom game data to the game server #
Send game and player data to the game server
When an allocated game server links to a game instance, the Game Instance Plugin’s buildExtGameStart
plugin method is invoked once. This method builds the ExtGameStart
payload, which declares custom data to start the game instance.
suspend fun buildExtGameStart(
gameInstance: GameInstance.GameInstance
): ExtGameStart
The Game Instance Plugin’s buildExtGameServerPlayer()
method is also invoked for each player in the game. This method builds the ExtGameServerPlayer
payload, which declares custom data to send to the game server for a player.
suspend fun buildExtGameServerPlayer(
gameInstance: GameInstance.GameInstance,
player: GameInstance.GamePlayer
): ExtGameServerPlayer
Update game instance data #
Update a game instance
Game instance data can be set or updated by various components, allowing you to handle data requests coming from the player, game server, or backend separately.
Update from a 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.
Server->GameInstanceApi()->Update(
const FPragma_GameInstance_ExtPlayerUpdateRequest& RequestExt,
const FOnCompleteDelegate& OnComplete
)
Server.GameInstanceApi.Update(
ExtPlayerUpdateRequest requestExt,
CompleteDelegate onComplete
)
When called, GameInstanceApi.Update()
invokes the Game Instance Plugin’s handlePlayerUpdateRequest()
method. Use this method to validate data arriving from the player client and update the game instance data.
suspend fun handlePlayerUpdateRequest(
gameInstance: GameInstance.GameInstance,
requestingPlayer: GameInstance.GamePlayer,
requestExt: ExtPlayerUpdateRequest
) {
//update game instance data
}
For example, you can allow players to signal they are ready to play and have handlePlayerUpdateRequest()
allocate a dedicated game server when all players are ready.
Update from game server #
To request game instance updates from the game server, use the Match API’s UpdateGameInstance()
SDK method with the ExtBackendUpdateRequest
payload.
Server->MatchApi()->UpdateGameInstance(
const FString& GameInstanceId,
const FPragma_GameInstance_ExtBackendUpdateRequest& RequestExt,
const UPragmaGameInstancePartnerServiceRaw::FUpdateGameInstanceV1Delegate& Delegate
);
Server.MatchApi.UpdateGameInstance(
PragmaId gameInstanceId
ExtBackendUpdateRequest requestExt,
Action<Result<UpdateGameInstanceV1Response>> callback
);
When called, MatchApi.UpdateGameInstance()
invokes the Game Instance Plugin’s handleBackendUpdateRequest()
method. Use this method to update existing game data with information contained in the ExtBackendUpdateRequest
.
suspend fun handleBackendUpdateRequest(
gameInstance: GameInstance.GameInstance,
requestExt: ExtBackendUpdateRequest
) {
//update game instance data
}
For example, you may have a currently running game instance whose matchmaking needs change depending on the game’s state.
Set from backend #
To update game instance data from the backend, 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 GameInstanceApi 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>
}
The update()
method invokes the Game Instance Plugin’s handleBackendUpdateRequest()
method, as shown in Set from game server section above.
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 Enter matchmaking to connect more players for more information about which plugins are invoked during this operation.
Related events:
Related errors:
Add more players #
Add more players to your existing game instance
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 Add parties and players to active game instances).
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:
- GameInstanceService_UnknownGameInstanceId
- GameInstanceService_GameInstanceAlreadyInMatchmaking
- PartyService_FailedToEnterMatchmaking
- GameInstanceService_FailedToLeaveMatchmaking
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:
value | description |
---|---|
OFF | (Default) When the player disconnects from Pragma Engine, the player is removed from their game instance. |
OPTIONAL | When 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. |
REQUIRED | When 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, callGetGameInstanceCache()
immediately afterGameInstanceApi.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
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,
UPragmaGameInstancePartnerServiceRaw::FRemovePlayersV1Delegate Delegate
)
Server.MatchApi.RemovePlayers(
PragmaId gameInstanceId,
IEnumerable<PlayerToRemove> players,
ExtRemovePlayersRequest ext,
Action<Result<RemovePlayersV1Response>> callback
)
{
"requestId": 19,
"type": "gameInstanceRpc.RemovePlayersV1Request",
"payload": {
"game_instance_id": "1",
"players": {},
"ext":{}
}
}
After a player has been removed from the party, the Game Instance Plugin onRemovePlayers
is called. This method is used to prepare any custom data (ExtRemovedFromGame
) to send to the removed players.
suspend fun onRemovePlayers(
gameInstance: GameInstance.GameInstance,
playersToRemove: List<PlayerToRemove>,
requestExt: ExtRemovePlayersRequest
): Map<PlayerId, ExtRemovedFromGame>
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.
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
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,
UPragmaGameInstancePartnerServiceRaw::FEndGameV1Delegate Delegate
)
Server.MatchApi.EndGame(
PragmaId gameInstanceId,
IEnumerable<PlayerGameResult> playerGameResults,
ExtEndGameRequest ext,
Action<Result<EndGameV1Response>> callback
)
{
"requestId": 20,
"type": "gameInstanceRpc.EndGameV1Request",
"payload": {
"game_instance_id": "1",
"player_game_results": {},
"ext":{}
}
}
Related events:
Related errors:
Handle game end failures #
Handle situations where the end game request never arrives
Pragma Engine provides two features to account for situations where the end game request never arrives: Keep Alive Heartbeats and Absolute Timeouts. A game end payload can fail to arrive due to game server crashes, game code bugs, network issues, or malicious actors. These features help release players from a game instance and allow them to reenter the game loop to start a new game instance.
In these failure situations, the player receives an OnGameInstanceTerminated
event, including a reason for shutdown.
When the game is forced to shut down, no end game data is processed.
Related events
Keep alive heartbeats #
The Keep Alive Heartbeats feature requires game servers to send occasional heartbeats after starting a game instance. This allows Pragma Engine to confirm that the game instance and game server are still operational.
When enabled, Pragma Engine expects a MatchKeepAliveV1
request based on the configured amount of time (keepAliveIntervalMillis
). If the engine doesn’t receive the request after a consecutive set of the configured amount of missed values (keepAliveMissesLimit
), it ends the game instance and returns players to their party, calling the Party Plugin returnFromGameInstance
method.
Game servers using the Pragma Engine-provided Unreal and Unity SDKs automatically send heartbeats to Pragma Engine on a successful response of the ConnectPlayers
method when this feature is enabled. It does this by starting a background process that makes the necessary MatchKeepAliveV1
requests every keepAliveIntervalMillis
while the match is progress.
The configuration for this feature can be found in GameInstanceServiceConfig
. This feature is enabled by default, with a 30000
milliseconds (30 seconds) value for keepAliveIntervalMillis
in production environments, and a keepAliveMissesLimit
value of 3
.
A separate keepAliveIntervalMillis
configuration for development in dev-defaults.yml
is set to 5000
milliseconds (5 seconds). To modify these values, edit the local-dev.yml
file.
To disable this feature, set the GameInstanceServiceConfig.enableKeepAlive
configuration property to false
.
As a best practice, we recommend against turning off the Keep Alive Heartbeats feature, as it’s critical for game flow health.
Absolute timeouts #
The Absolute Timeouts feature makes Pragma Engine end a game instance after a specified period of time elapses.
To enable this feature, set the GameInstanceServiceConfig.enableAbsoluteTimeout
configuration property to true
and provide a value for GameInstanceServiceConfig.absoluteTimeoutMillis
.
Once enabled, Pragma Engine starts a background task that runs after a configured amount of time (absoluteTimeoutMillis
) passes. If a game instance is still in progress when the background task runs, the engine ends the game instance and returns players to their party, calling the Party Plugin returnFromGameInstance
method.
This process releases the game instance from the Game Instance Service. The game server itself may still need to end or cleanup the game, and/or shutdown.
Related events
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 time the Matchmaking service creates the game instance). This configuration is set system-wide and can be used as a backstop to prevent situations where a game instance isn’t properly ended due to bugs or connection issues.
By default, the expiration feature is enabled, with an expiration time of seven days. After seven days the game instance will be removed, regardless of game instance activity. If current game instances already passed the configured time, they will be immediately 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: 10080
Once enabled, it could take up to five minutes for the configuration to take effect.