> ## Documentation Index
> Fetch the complete documentation index at: https://docs.playflowcloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quick Start - Godot Multiplayer

> Step-by-step tutorial for creating your first multiplayer game in Godot with PlayFlow Cloud

# Creating Your First Multiplayer Game in Godot

This comprehensive guide shows you how to create a simple multiplayer game from scratch in Godot that connects to a dedicated server hosted on PlayFlow Cloud. This setup is compatible with both web and desktop platforms using TCP + TLS ("wss" protocol).

> [!NOTE]
>
> **Godot 4.x** - This guide uses Godot 4.4, but any 4.x version should work. We'll be using the Compatibility renderer for web platform support.

> [card] [Complete Project](https://github.com/Xaimelee/playflow-quick-start-guide)
>
> Download the complete project files from GitHub (community-maintained sample)

## What You'll Build

By the end of this tutorial, you'll have:

* A 3D multiplayer game with player movement
* Connection UI for server details
* WebSocket networking with TLS support
* Multiplayer synchronization using Godot's built-in systems
* A working dedicated server setup on PlayFlow

## Before We Begin

**1. Create New Project**

Create a new Godot project and select the **Compatibility** renderer. This ensures web platform compatibility.

**2. Set Up Main Scene**

Create a new scene, save it as "main.tscn" and select **3D Scene** as the root type. Right-click the file after saving and select **Set as Main Scene**.

## Part 1: Building the Connection UI

Let's start by creating the user interface that players will use to connect to your PlayFlow server.

**1. Create Connection Display**

1. Open `main.tscn` and add a child node of type **Control**
2. Rename it to "ConnectionDisplay"
3. Set **Custom Minimum Size** to **X: 256.0px** and **Y: 128.0px**

**2. Add Container Layout**

1. Add a child node to "ConnectionDisplay" of type **VBoxContainer**
2. Set **Layout Mode** to **Anchors** and **Anchors Preset** to **Full Rect**

**3. Create URL Input Field**

1. Add a child node to "VBoxContainer" of type **HBoxContainer**
2. Add a **TextEdit** child to "HBoxContainer" and rename it to "UrlInput"
3. Set **Placeholder Text** to "Url"
4. Toggle on **Fit Content Height**
5. Set **Custom Minimum Size** to **X: 192.0px** and **Y: 32.0px**
6. Toggle on **Expand** under **Horizontal**

**4. Create Port Input Field**

1. Add another **TextEdit** child to "HBoxContainer" and rename it to "PortInput"
2. Set **Placeholder Text** to "Port"
3. Toggle on **Fit Content Height**
4. Toggle on **Expand** under **Horizontal**

**5. Add Connection Buttons**

1. Add a **Button** child to "VBoxContainer" and rename it to "ConnectButton"
2. Set **Text** to "Connect"
3. Add another **Button** child to "VBoxContainer" and rename it to "DisconnectButton"
4. Set **Text** to "Disconnect"

**6. Set Unique Names**

Right-click each input and button node and select **Access as Unique Name**. This allows us to reference them easily in scripts.

**7. Create Connection Display Script**

1. Right-click "ConnectionDisplay" and select **Attach Script**
2. Create a new script called "connection\_display.gd"
3. Add the following code:

```gdscript theme={null}
extends Control

@onready var url_input: TextEdit = %UrlInput
@onready var port_input: TextEdit = %PortInput
@onready var connect_button: Button = %ConnectButton
@onready var disconnect_button: Button = %DisconnectButton

func _ready() -> void:
    disconnect_button.visible = false
    multiplayer.connection_failed.connect(_on_connection_failed)

func _on_connect_button_pressed() -> void:
    connect_button.visible = false
    disconnect_button.visible = true
    url_input.editable = false
    port_input.editable = false
    MultiplayerManager.start_client(url_input.text, port_input.text)

func _on_disconnect_button_pressed() -> void:
    connect_button.visible = true
    disconnect_button.visible = false
    url_input.editable = true
    port_input.editable = true
    MultiplayerManager.disconnect_from_server()

# If we are unable to connect then we enable the connect button and text inputs again.
func _on_connection_failed() -> void:
    connect_button.visible = true
    disconnect_button.visible = false
    url_input.editable = true
    port_input.editable = true
```

**8. Connect Button Signals**

1. Select the "ConnectButton" and go to the **Node** tab next to the Inspector
2. Connect the "pressed()" signal to "ConnectionDisplay"
3. Repeat for "DisconnectButton"

**9. Add Game Environment**

1. Add a child node to "Main" of type **MeshInstance3D** and rename it to "Ground"
2. Assign a new **PlaneMesh** to the Ground
3. Set the PlaneMesh **Size** to **X: 10m** and **Y: 10m**
4. Add a child node to "Main" of type **Node3D** and rename it to "Players"
5. Add a child node to "Main" of type **MultiplayerSpawner**
6. Add a **Camera3D** as a child to "Main" and position it as desired
7. Add a **DirectionalLight3D** as a child to "Main" and rotate it (suggested: **X: -60.0**)

## Part 2: Creating the Multiplayer Manager

Now we'll create a Multiplayer Manager to handle all networking aspects of our game.

**1. Create Multiplayer Manager Scene**

1. Create a new scene, save it as "multiplayer\_manager.tscn"
2. Select **Node** as the root type

**2. Create Multiplayer Manager Script**

1. Right-click the root node and select **Attach Script**
2. Create a new script called "multiplayer\_manager.gd"
3. Add the following code:

```gdscript theme={null}
extends Node

# Internal server port
const PORT: int = 8080

@onready var peer = WebSocketMultiplayerPeer.new()

func _ready() -> void:
    # The server should always automatically start
    if is_server():
        start_server()

func start_server() -> void:
    var err: Error = peer.create_server(PORT)
    if err == 0:
        multiplayer.multiplayer_peer = peer
        multiplayer.peer_disconnected.connect(_on_peer_disconnected)

func start_client(url: String, port: String) -> void:
    # If you want to use localhost (i.e you run a windows server export on your PC for testing), then it must be ws:// instead.
    var err: Error = peer.create_client("wss://" + url + ":" + port)
    if err == 0:
        multiplayer.multiplayer_peer = peer

func disconnect_from_server() -> void:
    peer.close()

func is_server() -> bool:
    # Exports have tags and this lets us identify if the build is a dedicated server.
    return OS.has_feature("dedicated_server")

func _on_peer_disconnected(id: int) -> void:
    var nodes = get_tree().get_nodes_in_group("Players")
    for node in nodes:
        if node.get_multiplayer_authority() == id:
            node.queue_free()
            break
```

**3. Add as Autoload Singleton**

1. Go to **Project → Project Settings → Autoload**
2. Set **Path** to "res\://multiplayer\_manager.tscn"
3. Set **Node Name** to "MultiplayerManager"
4. Click **Add**

> [!NOTE]
>
> This makes the MultiplayerManager available globally throughout your project.

## Part 3: Creating the Player

Let's create the player character with movement and networking synchronization.

**1. Create Player Scene**

1. Create a new scene, save it as "player.tscn"
2. Select **3D Scene** as the root type (this creates a Node3D)
3. Add **MeshInstance3D** as a child to Player
4. Assign a **CapsuleMesh** to the MeshInstance3D
5. Move the MeshInstance3D up by **Y: 1.0m** so it sits on the ground

**2. Add Network Synchronization**

1. Add a **MultiplayerSynchronizer** as a child to Player
2. In the MultiplayerSynchronizer, click **Add property to sync**
3. Select **Player → position** under **Node3D**

> [!NOTE]
>
> This ensures player positions are automatically synchronized across all clients.

**3. Add Player to Group**

1. Select the Player root node
2. Go to the **Groups** tab next to the Inspector
3. Add the player to group "Players" (type "Players" and click **Add**)

**4. Create Player Script**

1. Right-click the Player root node and select **Attach Script**
2. Create a new script called "player.gd"
3. Add the following movement code:

```gdscript theme={null}
extends Node3D

@export var speed: float = 5.0

func _process(delta: float) -> void:
    if not is_multiplayer_authority(): return
    var input_dir: Vector2 = Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
    var offset: Vector3 = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() * speed * delta
    translate(offset)
```

## Part 4: Input Mapping and Player Spawning

**1. Configure Input Map**

1. Go to **Project → Project Settings → Input Map**
2. Add these input actions (click **Add New Action** for each):
   * `move_forward` - Assign W key and Up Arrow
   * `move_backward` - Assign S key and Down Arrow
   * `move_left` - Assign A key and Left Arrow
   * `move_right` - Assign D key and Right Arrow

**2. Create Player Spawning System**

1. In `main.tscn`, select the **MultiplayerSpawner** node
2. Right-click and **Attach Script**, create "spawner.gd"
3. Add the following code:

```gdscript theme={null}
extends MultiplayerSpawner

func _ready() -> void:
    spawn_function = spawn_player
    # We only want the server spawning new players when new peers connect
    if MultiplayerManager.is_server():
        multiplayer.peer_connected.connect(_on_peer_connected)

func spawn_player(peer_id: int) -> Node:
    var player: Node = load(get_spawnable_scene(0)).instantiate()
    player.set_multiplayer_authority(peer_id)
    return player

func _on_peer_connected(id: int) -> void:
    spawn(id)
```

**3. Configure MultiplayerSpawner**

1. Set **Spawn Path** to "Players" (the Node3D we created earlier)
2. Under **Auto Spawn List**, click **Add Element**
3. Set the scene path to "res\://player.tscn"

> [!NOTE]
>
> This tells the spawner which scene to instantiate and where to place new players.

## Part 5: Server Export Configuration

**1. Add Linux Export Preset**

1. Go to **Project → Export**
2. Click **Add...** and select **Linux/X11**
3. Rename the preset to "Linux Server"
4. Set the export path filename to "Server". Note the exact name you use here — when you upload the build, the executable path you configure must match this exported binary name so PlayFlow knows which file to run.

**2. Configure for Dedicated Server**

1. In the Linux Server preset, go to the **Resources** tab
2. Set **Export Mode** to "Export as dedicated server"
3. This will create a headless server build suitable for PlayFlow

**3. Export the Server**

1. Click **Export Project...**
2. Choose your export location
3. Toggle **Export With Debug** for development builds
4. Click **Save**

**4. Create Archive**

Create a .zip archive of all exported files. This is what you'll upload to PlayFlow.

> [!NOTE]
>
> PlayFlow expects a .zip file containing your server executable and any required assets.

## Part 6: Deploy to PlayFlow and Test

**1. Upload to PlayFlow**

1. Create your PlayFlow account at [app.playflowcloud.com](https://app.playflowcloud.com/)
2. Create a new project for your Godot game
3. Open the project's **Builds** page and upload your server .zip file there

> [!NOTE]
>
> Prefer automation? You can upload builds through the API instead: request an upload URL with `POST /api/v3/builds/upload-url?name=YOUR_BUILD` and `PUT` the zip to the returned `upload_url`. See the [API guide](https://docs.playflowcloud.com/fundamentals/api.md) for details.

**2. Configure Network Port**

**CRITICAL**: When configuring the port in PlayFlow:

1. Set **Port Number** to **8080** (matches our server code)
2. Set **Protocol** to **TCP**
3. **Enable TLS** - This is required for "wss\://" protocol used in browser games

> [!WARNING]
>
> Without TLS enabled, web clients cannot connect to your server. This is essential for browser compatibility.

**3. Start Server Instance**

1. Create and start a server instance in PlayFlow
2. Copy the **Host** URL and **External Port** from the server details

> [!NOTE]
>
> The **External Port** is allocated by PlayFlow and will differ from your internal `8080`. Always read the host and external port from the running server (the `network_ports[]` array in the API response, or the server details in the dashboard) rather than assuming a fixed port.

**4. Test the Connection**

1. Run your Godot project locally (you can enable **Multiple Instances** in Debug settings for testing)
2. Enter the PlayFlow Host URL in the "Url" field
3. Enter the External Port in the "Port" field
4. Click **Connect**
5. Your player should appear and you can move around with WASD keys!

> [!TIP]
>
> **Testing Multiple Players**: Enable **Debug → Customize Run Instances → Enable Multiple Instances** and set instance count to 2. Run the game and connect both instances to see multiplayer in action.

## Technical Architecture

### Network Setup

* **Protocol**: WebSocket with TLS ("wss\://")
* **Server Port**: 8080 (internal)
* **Synchronization**: Player position only
* **Authority**: Server-authoritative with client input

### Key Components

1. **MultiplayerManager**: Handles connection logic and server detection
2. **Player**: Networked player with movement and authority management
3. **ConnectionDisplay**: UI for entering server connection details
4. **MultiplayerSpawner**: Automatically spawns players when clients connect

### Platform Support

* **Desktop**: Windows, Linux, macOS
* **Web**: Browser support via WebSocket + TLS
* **Server**: Headless Linux builds for PlayFlow

## Next Steps

> [card] [Lobby System](https://docs.playflowcloud.com/unity/lobby-overview.md)
>
> Add lobbies and matchmaking

> [card] [WebGL Deployment](https://docs.playflowcloud.com/guides/webgl-deployments.md)
>
> Deploy for web browsers

## Common Issues

> [accordion] Connection timeout or 'wss\://' connection failed
>
> * Verify the server URL and port are correct
> * **Ensure TLS is enabled** in PlayFlow port configuration
> * Check that the PlayFlow server instance is running
> * For local testing, use "ws\://" instead of "wss\://" (localhost only)

> [accordion] Players not synchronizing or appearing
>
> * Check that MultiplayerSynchronizer is properly configured on the player
> * Verify the player scene is added to MultiplayerSpawner's Auto Spawn List
> * Ensure players are being added to the "Players" group
> * Check that spawn\_function is set correctly in spawner.gd

> [accordion] Server not starting automatically
>
> * Verify MultiplayerManager is added as an Autoload singleton
> * Check that `OS.has_feature("dedicated_server")` returns true in server builds
> * Ensure the "Export as dedicated server" option is enabled in export settings

> [accordion] Input not working
>
> * Check that input actions are properly defined in Project Settings → Input Map
> * Verify `is_multiplayer_authority()` check in player movement code
> * Ensure the player has authority set via `set_multiplayer_authority()`

## Resources

* **Godot Documentation**: [docs.godotengine.org](https://docs.godotengine.org/)
* **Godot Multiplayer**: [docs.godotengine.org/en/stable/tutorials/networking](https://docs.godotengine.org/en/stable/tutorials/networking/)
* **PlayFlow Documentation**: [docs.playflowcloud.com](https://docs.playflowcloud.com/index.md)
* **Complete Project**: [GitHub Repository](https://github.com/Xaimelee/playflow-quick-start-guide)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.