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

Complete Project

Download the complete project files from GitHub

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:
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:
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
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
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:
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:
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”
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. IMPORTANT: Set the export path filename to “Server” (required by PlayFlow)
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.
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
  2. Create a new project for your Godot game
  3. Upload your server .zip file to PlayFlow
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
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
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!
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

Common Issues

Resources