> ## 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).

<Info>
  **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.
</Info>

<Card title="Complete Project" icon="download" href="https://github.com/Xaimelee/playflow-quick-start-guide">
  Download the complete project files from GitHub (community-maintained sample)
</Card>

## 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

<Steps>
  <Step title="Create New Project">
    Create a new Godot project and select the **Compatibility** renderer. This ensures web platform compatibility.
  </Step>

  <Step title="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**.
  </Step>
</Steps>

## Part 1: Building the Connection UI

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

<Steps>
  <Step title="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**
  </Step>

  <Step title="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**
  </Step>

  <Step title="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**
  </Step>

  <Step title="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**
  </Step>

  <Step title="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"
  </Step>

  <Step title="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.
  </Step>

  <Step title="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
    ```
  </Step>

  <Step title="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"
  </Step>

  <Step title="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**)
  </Step>
</Steps>

## Part 2: Creating the Multiplayer Manager

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

<Steps>
  <Step title="Create Multiplayer Manager Scene">
    1. Create a new scene, save it as "multiplayer\_manager.tscn"
    2. Select **Node** as the root type
  </Step>

  <Step title="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
    ```
  </Step>

  <Step title="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**

    <Info>
      This makes the MultiplayerManager available globally throughout your project.
    </Info>
  </Step>
</Steps>

## Part 3: Creating the Player

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

<Steps>
  <Step title="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
  </Step>

  <Step title="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**

    <Info>
      This ensures player positions are automatically synchronized across all clients.
    </Info>
  </Step>

  <Step title="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**)
  </Step>

  <Step title="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)
    ```
  </Step>
</Steps>

## Part 4: Input Mapping and Player Spawning

<Steps>
  <Step title="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
  </Step>

  <Step title="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)
    ```
  </Step>

  <Step title="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"

    <Info>
      This tells the spawner which scene to instantiate and where to place new players.
    </Info>
  </Step>
</Steps>

## Part 5: Server Export Configuration

<Steps>
  <Step title="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.
  </Step>

  <Step title="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
  </Step>

  <Step title="Export the Server">
    1. Click **Export Project...**
    2. Choose your export location
    3. Toggle **Export With Debug** for development builds
    4. Click **Save**
  </Step>

  <Step title="Create Archive">
    Create a .zip archive of all exported files. This is what you'll upload to PlayFlow.

    <Info>
      PlayFlow expects a .zip file containing your server executable and any required assets.
    </Info>
  </Step>
</Steps>

## Part 6: Deploy to PlayFlow and Test

<Steps>
  <Step title="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

    <Info>
      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](/fundamentals/api) for details.
    </Info>
  </Step>

  <Step title="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.
    </Warning>
  </Step>

  <Step title="Start Server Instance">
    1. Create and start a server instance in PlayFlow
    2. Copy the **Host** URL and **External Port** from the server details

    <Info>
      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.
    </Info>
  </Step>

  <Step title="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.
    </Tip>
  </Step>
</Steps>

## 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

<CardGroup cols={2}>
  <Card title="Lobby System" icon="users" href="/unity/lobby-overview">
    Add lobbies and matchmaking
  </Card>

  <Card title="WebGL Deployment" icon="globe" href="/guides/webgl-deployments">
    Deploy for web browsers
  </Card>
</CardGroup>

## Common Issues

<AccordionGroup>
  <Accordion title="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>

  <Accordion title="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>

  <Accordion title="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>

  <Accordion title="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()`
  </Accordion>
</AccordionGroup>

## 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/)
* **Complete Project**: [GitHub Repository](https://github.com/Xaimelee/playflow-quick-start-guide)
