> For the complete documentation index, see [llms.txt](/llms.txt)


# Godot Client

Nodes and settings for displaying and operating shared objects in Godot



In `sync` and `stateMachine` rooms, the objects in the room can be displayed as Godot nodes. This page explains the Godot-side settings and APIs. It covers GDScript in Godot 4, and supports both 2D and 3D.

How syncing works (who can modify what, send order, reconnection and so on) is the same for every engine, so it is collected in [How Object Sync Works](). This page explains how to write things in Godot. You can also write the room rules in GDScript. For how, see [Writing Rules in GDScript]().

Unity clients and Godot clients are not meant to join the same room. Use only one of them in a game.

## Overview {#overview}

In Godot, you use the following nodes and resources. All of them are included in the GS2 SDK for Godot add-on (`addons/gs2`).

| Godot | Unity | Where to put it / Role |
| --- | --- | --- |
| `Gs2NetworkWorld` | `NetworkWorld` | One per scene. Creates, updates, and destroys instances from the received state, and sends changes in batches |
| `Gs2NetworkSession` | `NetworkSession` | One per scene. Connects to the room and attaches the World |
| `Gs2NetworkEntity` | `NetworkEntity` | A **child node** of the root of each synced scene. Associates an instance with a shared object |
| `Gs2NetworkTransform2D` / `Gs2NetworkTransform3D` | `NetworkTransform` | A child node of the root of a scene that syncs position and rotation |
| `Gs2SharedObject` | `SharedObject` | Sends operations to objects that the server updates |
| `Gs2NetworkPrefabCatalog` (resource) | Prefab Catalog | Maps Prefab IDs to the scenes (PackedScene) to display |
| `Gs2NetworkSchema` (resource) | Sync schema | An imported `.realtimeschema` |

In Unity you add components to a GameObject; in Godot you add nodes as children of the scene's root. By default, `Gs2NetworkEntity` and the Transform nodes target their parent (`target = ".."`).

GDScript has no exceptions, so failures are returned as return values (`false` or `null`), and the reason goes into each node's `last_error`. Usage mistakes are also reported with `push_error`.

## Connect to a Room {#connection}

1. Place `Gs2NetworkWorld` and `Gs2NetworkSession` in the scene.
2. Set the Prefab catalog in the World's `catalog`, and the World in the Session's `world`.
3. Call `connect_room` with the room address you received from GS2-Realtime (access token, IP address, port, encryption key).

```gdscript
extends Node

const PLAYER_PREFAB := 1 # The ID of the catalog entry

@onready var world: Gs2NetworkWorld = $World
@onready var session: Gs2NetworkSession = $Session


func _ready() -> void:
    # Connect the signals only once. Connecting them on each connection duplicates them on reconnect
    session.room_ready.connect(_on_room_ready)
    world.spawned.connect(_on_spawned)
    session.disconnected.connect(func(reason: String) -> void: print("Disconnected: ", reason))


# On each connection or reconnection
func join(access_token: String, ip_address: String, port: int, encryption_key: String) -> void:
    if not session.connect_room(access_token, ip_address, port, encryption_key):
        push_error(session.last_error)


func _on_room_ready() -> void:
    # After the snapshot is applied. You can spawn and send
    world.instantiate(PLAYER_PREFAB, Vector3.ZERO)


func _on_spawned(entity: Gs2NetworkEntity) -> void:
    if entity.is_mine():
        print("My player is in the room: ", entity.get_parent().name)
```

| Member | Description |
| --- | --- |
| `connect_room(access_token, ip_address, port, encryption_key)` | Connect to the room. Calling it again reconnects |
| `disconnect_room()` | Disconnect. `disconnected` is emitted when done |
| `state` | `DISCONNECTED`, `CONNECTING`, `SYNCHRONIZING` (applying the snapshot), or `READY` |
| `attached(client)` | Signal emitted right after the World is attached |
| `room_ready` | Signal emitted when the snapshot has been applied and you can spawn and send |
| `disconnected(reason)` | Signal emitted on disconnect. Includes disconnects by the other side, connection failures, and send or parse failures |

The Session also disconnects when the node is disabled (`process_mode = DISABLED`) or leaves the tree. The Session and World are not affected by pausing (`PROCESS_MODE_ALWAYS`), and they measure elapsed time in real time.

You may send at any time after connecting ([Send Order](#ordering)).

To connect your own transport instead of the Session, attach with `world.attach(connection_id, send)`, pass received data to `world.receive_at(payload, server_milliseconds)`, and call `world.detach()` on disconnect.

## Register Scenes {#prefabs}

1. Create a scene for display, and add `Gs2NetworkEntity` as a child of its root (`CharacterBody3D`, `Node2D`, and so on). To also sync position and rotation, add `Gs2NetworkTransform3D` (`Gs2NetworkTransform2D` for 2D) as a child too.
2. Create a new `Gs2NetworkPrefabCatalog` resource, and add `Gs2NetworkPrefabEntry` items (a Prefab ID and a PackedScene) to `prefabs`.
3. Specify that catalog in the World's `catalog`.

The Inspector shows warnings for duplicate IDs, missing scenes or Entities, and schemas meant for another Prefab. Register every Prefab ID the server may send.

When a player creates their own object, use [`world.instantiate`](#player-objects) instead of `PackedScene.instantiate()`.

## Sync Position and Rotation {#transform}

The SDK has standard fields for position and rotation, so you can sync them without a sync schema. Select only the axes the server allows to sync ([Position and Rotation](#transform)).

| Node | Values synced |
| --- | --- |
| `Gs2NetworkTransform2D` | Position X / Y and rotation. Rotation is converted to degrees and normalized to 0 to 360 before sending. Select with `position_axes` and `sync_rotation` |
| `Gs2NetworkTransform3D` | Position on 3 axes and rotation on 3 axes. Select with `position_axes` and `rotation_axes`. The Euler angle order is fixed to YXZ, and when all three axes are synced, rotation is interpolated as a Quaternion |

In 2D the Y axis is not flipped, so pixel coordinates reach the rules as they are. The default step size (0.01) is too fine for pixels, so we recommend making it coarser in the rules (in GDScript, `## @room_schema(..., standard=POSITION, position_precision=1.0)`).

| Item | Description |
| --- | --- |
| `target` | The node to move. Defaults to the parent |
| `interpolation_delay` | How many seconds in the past the displayed pose is (default 0.1). Interpolates between the updates received before and after |
| `teleport_distance` | Moves without interpolation to positions farther than this distance. If 0, it always interpolates |
| `snap_to_network()` | Snaps to the received pose immediately |

## Display Custom Values {#bindings}

For custom values such as HP or an open/closed state, declare them as a sync schema in the rules and use the generated `.realtimeschema` ([Custom Values](#fields)).

1. Put the generated `.realtimeschema` in the project. It is imported as a `Gs2NetworkSchema` resource.
2. Specify that resource in the scene's `Gs2NetworkEntity.schema`.
3. To reflect values in the display, set `property_bindings` (field name, target node, property or method), or use the `field_changed(field_name, value)` signal.

| Method | Description |
| --- | --- |
| `get_int` / `get_bool` / `get_float(name)` | Reads a value. If you can write it, this is the value you last wrote (even before it is sent) |
| `get_confirmed_int` / `get_confirmed_bool` / `get_confirmed_float(name)` | Reads the value received from the room |
| `set_int` / `set_bool` / `set_float(name, value)` | Sends a value. Requires write permission for that object |

To write values right before every send, implement `_network_write(entity)`; to read values when they are received, implement `_network_read(entity, changed)`. Implement them on a node in the scene (equivalent to Unity's `INetworkObservable`).

When you write the rules in GDScript, the compiler also generates typed accessors (such as `EnemyStateSchema.health(o)`).

## Send Operations to the Server {#interaction}

For objects managed by the rules, add `Gs2SharedObject` to the scene and send operations. For request statuses and the meaning of result codes, see [Send Operations to the Server](#interaction).

| Method | Description |
| --- | --- |
| `interact(method, args)` | Calls the rules' `on_interact` (`Interact` in C#) |
| `acquire()` / `renew()` / `release()` | Acquires, renews, or releases the lease |

All of them return a `Gs2CommandRequest`. Wait for completion with `await request.wait()`, or receive the result with `request.on_completed(callback)`.

```gdscript
var request := door.interact(DoorAction.OPEN) # DoorAction is a constant generated from the rules
await request.wait()
if request.status != Gs2SyncTypes.RequestStatus.SUCCEEDED:
    print("Refused: ", Gs2SyncTypes.result_name(request.result))
```

## Send Values with Control {#authority}

For the rules of control, see [Control](#authority).

| Member | Description |
| --- | --- |
| `request_control()` / `release_control()` | Request to acquire / release control |
| `acquire_control()` | Requests control and returns a `Gs2ControlRequest` you can wait on until you have control |
| `acquire_on_enable` | Automatically requests control when enabled |
| `auto_renew` | Automatically renews control before it expires (enabled by default) |
| `controlled_nodes` | Nodes that run only while you can operate the object (movement and input handling) |
| `control_changed(controlled)` | Signal emitted when whether you can operate the object changes |

`Gs2SharedObject`'s `acquire()` / `renew()` / `release()` manage the lease manually. They neither switch `controlled_nodes` nor renew automatically.

## Create Player Objects {#player-objects}

| Method | Description |
| --- | --- |
| `world.instantiate(prefab_id, position, rotation)` | Returns the instance immediately. You can move it or send RPCs right away. `is_spawned()` becomes true once the room confirms the spawn. If rejected, `spawn_failed` is emitted and the instance is removed |
| `world.spawn(prefab_id, position, rotation)` | Waits for the room's confirmation before creating the instance. Returns a `Gs2SpawnRequest` |
| `entity.is_mine()` | True if you are the owner or the lease holder |
| `entity.transfer_ownership(connection_id)` | Hands ownership to another member |
| `entity.destroy()` | Removes your own player object |

Position and rotation can be passed as `Vector2` and radians in 2D, and as `Vector3` and `Quaternion` / `Basis` / Euler angles (radians) in 3D. For what happens when the owner leaves, and the rules of ownership periods, see [Player Objects](#player-objects).

## RPC {#rpc}

RPCs are registered automatically from method names. The targets are methods of nodes under the Entity's parent (excluding those under another nested Entity).

```gdscript
# An RPC only the owner can call
func net_rpc_fire(direction: Vector3, damage: int, info: Gs2NetworkRpcInfo) -> void:
    print("fired by ", info.sender)

# An RPC any member can call (publicMethods in the object catalog)
func net_rpc_public_cheer(info: Gs2NetworkRpcInfo) -> void:
    pass
```

```gdscript
entity.send_rpc("fire", Gs2SyncTypes.RpcTarget.OTHERS, Vector3.FORWARD, 10)
entity.rpc_to("fire", connection_id, Vector3.FORWARD, 10)
```

- The RPC ID is the FNV-1a hash of the name without the prefix (`fire`). Renaming the method changes the ID, so update the object catalog and the clients together.
- The targets are `ALL`, `OTHERS`, `OWNER`, `ALL_BUFFERED`, and `OTHERS_BUFFERED`. For their meanings, see [RPC](#rpc).
- Give arguments types. Supported types are `bool`, `int` (including enums), `float`, `String`, `PackedByteArray`, `Vector2` / `3` / `4`, `Vector2i` / `3i`, `Quaternion`, and `Color`. A final `Gs2NetworkRpcInfo` argument receives the sender and other information.
- The send method is named `send_rpc` so that it does not collide with `Node.rpc()`.

Select an Entity in the Inspector to see the registered RPCs and their IDs.

## Room Events, Custom Properties, Master Client, and Interest Groups {#room}

For meanings and limits, see [Room Events](#events), [Custom Properties](#properties), [Master Client](#master) and [Interest Group](#interest-groups).

| Feature | How to write it in Godot |
| --- | --- |
| Room events | Register a node with `net_event_<name>` methods with `world.add_event_target(node)`, and send with `world.raise_event("round_start", Gs2SyncTypes.RpcTarget.OTHERS, 3)` |
| Custom properties | Write with `world.set_room_property(key, value)` / `set_player_property(key, value)`, and read with `room_property(key, fallback)` |
| Master client | Read with `world.master_client_id()`, and receive changes with `master_client_changed`. Appoint another member with `set_master_client(id)` |
| Interest Groups | The owner moves an object with `entity.set_group(group)`, and only members who called `world.subscribe_group(group)` receive its updates |

## Control Objects Moved by Ticks {#tick}

In rooms that use ticks, send inputs with `world.set_input(slot, value)` ([Ticks and Prediction](#tick)). For the slot, use the constants generated from the rules (such as `TickInput.MOVE_X`).

When the rules have prediction functions, the compiler generates `<RuleName>Prediction`. Call `<RuleName>Prediction.register(world)` once when the game starts, and your own objects move first with the same calculation as the room. When a prediction was off, the Transform closes the gap over about `correction_time` (0.1 seconds by default). For lag compensation, keep the World's `view_delay` equal to the Transform's `interpolation_delay` (0.1 seconds by default).

## Send State over UDP {#udp}

When the room provides UDP and the game is not running on the Web, the Session uses UDP ([UDP](#udp)). To not use UDP, set the Session's `use_udp` to false.

UDP is not available in Web builds. Also, a page served over https may not be able to connect to `ws://`.

## Object Catalog {#catalog}

Select a `Gs2NetworkPrefabCatalog` in the Inspector and run **Project > Tools > GS2 > Copy Object Catalog JSON** to copy the JSON for the namespace's `syncSetting.objectCatalog` to the clipboard. Prefabs that the rules define (`Gs2SharedObject` and those with schemas the rules define) are excluded. For the JSON format and items, see [Object Catalog](#catalog).

## Object Lifetime, Room Termination and Send Volume {#lifetime}

For how objects are handled on disconnect and reconnect, the relation to room termination conditions, and the estimated send volume, see [Object Lifetime and Reconnection](#lifetime), [Relation to Room Termination Conditions](#idle) and [Estimated Send Volume](#size). The World's send interval is `send_rate` (default 20 times per second).

## Troubleshooting {#troubleshooting}

For checks common to every engine, see [How Object Sync Works](#troubleshooting). Godot-specific checks are as follows.

| Symptom | What to check |
| --- | --- |
| Connecting or sending fails | Check `last_error` on the node that failed, and the errors shown in the Output panel |
| Custom values cannot be read | Whether you specified the generated `.realtimeschema` in the scene's `Gs2NetworkEntity.schema` |
| Nothing is sent when you move | Whether it is your object (`is_mine()`). For a leased object, whether you have acquired control |
| RPCs are not called | Whether the method is named `net_rpc_<name>` and its arguments have types. Whether the node with the method is under the Entity's parent |
| Operations are refused | Check the World's `command_rejected` signal for the refused command, the target ID and the reason |
| Spawned objects disappear | Check the result of the World's `spawn_failed` signal |
| A generated class is not found, or names collide | Whether you rebuilt the rules. Whether the generated class names collide with other rules or scripts ([Writing Rules in GDScript](#deployment)) |

## Samples {#samples}

The SDK's `samples/realtime/` contains two samples ported from the Unity samples. Both write their rules in GDScript.

| Sample | Contents |
| --- | --- |
| `object_sync` (3D) | Players, a box you lease and move, a door the rules open and close, and an enemy whose health drops and revives |
| `air_hockey` (2D) | Paddles, a puck, and scoring |



