Appearance
Implementing Your Own Game Server Connection
How to integrate the MetaplaySDK connection into your own state manager.
Appearance
How to integrate the MetaplaySDK connection into your own state manager.
To make use of the shared logic and features of the Metaplay SDK, you need to be able to connect to the server and further manage your game's state. Although the Hello World sample comes with an ApplicationStateManager implemented, you'll probably want to integrate the connection logic into your own state manager.
As a starting point, we recommend using the ApplicationStateManager that is included in your default Metaplay integration to connect to the server. However, if you already have your own state management system, then there are a few steps you need to go through to connect to the Metaplay server.
The first thing you need to do is to define and initialize the MetaplayClient class.
/// <summary>
/// Declare concrete MetaplayClient class for type-safe access to player context from game code.
/// </summary>
public class MetaplayClient : MetaplayClientBase<PlayerModel>
{
}Next, we need to choose whether to use async loop for lifecycle management or callbacks. For async loop, we use ConnectAsync() and for callback-oriented management we use IMetaplayLifecycleDelegate. This page described both approaches.
In async control loop, we implement an async method that performs connecting, error handling, scene changes and reconnecting with a signle linear flow. We start by initializing SDK and launching the control loop.
public class StateManager : MonoBehaviour
{
/// <summary>
/// Game-specific initialization code
/// </summary>
public void Init()
{
MetaplayClient.Initialize(new MetaplayClientOptions
{
}
_ = SessionLoop();
}
async Task SessionLoop()
{
while (true)
{
// We'll fill this later.
}
}
}Next, let's start filling the control loop. We want to connect to the server, and while connecting show a loading screen. We implement this by calling and awaiting for ConnectAsync():
async Task SessionLoop()
{
while (true)
{
// Show loading screen
await SceneManager.LoadSceneAsync("Loading Scene");
// Connect to the server
MetaplaySession session;
try
{
session = await MetaplayClient.ConnectAsync();
}
catch (FailedToStartSessionException ex)
{
// TODO: show error and wait until user has seen it before retrying
// await ShowConnectionErrorPopup(ex.Failure);
continue;
}
}
}After establishing a connection, we have received the player's game state from the server. We display the state by switching to the Game scene.
async Task SessionLoop()
{
while (true)
{
// session = await MetaplayClient.ConnectAsync();
// ...
// Start game session
try
{
// As an example, hook up to updates in PlayerModel.
PlayerModel player = (PlayerModel)session.PlayerContext.Model;
player.ClientListener = this;
// Session is now available, so go to game scene.
await SceneManager.LoadSceneAsync("Game Scene");
session.SessionStartComplete();
}
catch (Exception ex)
{
session.SessionStartFailed(ex);
// TODO: show error from SessionStartFailed return value and wait until
// user has seen it before retrying
// await ShowConnectionErrorPopup(lostEvent);
continue;
}
}
}We have now connected to the server, and made game display the ongoing game state. Now, the only thing remaining is handling the session ending. Session end if connection is lost and it can no longer be resumed, for example if the application is put on the background for long time. We can await for this event with WaitForSessionEndAsync().
async Task SessionLoop()
{
while (true)
{
// session.SessionStartComplete();
// ...
ConnectionLostEvent connectionLost = await session.WaitForSessionEndAsync();
if (connectionLost.AutoReconnectRecommended)
{
// For certain errors, we auto-reconnect straight away without
// prompting the player. Note that AutoReconnectRecommended is
// just a suggestion by the SDK and is based on the type of the
// error. The game does not have to obey the suggestion.
}
else
{
// Otherwise, show the connection error popup, with info text
// and a reconnect button. Despite losing the session, the game
// scene will linger until the player clicks on the reconnect
// button. PlayerModel is still available so that the game scene
// can continue to access it. It will remain available until
// the reconnection starts.
// TODO: await ShowConnectionErrorPopup(connectionLost);
}
}
}We have now implemented a control loop that Connects to the game server when connection is lost, loads the correct scene on each state transition and straightforward error handling.
In callback-oriented control loop, we're implementing the IMetaplayLifecycleDelegate interface, which enables you to receive callbacks when a session has been established, lost, or failed for any reason. First, we need to hook the LifecycleDelegate in initialization.
public class StateManager : MonoBehaviour, IMetaplayLifecycleDelegate
{
/// <summary>
/// Game-specific initialization code
/// </summary>
public void Init()
{
MetaplayClient.Initialize(new MetaplayClientOptions
{
// Hook all the lifecycle and connectivity callbacks back to this class.
LifecycleDelegate = this,
// Check out the other members from MetaplayClientOptions,
// these are optional but have useful settings
// or provide useful information.
});
}
}Next, we're ready to establish the connection. When the game is ready to connect (when a loading screen is visible for the user, for example), just call MetaplayClient.Connect().
public class StateManager : MonoBehaviour, IMetaplayLifecycleDelegate
{
...
public void OnLoadingScreenBecameVisible()
{
MetaplayClient.Connect();
}
}Then, we use IMetaplayLifecycleDelegate.OnSessionStartedAsync() to detect when session has been established.
Finally, we recommend that you set a ClientListener in the state manager. ClientListeners are used by models and actions to communicate changes from the shared game logic code into the Unity world: for example, you can use them to update the UI or other visuals.
public class StateManager : MonoBehaviour, IMetaplayLifecycleDelegate
{
...
/// <summary>
/// A session has been successfully negotiated with the server.
/// At this point, we also have the relevant state initialized
/// on the client, so we can move on to the game state.
/// </summary>
Task IMetaplayLifecycleDelegate.OnSessionStartedAsync(IMetaplayLifecycleDelegate.SessionStartedArgs args)
{
PlayerModel player = (PlayerModel)args.Session.PlayerContext.Model;
// Hook up to updates in PlayerModel.
// This can be the same class,
// however it has to implement `IPlayerModelClientListener`
player.ClientListener = this;
// Continue with your loading sequence here.
// At this point, the player state is available.
// For example, the following are now valid:
// Access player state members: MetaplayClient.PlayerModel.CurrentTime
// Execute player actions: MetaplayClient.PlayerContext.ExecuteAction(..);
return Task.CompletedTask;
}
}You can learn more about listeners on the Client and Server Listeners page. It's also noteworthy that server listeners exist and can be used on the server to trigger server-only actions and other behaviors.