Single-player Matches
Matches started in the single-player mode take place entirely locally, on the player's device. Since one game instance acts as both a server and a client, there is no real server authority. It also means that creation of such match and its result are not reported to external backend servers or included in tournament leaderboards. Single-player matches also don't affect player's high score. At the same time a match in this mode can start almost immediately, with no need to wait for external game server allocation and connection.
Editor
The single-player mode can be used to create matches in deployed games, but it can also be tested in editor. To start a single-player match in editor, select it as your current development mode and enter Play mode with your gameplay scene loaded. This is the easiest way to see if your game works in the single-player mode. For any adjustments that may be necessary to make your game compatible with that mode, see the section on adding support.
Implementation
Starting a match
To start a single-player match, all you have to do is call Elympics.ElympicsLobbyClient.PlaySinglePlayer
while in lobby.
This will load the gameplay scene and start the match.
Adding support
As long as your gameplay logic follows some simple rules and guidelines, you should be able to use the same code for both regular online matches and local single-player matches.
IsServer vs. IsClient
A common assumption when working with code that should only be executed on client or on server is that in an ElympicsMonoBehaviour
component the expression Elympics.IsServer == !Elympics.IsClient
will always evaluate to true
.
In the single-player mode both IElympics.IsServer
and IElympics.IsClient
are true, which can lead to unexpected behavior in code that was written without keeping that in mind.
For example with the following code:
void Shoot()
{
SpawnBullet();
Debug.Log("Bullet spawned.)
if (Elympics.IsServer)
return;
PlayBulletSoud();
SpawnBulletSFX();
}
sound and special effects will not be present in a single-player match.
To fix that kind of issue simply replace Elympics.IsServer
with !Elympics.IsClient
:
void Shoot()
{
SpawnBullet();
Debug.Log("Bullet spawned.)
if (!Elympics.IsClient)
return;
PlayBulletSoud();
SpawnBulletSFX();
}
Callbacks
In the single-player mode relevant callbacks from both the IServerHandlerGuid
and the IClientHandlerGuid
interface are called.
In a standard playthrough, you can expect to receive the following callbacks:
IServerHandlerGuid
OnServerInit
OnPlayerConnected
IClientHandlerGuid
OnClientsOnServerInit
OnConnected
OnAuthenticated
OnMatchJoined
OnMatchEnded
OnDisconnectedByServer
Keep in mind that IClientHandlerGuid.OnStandaloneClientInit
will not be called in the single-player mode.
You can use that fact to avoid double initialization of some systems which could happen in your code when it receives callbacks for both server and client.
RPCs
Remote Procedure Calls invoked in the single-player mode in any direction (player to server or server to player) are executed instantly.
This allows you to maintain a normal flow of game logic, but it could also cause some actions to become duplicated, for example if you used an RPC to synchronize state between client and server.
Assume the following code is placed in an ElympicsMonoBehaviour
component.
public GameObject target;
public void ElympicsUpdate()
{
if (Elympics.IsClient && Input.GetKeyDown(KeyCode.Space))
{
DestroyLocally();
DestroyOnServer();
}
}
[ElympicsRpc(ElympicsRpcDirection.PlayerToServer)]
void DestroyOnServer() => DestroyLocally();
void DestroyLocally()
{
target.Destroy();
target = null;
}
This code will work in regular matches, but in the single-player mode it will end up calling DestroyLocally
twice, which will lead to NullReferenceException
.
In cases like this, you have to make sure that the instance of the game which is executing this code is not a server:
public GameObject target;
public void ElympicsUpdate()
{
if (Elympics.IsClient && Input.GetKeyDown(KeyCode.Space))
{
DestroyLocally();
if (!Elympics.IsServer)
DestroyOnServer();
}
}
[ElympicsRpc(ElympicsRpcDirection.PlayerToServer)]
void DestroyOnServer() => DestroyLocally();
void DestroyLocally()
{
target.Destroy();
target = null;
}