https://github.com/Xaimelee/playflow-quick-start-guide This guide shows you how to create a very simple multiplayer game which connects to a dedicated server uploaded to PlayFlow. This setup is compatible with both web and windows platforms (TCP + TLS enabled using “wss” protocol). This guide uses Godot 4.4 but any 4.x version should work. Part 1 This covers creating the project, main scene and the UI we need for pasting the connection info before we connect to the server. 1: Create a new project. Select the “Compatibility” renderer. This lets us support web platforms. 2: Create a new scene. Save it as “main.tscn” and select the “3D Scene” root type. Right click the file after it’s saved and select “Set as Main Scene”.
3: Open main.tscn and add a child node of type “Control”. Rename it to “ConnectionDisplay”. 4: Set “Custom Minimum Size” to X:256.0px and Y:128.0px.
5: Add a child node to “ConnectionDisplay” of type “VBoxContainer”. 6: Set “Layout Mode” to Anchors and “Anchors Preset” to Full Rect.
7: Add a child node to “VBoxContainer” of type “HBoxContainer”. 8: Add a child node to “HBoxContainer” of type “TextEdit” and rename it to “UrlInput”. 9: Set “Placeholder Text” to Url.
10: Toggle on “Fit Content Height”.
11: Set “Custom Minimum Size” to X:192.0px and Y:32.0px.
12: Toggle on “Expand” under “Horizontal”.
13: Add a child node to “HBoxContainer” of type “TextEdit” and rename it to “PortInput”. 14: Set “Placeholder Text” to Port.
15: Toggle on “Fit Content Height”.
16: Toggle on “Expand” under “Horizontal”.
17: Add a child node to “VBoxContainer” of type “Button” and rename it to “ConnectButton”. 18: Add a child node to “VBoxContainer” of type “Button” and rename it to “DisconnectButton”. 19: Right click on “UrlInput” and click “Access as Unique Name”. Repeat this step for “PortInput”, “ConnectButton” and “DisconnectButton”. 20: Create a new script on “ConnectionDisplay”. Save it as “connection_display.gd” and inherit from “Control”.

21: Add the following to “connection_display.gd”.
[CODEBLOCK]
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
func _on_disconnect_button_pressed() -> void:
connect_button.visible = true
disconnect_button.visible = false
url_input.editable = true
port_input.editable = true
# 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
[CODEBLOCK]
[DISCLAIMER]
We will revisit this script later after creating the “MultiplayerManager”.
22: Connect the “pressed()” signal on “ConnectButton” to “ConnectionDisplay”. Repeat step on “DisconnectButton”.
https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html#connecting-a-signal-in-the-editor
23: Add a child node to “Main” of type “MeshInstance3D” and rename it to “Ground”. 24: Assign a new “PlaneMesh” to “MeshInstance3D” and set the “Size” to X:10m and Y:10m.
25: Add a child node to “Main” of type “Node3D” and rename it to “Players”. 26: Add a child node to “Main” of type “MultiplayerSpawner” and rename it to “MultiplayerSpawner” 27: Add a child node to “Main” of type “Camera3D”. Position this however you like, the values I used are below.
28: Add a child node to “Main” of type “DirectionalLight3D”. Rotate this however you like, my rotation is X:-60.0. That is Part 1 concluded. We have built out the main scene and UI functionality we will need for connecting to the server. Part 2
This covers creating a Multiplayer Manager which handles the networking aspects of our game.
1: Create a new. Save it as “multiplayer_manager.tscn” and select the “Node” root type.
2: Open “multiplayer_manager.tscn” and create a new script on “MultiplayerManager”. Save it as “multiplayer_manager.gd” and inherit from “Node”.
3: Add the following to “multiplayer_manager.gd”.
[CODEBLOCK]
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
[CODEBLOCK]
4: Add “multiplayer_manager.tscn” as an Autoload.
https://docs.godotengine.org/en/latest/tutorials/scripting/singletons_autoload.html#autoload
5: Update “connection_display.gd” with the following code.
5.a: Add [CODE] MultiplayerManager.start_client(url_input.text, port_input.text) [CODE] to “_on_connect_button_pressed()”.
5.b: Add [CODE] MultiplayerManager.disconnect_from_server() [CODE] to “_on_disconnect_button_pressed()”.
It should look like this below. [CODEBLOCK]
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
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()
[CODEBLOCK]
Part 3 This covers creating the player scene and adding movement inputs to the project. 1: Create a new save. Save it as “player.tscn” and select the “3D Scene” root type. 2: Open “player.tscn” and add a child node to “Player” of type “MeshInstance3D”.
3: Assign a new “CapsuleMesh” to “MeshInstance3D”. 4: Move the “MeshInstance3D” up by Y:1.0m.
5: Add a child node to “Player” of type “MultiplayerSynchronizer”. 6: Select “MultiplayerSynchronizer” and click “Add property to sync…”.
6a: Choose “Player”.
6b: Select “position” under “Node3D”.


7. Select “Player” and assign to a new group called “Players”. The group should be global.
https://docs.godotengine.org/en/stable/tutorials/scripting/groups.html#using-the-node-dock
8. Create a new script on “Player”. Save it as “player.gd” and inherit from “Node3D”.
9: Add the following code to “player.gd”.
[CODEBLOCK]
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)
[CODEBLOCK]
10: Add the following actions to the “Input Map” (Project Settings/Input Map).
10a: “move_forward”, “move_backward”, “move_left” and “move_right”.
Part 4 This covers spawning in the player scene whenever a client connects to the server. 1: Open “main.tscn” and create a new script on “MultiplayerSpawner”. Save it as “spawner.gd” and inherit from “MultiplayerSpawner”.
2: Add the following code to “spawner.gd”.
[CODEBLOCK]
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)
[CODEBLOCK]
3: Select “MultiplayerSpawner” and set “Spawn Path” to “Players”.
4: Click “Add Element” under “Auto Spawn List” and set the empty field to the file path of the “player.tscn” scene.
Part 5 This will cover exporting the project as a dedicated server. 1: Go to the “Export” window (Project/Export) and add a “Linux” preset. As good practice I will name it “Linux Server” but you can name it anything. 2: Set your “Export Path”. Make sure your file name is “Server” otherwise PlayFlow won’t be able to run your server. 3: Select the “Resources” tab and change the “Export Mode” to Export as dedicated server. Your preset should look like this. Your “Export Path” will be different but it should end with “/Server”.
4: When ready click “Export Project…” and as good practice while developing I toggle on Export With Debug. Here are what your exported files should be.
5: Use WinRAR or any other program to put your files into a “.zip” archive. Here are my WinRAR settings.
And here is what my zip file looks like when opened.
Part 6 This will cover uploading, deploying and connecting to the server. We are now ready to upload our server to PlayFlow. Follow the quickstart guide which will take you through all the necessary steps to get your server running on PlayFlow.
https://documentation.playflowcloud.com/quickstart
[IMPORTANT DISCLAIMER]
When adding the port to your configuration, select TCP for the “Protocol”, and toggle on Enable TLS. We need to have TLS enabled because we’re using “wss://” protocol for our client connection, which is required if the game is played in the browser.
You can skip steps 1 and 2 if you don’t want to run multiple instances (windows) when playing the game. 1: Back in Godot, open the “Run Instances” window (Debug/Customize Run Instances…” and toggle on Enable Multiple Instances. 2: Set the number of instances to 2.
3: Press the play button (F5). 4: Copy over the “Host” url from your server details to the “UrlInput”. 5: Copy over the “External Port” from your server details to the “PortInput”. 6: Click “Connect” and after just a moment, you’ll be connected to the server! Repeat steps 4 and 5 on the second play window to have 2 clients connected. Congratulations! You just created a very simple multiplayer game using Godot 4.x and connected to a dedicated server hosted using PlayFlow.