Game Services
Overview
A game service is a global, independent system which aims to solve one major problem / feature requirement in the game. All game services implement IGameService
and there is always exactly one instance per service in use. All services live throughout the application lifetime; some of them rely on other services.
Network
The Network
service maintains the connection to the Quantum application in a specific region and provides an extra layer on top of LoadBalancingClient
of Photon Realtime.
The connection is maintained with a best-effort approach; as soon as the network client gets disconnected, the service tries to reconnect. The Network
service only starts and stops upon receiving an explicit request.
API
Connect()
: connects to Photon Realtime with a specific AppID, Version, Region, UserIDDisconnect()
: disconnects from Photon RealtimePause()
: pauses the service and keeps the connection alive by sending ack packets on the background thread. This has to be called before loading a scene.Unpause()
: resumes the sending and receiving of commands from theNetwork
service. This has to be called after a scene has finished loading.
Messages
The Messages
service maintains connection to the Photon Chat application in a specific region and provides an extra API for sending and receiving typed messages. The connection is maintained with a best-effort approach; as soon as the network client gets disconnected, the service tries to reconnect. The Messages
service only starts and stops upon receiving an explicit request.
The servises comes with support for 2 types message from which new type can be derived:
PrivateMessage
, this message is intended to be sent directly to a specific Photon User.ChannelMessage
, this message is intended to be sent / shared with an entire Photon Chat channel.
API
Connect()
: connects to Photon Chat with a specific AppID, Version, Region, UserIDDisconnect()
: disconnects from Photon ChatSubscribe()
: subscribes to a specific Photon Chat channelUnsubscribe()
: unsubscribes from a specific Photon Chat channelSetProperties()
: sets the properties of a specific Photon Chat channelAddFriends()
: receive status updates for these playersRemoveFriends()
: stop receiving status updates for these playersCanChatInChannel()
: returns true if you can chat in a specific Photon Chat channelTryGetChannel()
: returns Photon Chat channelSendMessage()
: sends a private or channel message to a receiverFlush()
: immediately send all outgoing commandsPause()
: pauses the service and keeps the connection alive by sending ack packets on the background thread. This has to be called before loading a scene.Unpause()
: resumes the sending and receiving of commands from theNetwork
service. This has to be called after a scene has finished loading.
Example
This section present several examples on use the Messages
service.
Private Message
C#
IMessage message = new PrivateMessages.Text("This is a private message");
Game.Services.Messages.SendMessage(message, "targetUserID");
Channel Message
C#
IMessage message = new ChannelMessages.Text("This is a channel message");
Game.Services.Messages.SendMessage(message, "targetChannelName");
Custom Message definition
Custom message definitions can be created by deriving from one of the base classes (PrivateMessage
or ChannelMessage
). The snippet below shows how to create a new type deriving from ChannelMessage
.
C#
public static partial class CustomMessages
{
public sealed class Emote : ChannelMessage
{
public string EmoteID { get; private set; }
public Emote(string emoteID) {
EmoteID = emoteID;
}
private Emote() {}
protected override object Serialize() {
return EmoteID;
}
protected override void Deserialize(object data) {
EmoteID = (string)data;
}
}
}
Lobby
The Lobby
service relies on the Messages
service and is backed by a Photon Chat channel. The service provides a connection to a single lobby identified by a string
. Players can send lobby messages and set their own custom properties in the LobbyPlayerData
structure which is available to all other players in the same lobby.
Note: A single lobby is limited to 1000 players.
API
Join()
: joins a specific lobby. If no lobby is specified it will join the default lobby, or create it if does not already exist.Leave()
: leaves current lobbySetSynchronizationInterval()
: sets the interval at which players synchronize the lobby's data and state.GetPlayerData()
: returnsLobbyPlayerData
of a specific playerGetLocalPlayer()
: returns localLobbyPlayer
SendMessage()
: sends a lobby message (must be a type deriving fromChannelMessage
)
Party
The Party
service relies on the Messages
service and is backed by a Photon Chat channel. The service provides a basic party system. The party is identified by a string
. You can send party messages and set custom player properties in the PartyPlayerData
structure which is available to all other players in the same party. The player who creates the party can also set the maximum size of it.
The party always has one party leader (by default the player who creates the party). The party leader is the player who maintains it. The leader can promote another players (and demote themself) at any time. If the leader gets disconnected, other players will immediately try to take the leadership.
Note: The party system is fully client driven and the code is not burdened with security checks. Keep this in mind as it makes the solution cheatable.
API
AutoAcceptJoinRequest
: automatically joins the party received in aJoin
message. True by default.AllowRemotePlayerDataChange
: allows to change other players data. False by default.PlayerInviteTimeout
: timeout of a pending invite request. 15s by default.PlayerKickTimeout
: the length of time until a disconnected player is kicked from the party. 30s by default.Create()
: creates a party with specific party IDJoin()
: joins a party with specific party IDLeave()
: leaves current partyInvite()
: invites another player to the party. The target player must have connected via theMessages
service to receive the invitation.Kick()
: kicks a specific player from the partySetLeader()
: promotes another player to leaderSetPlayerSlot()
: sets party slot of a specific playerExchangePlayerSlots()
: exchange party slots of 2 specific players atomicallyGetPlayerData()
: returnsPartyPlayerData
of a specific playerGetLocalPlayer()
: returns localPartyPlayer
SendMessage()
: sends a party message (must be a type deriving fromChannelMessage
)
PartyPlayer Properties
UserID
: unique for each playerStatus
: connected / disconnectedSlot
: unique position within the partyData
: instance of PartyPlayerData with custom properties
MatchMaking
The Matchmaking
service relies on Network
and Messages
services, is responsible for finding a specific / random match based on given parameters and keeps the player connected to the match with best-effort approach. The service operates with Match
, MatchRequest
and MatchConfig
data structures.
API
MatchRequest
Mode
: match request mode, currently supported are Join, Create, JoinOrCreate, JoinRandom, CreateRandom, JoinOrCreateRandomRoom
: name of the room, mandatory for Join, Create and JoinOrCreate modesPlugin
: name of the Photon Realtime server plugin, default is “QuantumPlugin”IsOpen
: initial open state of the room, default is trueIsVisible
: initial visibility state of the room, default is trueIsSpectator
: set if the joining player is spectating (not participating in gameplay), default is falseAutoStart
: if true, the match will be automatically started if the room is full, there are at least [MinStartPlayers] non-spectating players or after [Timeout], default is trueMinStartPlayers
: minimum non-spectating players for the match to be automatically started (only if [AutoStart] is true), default is 0 (all players required)ExpectedPlayers
: number of expected players participating in gameplay, mandatory for Create, JoinOrCreate, CreateRandom and JoinOrCreateRandom modesExtraSlots
: number of extra slots for players not participating in gameplay, default is 0MatchTTL
: how long empty room stays alive before destroyed, default is 0PlayerTTL
: how long player stays inactive in the room before disconnected, default is 0FillTimeout
: time limit for filling all room spots, the match is automatically started when reached (only if [AutoStart] is true), default is 0 (infinite timeout)Config
: MatchConfig instance with custom dataPlayerProperties
: custom player properties which are set upon joining the roomExpectedRoomProperties
: only rooms with these properties will be selected, valid for JoinRandom and JoinOrCreateRandom modesRoomProperties
: custom properties which the room will be created with, valid for Create, JoinOrCreate, CreateRandom and JoinOrCreateRandom modesRoomPropertiesForLobby
: list of property keys, properties with these keys will be exposed to all users in Realtime lobbyExpectedUserIDs
: list of users expected to join the same room, this will reserve spots for all players and find only rooms with enough free spots, typically filled with party members, local player is always set as expected userMatchmakingMode
: defines matchmaking rules, default is FillRoomTypedLobby
: lobby type, default is TypedLobby.DefaultSQLLobbyFilter
: SQL filter used in combination with LobbyType.SqlLobby
Match
UpdateConfig
: callback, executed before the match is started, intended for making final changes inMatchConfig
Connected
: callback, executed when the player connects to a roomReconnected
: callback, executed when the player reconnects to the room after unexpected disconnectDisconnected
: callback, executed on unexpected disconnectUpdated
: callback, executed every frame until the match is startedStarted
: callback executed when the match is startedStart()
: explicit command to start the match, ignored if the match is already started
Matchmaking
Run()
: starts matchmaking process for givenMatch
/MatchRequest
Leave()
: leaves currentMatch
Match Process Flow
Following diagram shows simplified process from match request to match start:
The Matchmaking
service is designed with robustness in mind, the matchmaking process should survive disconnection of any player at any time unless all players are disconnected and the room is disposed. This includes processing of party members. Evaluation of start conditions and decision when the match starts is always done by the master client. He also periodically checks for missing expected users and invites them via private message.
Remote Settings
The RemoteSettings
service allows you to fetch settings from a remote server. It is backed by the Unity Remote Config.
API
IsLoaded
: indicates if the config is already fetched from Remove Config serverLoaded
: callback, executed when the config is fetchedLoad()
: fetch config for given User ID and and Environment IDGetBool()
: returns a bool value from Remote ConfigGetInt()
: returns an int value from Remote ConfigGetFloat()
: returns a float value from Remote ConfigGetString()
: returns a string value from Remote Config
Scene Loader
The SceneLoader
service is responsible for initialization, deinitialization and switching Unity scenes. The FPS Template supports scene switching in the background by additively loading a loading scene in the foreground while the main scene is being loaded in the background.
Following diagram illustrates the process:
SceneManager.LoadSceneAsync(LoadingScene)
: the scene ( LoadingScene ) is additively loaded; nothing should be visible by default to prevent visual glitchesSceneLoadingDirector.Initialize()
: this is used to mark all important object withDontDestroyOnLoad
to prevent an invalid state of the main scene loading at later pointSceneLoadingDirector.Activate()
: in this coroutine the main scene is hidden by an UI or similar effects, the loading process continues after the coroutine finishesSceneDirector.Deinitialize()
: the current scene ( MenuScene ) is deinitializedSceneManager.LoadSceneAsync(GameScene)
: the GameScene is loaded and replaces MenuSceneResources.UnloadUnusedAssets()
: cleanup resources used by MenuSceneSceneDirector.Initialize()
: the current scene ( GameScene ) is initialized. Note: this method is a coroutine, thus allowing to postpone the loading process as long as neededSceneLoadingDirector.Deactivate()
: in this coroutine the loading UI is hidden and the main scene becomes visibleSceneLoadingDirector.Deinitialize()
: cleanup of LoadingScene objects which were marked withDontDestroyOnLoad