GS2-Realtime

Real-time communication feature

This feature can be used for low-latency, high-frequency communication to allow game players to play against each other.

GS2 typically charges by the number of API requests, but for this service, once the game server is up and running, a fee is charged for the time the game server is running and for communication capacity. There is no charge per number of communications.

graph TD
  Match["Lobby established<br/>by GS2-Matchmaking"] --> Want["Request a room with RoomWant"]
  Want -- Allocated from hot standby --> Warm["Warm Start (1 to 3 seconds)"]
  Want -- New launch --> Cold["Cold Start (40 to 60 seconds)"]
  Warm --> Connect["Player connects"]
  Cold --> Connect
  Connect --> Play["In-game communication via packet relay"]
  Play -- No communication for 1 minute or 3 hours elapsed --> Shutdown["Room terminated"]

Server Type

GS2-Realtime has three server types.

serverTypeFunction
relayPacket relay that forwards messages between players
syncObject sync that manages the state of objects and who can update them
stateMachineIn addition to sync, runs a state machine defined in GSL on the server

You select it with serverType in the Realtime namespace settings. For how to choose, and what you build with sync / stateMachine, see Server Types and How to Choose.

With relay, the game server forwards binary data to specified players or to all participants, and the application implements the payload format and the game logic. What you can do with sync and stateMachine is explained below in Object Sync, Rollback Sync, and Server-Side Logic with a State Machine. You send a message to the game server, which broadcasts it to the other players; you can also specify a player ID to deliver the message only to that player. Binary encoding of the payload is the responsibility of the application, so you can adopt any encoding such as Protocol Buffers / FlatBuffers / MessagePack.

Server specs and the life cycle are common to all server types. Connecting to a Room, Profile, Event Handling, and Example Implementation describe packet relay (relay).

Unreal Engine Dedicated Server Hosting

We are looking into the possibility of allowing developers to host a dedicated server built with the Unreal Engine. We have not yet decided when this feature will be available.

If you have a strong need and would like to proceed with development under a paid support contract, we can work out a schedule, so please consult us.

Object Sync

A room with the sync server type holds the state of the objects in the room (such as players’ characters) and delivers only the changed values to all participants. You do not need to design a message format; you just add components to a Prefab in Unity, or nodes to a scene in Godot, and position and custom values are synced.

sequenceDiagram
  participant Owner as Owner's client
  participant Room as Room (sync)
  participant Other as Other clients
  Owner ->> Room: Spawn a character with world.Instantiate
  Room -->> Other: Deliver the spawn (create a GameObject from the Prefab)
  loop Each time it moves
    Owner ->> Room: Send only the changed values (NetworkTransform)
    Room -->> Other: Deliver, and interpolate toward the received position for display
  end
  Note over Room,Other: Players who join later receive<br/>the current state (snapshot)
  • The server manages who can modify values. Player objects can be modified only by their owner, and objects that lend control only by the one player who has obtained control. The room refuses writes from other participants.
  • Works the way Photon (PUN2) does. It provides player object spawning, RPC, room events, room and player custom properties, the master client, and Interest Groups. For a mapping table, see the Migration Guide from Photon.
  • Keeps traffic low. Instead of sending field names, it sends only the numbers and integer values of the changed fields. Periodic updates such as position are sent over UDP, and anything that does not arrive is covered over WebSocket (WebGL works with WebSocket only).
  • Resilient to reconnection. When a client reconnects, the view is rebuilt from the current state. The client does not need to replay the history of operations.

You can use it without writing room rules. To restrict the Prefabs that can be spawned in the room and the RPCs that can be called, set an object catalog in the namespace setting syncSetting.objectCatalog.

To try the whole procedure, see Tutorial: Sync Player Characters; for Unity settings and APIs, see Unity Client; for Godot settings and APIs, see Godot Client.

Rollback Sync

This sync method is for cases such as action games, where you want a character to move the instant the player gives input, while still preventing position tampering. You use it in a room with the stateMachine server type by defining ticks (a simulation at a fixed interval) in the rules.

  • The server decides positions. Clients send only inputs such as “move right” or “shoot”, and the room moves objects from everyone’s inputs a fixed number of times per second (1 to 60). Clients cannot write positions, which prevents movement speed tampering and wall clipping.
  • Your own character moves without waiting. When you add [Predict] to the movement logic in the rules, the compiler also emits the same logic as C# for Unity (GDScript for Godot). The client uses it to display the result of each input tick immediately (prediction).
  • Discrepancies are fixed by rewinding to the server’s result. When the room’s result arrives, the client rewinds to the state of that tick and recalculates up to the current tick (rollback). Even if a prediction was wrong, for example because of a collision with another player, the display is corrected unobtrusively over about 0.1 seconds.
sequenceDiagram
  participant Client as Your client
  participant Room as Room (stateMachine)
  Client ->> Client: Predict and display the input for tick 10 immediately
  Client ->> Room: Send the input for tick 10
  Client ->> Client: Also predict and display ticks 11, 12, ...
  Room ->> Room: Calculate tick 10 with everyone's inputs
  Room -->> Client: Result of tick 10
  Client ->> Client: Rewind to the result of tick 10 and<br/>recalculate up to the current tick
  • Hits land as the shooter saw them (lag compensation). Each input carries how far behind the screen was displayed at that moment. With the Room.Raycast and Room.Overlap hit detection, the room rewinds other objects to the positions the shooter saw before testing (up to 500 milliseconds).
  • Bullets can be displayed immediately too (predicted spawns). Bullets spawned in predicted logic appear on the shooter’s screen immediately and are carried over to the bullets the room spawns.
  • Keeps traffic low even with many players. Updates for distant objects can be sent less often.

The rules calculate using integers only, so the room and the client’s prediction produce the same results. Other players’ characters are not predicted; they are displayed by interpolating the room’s results.

To try the whole procedure, see Tutorial: Move Characters with Rollback Sync; for how to write the rules, see Ticks. The SDK includes a sample (Assets/Scenes/Samples/Realtime/Arena) that uses prediction, shooting with lag compensation, and predicted spawns.

Server-Side Logic with a State Machine

A room with the stateMachine server type, in addition to the object sync features, runs rules registered with GS2-StateMachine inside the room. Logic that could be tampered with if left to clients, such as enemy HP, whether an attack is allowed, boss phases, and time limits, can run on the server.

You write the rules in C#. From the C#, the compiler generates both the rules that run in the room (GSL) and the sync schema and operation number enums used in Unity, so value numbers cannot disagree between the server and the client. If you build your game with Godot, you can write the rules in GDScript and generate the sync schema and constant classes used in Godot (Writing Rules in GDScript).

graph LR
  CS["Rules in C#"] --> Gen["Unity's<br/>Generate from C#"]
  Gen --> GSL["main.gsl"]
  Gen --> Unity["Sync schema and enums<br/>(for Unity)"]
  GSL --> Master["GS2-StateMachine<br/>StateMachineMaster"]
  Master --> Room["Room (stateMachine)<br/>runs the rules"]
  Unity --> Client["Unity client"]
  Client -- "Interact (operation)" --> Room
  Room -- "Deliver confirmed values" --> Client
What the rules receiveWhen it is calledExample
StartOnce when the room startsSpawn enemies and doors
InteractWhen a player sends an operation with SharedObject.InteractValidate an attack and reduce HP
TimerWhen the time scheduled with Room.After arrivesRevive an enemy after 8 seconds
TickAt a fixed intervalMove characters from everyone’s inputs (Rollback Sync)
SpawnedRight after a player spawns their own objectPlace it at the team’s spawn position
  • Each operation is committed or rolled back as a whole. When the rules reject it with Step.Ignore, all changes made by that operation are rolled back, and the reason code reaches the client that sent it.
  • Rule versions can be managed. A room loads the rules when it is allocated and runs with the same version until it ends. When you register the rules again, rooms allocated after that use the new rules.
  • Self-contained within the room. Rules cannot call external APIs or GS2-Script. The room state is lost when the room ends, so grant rewards and save results outside the room using the GS2 services.

To try the whole procedure, see Tutorial: Build an Enemy Whose HP Is Managed by the Server; for how to write the rules, see the Room Definition Reference.

Getting Started

Choose a server type based on what you want to build, and start with a tutorial.

What you want to buildServer typeTutorial
Share players’ positions and statesyncSync Player Characters
Manage enemy HP and game progression on the serverstateMachineBuild an Enemy Whose HP Is Managed by the Server
Move input-driven characters without noticeable latencystateMachine (ticks)Move Characters with Rollback Sync
Relay messages in your own formatrelayExample Implementation on this page

The preparation common to sync and stateMachine is as follows.

  1. Install the GS2 SDK for Unity in a Unity project (Unity 2021.3 or later). The sync components (Gs2.Realtime.Sync) are included in the SDK. To write stateMachine rules in C#, use a version of LocalStateMachineKit that includes the C# generation feature.
  2. Create a GS2-Realtime namespace and set serverType. For stateMachine, also specify the GS2-StateMachine namespace where you registered the rules.
  3. Gather players with GS2-Matchmaking and prepare a room with RoomWant.
  4. In Unity, place NetworkWorld and NetworkSession in the scene, and connect by passing the room’s connection information (IP address, port, encryption key) and the access token.

For Godot (Godot 4), install the GS2 SDK for Godot in step 1, and place Gs2NetworkWorld and Gs2NetworkSession in the scene in step 4. You can write stateMachine rules in GDScript. For details, see the Godot Client.

Server Specs

You can set the performance of the server. The performance will make a difference in the price per minute. We have confirmed that the cheapest realtime1.nano can have 8 players send messages to each other 3 times per second.

The Unreal Engine Dedicated Server hosting we are considering will have higher requirements. Unreal Engine Dedicated Server is not recommended for projects where cost efficiency, not development efficiency, is paramount.

The server spec is configured per Namespace, by specifying an identifier such as realtime1.nano in the serverSpec field.

Life Cycle

A game server is not scheduled to start until it receives a startup request.

At this point, GS2 will prepare a hot standby for game servers that receive frequent startup requests. Therefore, allocation can be expected immediately after the startup request is received, but for small-scale games where startup requests do not occur frequently, it takes time from the startup request to the actual allocation.

We refer to the case where the allocation is done from a hot standby as a warm start, and the case where the allocation is done by starting a new server as a cold start. The estimated time required for cold start allocation is about 40 to 60 seconds, while the time required for warm start allocation is assumed to be 1 to 3 seconds.

For titles of a size that do not generate frequent startup requests and where cold start time is unacceptable, Hot Standby can be provided on the assumption that a fixed monthly fee will be charged. We accept contracts for hot standby with a minimum of 5 units.

We cannot guarantee that cold starts will not occur even if you have a hot standby contract or in situations where frequent startup requests are occurring. Cold starts can occur if server startup requests are made faster than hot standby is available or if the game server is being upgraded. Therefore, please design your system with the worst-case scenario of a cold start in mind.

Types of Start

TypeTime RequiredDescription
Warm Start1 to 3 secondsImmediate allocation from hot standby
Cold Start40 to 60 secondsLaunches a new server process

Room Termination Conditions

A launched room will be terminated under the following conditions:

  • If no one connects within 5 minutes after the room is created
  • If all players in the room have been silent for 1 minute
  • If the maximum room lifetime of 3 hours is reached

In addition, you can explicitly call RoomShutdown to terminate the room immediately.

Note that the same termination process applies to hot standby rooms as well, but hot standby uptime is not added to your usage charges.

Connecting to a Room

GS2-Realtime uses the following flow for connection.

sequenceDiagram
  participant Player
  participant Realtime as GS2-Realtime
  participant Server as GameServer
  Player ->> Realtime: Room(roomName).Model()
  Realtime -->> Player: IpAddress / Port / EncryptionKey
  Player ->> Server: RelayRealtimeSession.ConnectAsync()
  Server -->> Player: Connection established
  Note over Player,Server: After this, communicate directly with the game server over WebSocket

The connection information consists of the following three items:

  • IpAddress: IP address of the game server
  • Port: Port number to connect to
  • EncryptionKey: Encryption key for the message payload

When connecting, you pass the access token and the initial profile value of the player.

Profile

Relay servers not only forward received messages to other players, but also allow each player to have profile data.

When a new player connects to the game server, they will receive profile data for all other participating players. Storing basic player information simplifies the process when a new player connects.

When a player updates their profile, the information is sent to other players, so it can be used as a substitute for sending a message. However, it is not optimal in terms of communication efficiency to use high-frequency messaging via profile updates, since the entire payload is sent and received when a profile is updated.

A profile can be encoded as a binary payload in any format. It is suitable for storing snapshots of player character information, equipment, level, in-game currency, etc.

Event Handling

You can set the following event handlers on RelayRealtimeSession.

HandlerTrigger
OnJoinPlayerWhen another player joins the room
OnLeavePlayerWhen another player leaves the room
OnRelayMessageWhen a relay message is received from another player
OnUpdateProfileWhen another player updates their profile
OnErrorWhen a protocol error occurs
OnGeneralErrorWhen a communication error occurs
OnCloseWhen the connection to the game server is closed

Extended versions including MessageMetadata (such as OnRelayMessageWithMetadata) are also provided for each event, allowing you to also retrieve metadata about the message.

Push Notifications

The main push notifications you can configure and their settings are as follows.

  • createNotification: Notifies when a room is assigned

Enabling enableTransferMobileNotification also allows mobile push forwarding to offline devices.

By notifying all members gathered by GS2-Matchmaking of the room assignment, you can announce in real time that the match is ready.

Master Data Operation

GS2-Realtime does not require master data. It can be operated solely with namespace settings (server type, server spec, and push notification settings).

Transaction Actions

GS2-Realtime does not provide transaction actions.

Integration with Other Microservices

GS2-Realtime is typically used in combination with the following microservices rather than on its own.

ServicePurpose
GS2-MatchmakingMatch opponents, then call RoomWant once a match is formed
GS2-GatewayUse as the route for the createNotification push notification
GS2-AccountIssue player access tokens

Example Implementation

Obtaining connection information for the game server

Specify the room name to retrieve the connection information. If the room was prepared via matchmaking through RoomWant, specify its roomName.

    var item = await gs2.Realtime.Namespace(
        namespaceName: "namespace-0001"
    ).Room(
        roomName: "room-0001"
    ).ModelAsync();

    var ipAddress = item.IpAddress;
    var port = item.Port;
    var encryptionKey = item.EncryptionKey;
    const auto Domain = Gs2->Realtime->Namespace(
        "namespace-0001" // namespaceName
    )->Room(
        "room-0001" // roomName
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Result = Future->GetTask().Result();

    const auto IpAddress = Result->GetIpAddress();
    const auto Port = Result->GetPort();
    const auto EncryptionKey = Result->GetEncryptionKey();
var domain = ez.realtime.namespace_(
        "namespace-0001"
    ).room(
        "room-0001"
    )

var async_result = await domain.model()
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

Connect to game server

    using (
      var session = new RelayRealtimeSession(
         accessToken, // access token
         ipAddress, // game server IP address
         port, // game server port
         encryptionKey, // encryption key
         ByteString.CopyFromUtf8("my profile") // initial profile value
      )
    )
    {
      // set event handlers

      await session.ConnectAsync();

      // scope where session is valid
    }
    // The Realtime SDK for Unreal Engine is currently in preparation.
var session = Gs2RelayRealtimeSession.new(
    game_session.access_token.token, ip_address, port, encryption_key,
    "my profile".to_utf8_buffer()
)
var async_result = await session.connect_session()
if async_result.error != null:
    push_error(str(async_result.error))
    return
session.dispatch()

Handling connections from other players

    session.OnJoinPlayer += player => {
        var connectionId = player.ConnectionId;
        var profile = player.Profile;
        // Handle initial display for the new participant
    };
    // The Realtime SDK for Unreal Engine is currently in preparation.
session.join_player.connect(func(player):
    var connection_id = player.connection_id
    var profile = player.profile
    # Initialize the newly joined player in the game UI.
)

Handling disconnections of other players

    session.OnLeavePlayer += player => {
        // Clean up the display for the leaving player
    };
    // The Realtime SDK for Unreal Engine is currently in preparation.
session.leave_player.connect(func(player):
    # Remove the player who left from the game UI.
    pass
)

Handling messages from other players

    session.OnRelayMessage += message => {
        var sourceConnectionId = message.ConnectionId;
        var payload = message.Data;
        // Decode and interpret the payload in the application layer
    };
    // The Realtime SDK for Unreal Engine is currently in preparation.
session.relay_message.connect(func(message):
    var source_connection_id = message.connection_id
    var payload = message.data
    # Decode and handle the payload in the application layer.
)

Handling profile updates of other players

    session.OnUpdateProfile += player => {
        // Reflect profile changes on the screen
    };
    // The Realtime SDK for Unreal Engine is currently in preparation.
session.update_profile_received.connect(func(player):
    # Reflect the changed profile in the game UI.
    pass
)

Handling disconnects from game server

    session.OnClose += () => {
        // Retry or transition logic on disconnection
    };
    // The Realtime SDK for Unreal Engine is currently in preparation.
session.closed.connect(func():
    # Retry the connection or transition to an error screen.
    pass
)

Update profile

    await session.UpdateProfileAsync(
      ByteString.CopyFromUtf8("my profile2")
    );
    // The Realtime SDK for Unreal Engine is currently in preparation.
var async_result = await session.update_profile(
    "my profile2".to_utf8_buffer()
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

Send broadcast message

If targetConnectionIds is omitted, the message is delivered to every player in the room except yourself.

    await session.SendAsync(
      ByteString.CopyFrom(0x00, 0x01, 0x02)
    );
    // The Realtime SDK for Unreal Engine is currently in preparation.
var async_result = await session.send(
    PackedByteArray([0x00, 0x01, 0x02])
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

Send unicast message

Specify the ConnectionId of the target player in targetConnectionIds. The ConnectionId can be obtained from events such as OnJoinPlayer.

    await session.SendAsync(
      ByteString.CopyFrom(0x00, 0x01, 0x02),
      new uint[] { targetConnectionId1, targetConnectionId2 }
    );
    // The Realtime SDK for Unreal Engine is currently in preparation.
var async_result = await session.send(
    PackedByteArray([0x00, 0x01, 0x02]),
    [target_connection_id]
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

Design Notes

Designing the binary payload

GS2-Realtime simply relays binary data as-is without interpreting its content. Therefore, the following aspects must be designed on the application side:

  • A header to identify message types (for example, putting an opcode in the first byte)
  • Serialization format (Protocol Buffers / FlatBuffers / MessagePack, etc.)
  • Ordering guarantees if needed (assign sequence numbers in the application layer when required)

Cheat Prevention

Since the relay server does not interpret the payload contents, it cannot detect cheating itself. For calculations such as rating, results are determined by majority vote.

Detailed Reference