Skip to main content

Client Relayed Multiplayer

The real-time multiplayer engine makes it easy for users to set up and join matches where they can rapidly exchange data with opponents. This relayed multiplayer (also known as client-authoritative) model is suitable for many game types, such as simple 1vs1 or co-op games, where authoritative control on the server is not important (e.g. cheating is a non-factor).

In relayed multiplayer, LayerG facilitates the exchange of data regardless of message size or content. Any data sent through a match is immediately routed to the client requested match opponents. The only match data maintained by LayerG are the match ID and list of presences in that match.

Client messages destined for the other connected clients are forwarded by the server without inspection. Since LayerG does not track the amount or content of the data forwarded in a relayed multiplayer match, there is no cheat detection, error correction, or other such functionality available. This approach relies on one client in each match, decided upon by the clients themselves, to act as the host. This host will reconcile state changes between peers and perform arbitration on ambiguous or malicious messages sent from bad clients.

Any user can participate in matches with other users, there is no ability to password protect or otherwise restrict access to a match. There is no explicit limit on the number of players in a match, only the practical limits imposed by your game design (the message size and frequency) and available resources (server hardware and network capability).

Users can create, join, and leave matches with messages sent from clients.

Matches exists on the server only until the last participant has left. They are kept in-memory and cannot be persisted.

Create a match

A new match can be created by any user by explicitly calling the “match create” operation. No match state can be provided by the creator when creating a match.

The server will assign a unique ID to the new match. This ID can be shared with other users for them to join the match.

Users can optionally provide a name when creating a new match. This name is used to generate the match ID, meaning two players creating matches with the same name will end up with identical match IDs and, as a result, in the same match.

A user can leave a match at any point, which will notify all other users. When all users have left the match it ceases to exist.

Join a match

A user can join a specific match using its ID. Matches cannot be password protected or otherwise closed, if a user has the match ID then they are able to join. Matches can be joined at any point until the last participant leaves.

Upon joining a match, a list of match opponents is returned in the success callback. Keep in mind this list might not include all users, it contains users who are connected to the match at that point in time.

List opponents

When a user creates or joins a new match they receive an initial list of connected opponents. After this initial list, the server pushes events to connected clients with the match joins and match leaves that occur - if no changes to the presence list occur then no server updates are sent. Events are batched for efficiency, meaning any event can contain multiple joins and/or leaves.

These events can be used to update the list of connected opponents so your players can see an accurate view of all match participants.

Some best practices to keep in mind for listing opponents:

  • Register a client-side presence event listener before joining or creating a match
  • For batched events with both a join and leave for the same presence, process the leave then join for presences already in the list and process the join then leave for presences not in the list

Send data messages

A user in a match can send data messages which will be received by all other opponents. These messages are streamed in real-time to the destined clients and can contain any binary content. LayerG broadcasts messages in the order received, not necessarily in the order they are sent.

The binary content in each data message should be as small as possible within the maximum transmission unit (MTU) of 1500 bytes. It is common to use JSON and preferable to use a compact binary format like Protocol Buffers or FlatBuffers.

When further reducing the message size and/or frequency is not possible, it is best to prioritize sending fewer messages. For example, 1 message of 1000 bytes per second is better than 5 messages of 200 bytes per second.

To identify each message as a specific “command” it contains an Op code as well as the payload.

While messages are broadcast to all other match presences by default, users can optionally specify a desired subset of the match participants (i.e. their friends, teammates, etc.) to receive the message exclusively.

Op codes

An op code is a numeric identifier for the type of message sent. Op codes can provide insight into the purpose and content of a message before you decode it.

They can be used to define commands within the gameplay which belong to certain user actions, such as:

  • Initial state synchronization
  • Ready status
  • Ping / Pong
  • Game state update
  • Emote

See the Fish Game tutorial for an example implementation.

Receive data messages

The server delivers data in the order it processes data messages from clients. A client can add a callback for incoming match data messages. This should be done before they create (or join) and leave a match.

Leave a match

Users can leave a match at any point. This may occur voluntarily via client action (quitting the match) or involuntarily (e.g. network connectivity), but in either case it must be accounted for and handled appropriately in your game logic.

A match ends when all users have left. At that time it’s ID becomes invalid and cannot be re-used to join again.

Examples

Match host rotation

You must determine how the match host will be selected from among the connected clients. This is best implemented without requiring any “negotiation” between the match participants while still ensuring that all clients acknowledge the same host.

You can accomplish this by deterministically sorting the match presences and selecting a host based on any desired factor. In the example below, we sort the presence list and select the lowest indexed session ID as the host: