Multiplayer no Godot: Tutorial Completo de Networking Para Iniciantes

Aprenda a criar jogos multiplayer no Godot Engine. Tutorial passo a passo sobre networking, sincronização, High-Level Multiplayer API e implementação prática.
Índice do Conteúdo
Artigos Relacionados

Física de Jogos no Godot: Tutorial Completo de CharacterBody e RigidBody
01/10/2025

C# vs GDScript no Godot: Qual Linguagem Escolher em 2025?
01/10/2025

Como Criar Seu Primeiro Jogo Mobile em 2025: Guia Completo Android e iOS
01/10/2025

Como Fazer Um Jogo do Zero: Guia Completo Para Iniciantes em 2025
01/10/2025

Design de Levels: Princípios e Práticas Para Criar Níveis Memoráveis
01/10/2025
Multiplayer no Godot: Tutorial Completo de Networking Para Iniciantes
Multiplayer transforma jogos. A experiência de competir ou colaborar com outros humanos adiciona imprevisibilidade, emoção e longevity que single-player raramente alcança. Mas implementar networking é notoriamente complexo - sincronização, latência, segurança e bugs sutis aguardam desenvolvedores inexperientes.
No Godot Engine, a High-Level Multiplayer API torna networking significativamente mais acessível. Com sistemas de RPC (Remote Procedure Calls), sincronização automática e abstrações inteligentes, você pode criar experiências multiplayer funcionais sem se perder em baixo nível de sockets e protocolos.
Neste tutorial completo, vou te guiar desde conceitos fundamentais de networking até implementação prática de jogo multiplayer no Godot. Você aprenderá arquitetura cliente-servidor, sincronização de estado, RPCs, autoridade de nodes, prediction, interpolation e muito mais. Ao final, você terá conhecimento sólido para criar seus próprios jogos multiplayer.
Fundamentos de Networking em Jogos
Antes de código, precisamos entender conceitos fundamentais.
Cliente-Servidor vs Peer-to-Peer
Cliente-Servidor (Recomendado):
- Um player (ou servidor dedicado) é autoridade
- Clientes enviam input, servidor processa e retorna estado
- Servidor previne cheating (valida tudo)
- Mais complexo mas mais robusto
Peer-to-Peer (P2P):
- Todos os players conectam entre si
- Sem servidor central
- Mais simples para jogos casuais
- Vulnerável a cheating
Godot suporta ambos, mas cliente-servidor é padrão para jogos sérios.
Latência e Tick Rate
Latência (Ping):
- Tempo para mensagem ir e voltar
- <50ms: Excelente
- 50-100ms: Bom
- 100-150ms: Aceitável
- 150ms+: Notável, precisa compensation
Tick Rate:
- Frequência de updates do servidor
- 20-30 ticks/segundo: Típico para casual
- 64+ ticks/segundo: Competitivo (CS:GO usa 64/128)
- Maior = mais responsivo mas mais bandwidth
Bandwidth:
- Dados enviados/recebidos por segundo
- Minimize enviando apenas o necessário
- Compression ajuda
Sincronização de Estado
Problema: Cada cliente tem versão local do game state. Como mantê-los sincronizados?
Abordagens:
1. State Synchronization:
- Servidor envia estado completo periodicamente
- Simples mas bandwidth-intensive
- Godot: Automatizado via MultiplayerSynchronizer
2. Input Synchronization:
- Clientes enviam input, servidor simula
- Menos bandwidth
- Requer gameplay determinístico
3. Snapshot Interpolation:
- Servidor envia snapshots, clientes interpolam entre eles
- Smooth visual mas ligeiramente atrasado
- Godot 4.x suporta nativamente
Godot combina todas três através da High-Level API.
Descubra Seu Perfil em Game Development
Multiplayer networking é área técnica e desafiadora. Você é o tipo de desenvolvedor que ama resolver problemas complexos de sistemas, ou prefere focar em design e criatividade? Descubra seu perfil ideal.
Setup Básico: Criando Servidor e Cliente
Vamos começar com exemplo mínimo funcional.
Estrutura de Projeto
Multiplayer_Game/
├── scenes/
│ ├── Player.tscn
│ ├── World.tscn
│ └── UI.tscn
├── scripts/
│ ├── player.gd
│ ├── network.gd
│ └── world.gd
└── main.tscn
Network Manager (Singleton)
Crie
scripts/network.gd
extends Node
const PORT = 7777
const MAX_PLAYERS = 4
var peer = ENetMultiplayerPeer.new()
func create_server():
peer.create_server(PORT, MAX_PLAYERS)
multiplayer.multiplayer_peer = peer
print("Servidor criado na porta ", PORT)
# Callbacks
multiplayer.peer_connected.connect(_on_player_connected)
multiplayer.peer_disconnected.connect(_on_player_disconnected)
func join_server(address: String):
peer.create_client(address, PORT)
multiplayer.multiplayer_peer = peer
print("Conectando a ", address)
multiplayer.connected_to_server.connect(_on_connected_to_server)
multiplayer.connection_failed.connect(_on_connection_failed)
func _on_player_connected(id: int):
print("Player conectou: ", id)
func _on_player_disconnected(id: int):
print("Player desconectou: ", id)
func _on_connected_to_server():
print("Conectado ao servidor com sucesso!")
func _on_connection_failed():
print("Falha ao conectar ao servidor")
UI Para Criar/Juntar
Crie UI simples com botões:
extends Control
@onready var host_button = $VBoxContainer/HostButton
@onready var join_button = $VBoxContainer/JoinButton
@onready var address_input = $VBoxContainer/AddressInput
func _ready():
host_button.pressed.connect(_on_host_pressed)
join_button.pressed.connect(_on_join_pressed)
func _on_host_pressed():
Network.create_server()
get_tree().change_scene_to_file("res://scenes/World.tscn")
func _on_join_pressed():
var address = address_input.text if address_input.text != "" else "127.0.0.1"
Network.join_server(address)
get_tree().change_scene_to_file("res://scenes/World.tscn")
Testando:
- Run game, clique "Host" (cria servidor)
- Run game novamente, digite "127.0.0.1", clique "Join"
- Se conectou, você tem networking básico funcionando!
Sincronizando Players
Agora vamos criar players sincronizados entre clientes.
Player Scene
Crie
Player.tscn
Player (CharacterBody2D)
├── Sprite2D
├── CollisionShape2D
└── Camera2D
Player Script com Multiplayer Authority
extends CharacterBody2D
const SPEED = 300.0
# O ID deste player
@export var player_id: int = 1
func _enter_tree():
# Define autoridade: apenas o dono controla este node
set_multiplayer_authority(player_id)
func _physics_process(delta):
# Apenas o dono processa input
if not is_multiplayer_authority():
return
# Input local
var direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
velocity = direction * SPEED
move_and_slide()
# Sincroniza posição via RPC
rpc("sync_position", position)
@rpc("unreliable")
func sync_position(pos: Vector2):
# Clientes remotos recebem posição
if not is_multiplayer_authority():
position = pos
Spawning Players no Servidor
No
World.gd
extends Node2D
const PLAYER_SCENE = preload("res://scenes/Player.tscn")
@onready var players_node = $Players
func _ready():
if multiplayer.is_server():
multiplayer.peer_connected.connect(_on_player_connected)
multiplayer.peer_disconnected.connect(_on_player_disconnected)
# Spawn player do próprio servidor
spawn_player(1)
func _on_player_connected(id: int):
print("Spawnando player: ", id)
spawn_player(id)
func _on_player_disconnected(id: int):
print("Removendo player: ", id)
if players_node.has_node(str(id)):
players_node.get_node(str(id)).queue_free()
func spawn_player(id: int):
var player = PLAYER_SCENE.instantiate()
player.player_id = id
player.name = str(id)
players_node.add_child(player, true)
Problema: Clientes não vêem outros players spawning!
Spawning Replicado
Use MultiplayerSpawner:
# Em World.tscn, adicione:
MultiplayerSpawner
├── Spawn Path: Players (aponta para node Players)
# Configure no Inspector:
# - Auto Spawn List: Adicione Player.tscn
# - Spawn Function: _spawn_player
Atualização de
World.gd
extends Node2D
const PLAYER_SCENE = preload("res://scenes/Player.tscn")
@onready var players_node = $Players
@onready var spawner = $MultiplayerSpawner
func _ready():
if multiplayer.is_server():
multiplayer.peer_connected.connect(_on_player_connected)
multiplayer.peer_disconnected.connect(_on_player_disconnected)
spawn_player(1)
func _on_player_connected(id: int):
spawn_player(id)
func _on_player_disconnected(id: int):
if players_node.has_node(str(id)):
players_node.get_node(str(id)).queue_free()
func spawn_player(id: int):
var player = PLAYER_SCENE.instantiate()
player.player_id = id
player.name = str(id)
# Add como child - MultiplayerSpawner sincroniza automaticamente!
players_node.add_child(player, true)
Agora players aparecem para todos os clientes automaticamente!
RPCs: Remote Procedure Calls
RPCs permitem chamar funções remotamente em outros peers.
Tipos de RPC
@rpc("any_peer"): Qualquer peer pode chamar (default, perigoso!) @rpc("authority"): Apenas autoridade pode chamar (seguro) @rpc("call_local"): Executa localmente também @rpc("reliable"): Garantido chegar (TCP-like) @rpc("unreliable"): Pode perder pacote (UDP-like, mais rápido)
Exemplo: Chat System
extends Control
@onready var chat_log = $VBoxContainer/ChatLog
@onready var input_field = $VBoxContainer/HBoxContainer/InputField
@onready var send_button = $VBoxContainer/HBoxContainer/SendButton
func _ready():
send_button.pressed.connect(_on_send_pressed)
func _on_send_pressed():
var message = input_field.text
if message.strip_edges() == "":
return
# Envia mensagem para servidor
rpc_id(1, "receive_message", multiplayer.get_unique_id(), message)
input_field.clear()
@rpc("any_peer", "call_local", "reliable")
func receive_message(sender_id: int, message: String):
# Apenas servidor processa e broadcast
if multiplayer.is_server():
# Valida mensagem (anti-spam, profanity filter, etc.)
var validated_message = validate_message(message)
# Broadcast para todos
rpc("display_message", sender_id, validated_message)
@rpc("authority", "reliable")
func display_message(sender_id: int, message: String):
# Todos os clientes exibem
chat_log.text += "\n[%d]: %s" % [sender_id, message]
func validate_message(message: String) -> String:
# Truncate, remove caracteres perigosos, etc.
return message.substr(0, 200)
Fluxo:
- Cliente chama → envia para servidor
rpc_id(1, "receive_message", ...)
- Servidor valida em
receive_message()
- Servidor chama → broadcast para todos
rpc("display_message", ...)
- Todos exibem em
display_message()
Segurança: Servidor sempre valida. Nunca confie em input de cliente!
MultiplayerSynchronizer: Sincronização Automática
Para propriedades que mudam frequentemente, use MultiplayerSynchronizer.
Setup
Em
Player.tscn
Player (CharacterBody2D)
├── ...
└── MultiplayerSynchronizer
No Inspector do MultiplayerSynchronizer:
- Root Path: . (aponta para Player)
- Replication Config: Crie novo SceneReplicationConfig
No SceneReplicationConfig, adicione propriedades:
- (Vector2)
position
- (Vector2)
velocity
- (float)
rotation
Configurações:
- Spawn: Sincronizar quando spawna
- Replication Interval: 0.1 (10 updates/segundo)
Player Script Simplificado
extends CharacterBody2D
const SPEED = 300.0
@export var player_id: int = 1
func _enter_tree():
set_multiplayer_authority(player_id)
func _physics_process(delta):
if not is_multiplayer_authority():
return
var direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
velocity = direction * SPEED
move_and_slide()
# position e velocity são sincronizados automaticamente!
# Não precisa de RPC manual
Vantagem: Zero código de networking no script do player. MultiplayerSynchronizer handled everything!
Interpolation Para Movimento Suave
Sincronização automática pode resultar em movimento "jittery" devido a latência.
Client-Side Interpolation
extends CharacterBody2D
const SPEED = 300.0
const INTERPOLATION_SPEED = 10.0
@export var player_id: int = 1
var target_position: Vector2
var target_velocity: Vector2
func _enter_tree():
set_multiplayer_authority(player_id)
func _ready():
if not is_multiplayer_authority():
# Clientes remotos inicializam targets
target_position = position
target_velocity = velocity
func _physics_process(delta):
if is_multiplayer_authority():
# Owner: movimento normal
var direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
velocity = direction * SPEED
move_and_slide()
else:
# Remote: interpola para target
position = position.lerp(target_position, INTERPOLATION_SPEED * delta)
velocity = velocity.lerp(target_velocity, INTERPOLATION_SPEED * delta)
# Callback quando MultiplayerSynchronizer atualiza
func _on_position_changed(new_pos: Vector2):
if not is_multiplayer_authority():
target_position = new_pos
func _on_velocity_changed(new_vel: Vector2):
if not is_multiplayer_authority():
target_velocity = new_vel
Configure callbacks no MultiplayerSynchronizer para chamar
_on_position_changed()
Lidando com Latência: Prediction e Reconciliation
Para jogos responsivos, clientes precisam prever movimentos.
Client-Side Prediction
extends CharacterBody2D
const SPEED = 300.0
@export var player_id: int = 1
# Histórico de inputs
var input_history: Array = []
var last_processed_input: int = 0
func _physics_process(delta):
if not is_multiplayer_authority():
return
# Captura input
var direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
var input_data = {
"seq": Time.get_ticks_msec(),
"direction": direction
}
# Aplica imediatamente (prediction)
velocity = direction * SPEED
move_and_slide()
# Envia para servidor
rpc_id(1, "process_input", input_data)
# Armazena para reconciliation
input_history.append(input_data)
if input_history.size() > 100:
input_history.pop_front()
@rpc("any_peer", "reliable")
func process_input(input_data: Dictionary):
if not multiplayer.is_server():
return
# Servidor processa
var direction = input_data.direction
velocity = direction * SPEED
move_and_slide()
# Envia estado autoritativo de volta
rpc_id(input_data.get("sender_id"), "reconcile_state", input_data.seq, position)
@rpc("authority", "reliable")
func reconcile_state(seq: int, server_position: Vector2):
# Cliente compara posição servidor com prediction
var position_error = position.distance_to(server_position)
if position_error > 10.0: # Threshold
# Correção: reaplica inputs após o seq recebido
position = server_position
for input_data in input_history:
if input_data.seq > seq:
# Reaplica input
velocity = input_data.direction * SPEED
move_and_slide()
# Limpa inputs antigos
input_history = input_history.filter(func(i): return i.seq > seq)
Complexo mas essential para jogos competitivos. Para jogos casuais, interpolation simples é suficiente.
Domine Todas as Áreas de Game Development
Multiplayer networking é apenas uma das muitas skills necessárias. Combinado com arte, design, áudio e marketing, você pode criar experiências completas e bem-sucedidas. Aprenda todas as disciplinas ou a colaborar efetivamente.
Segurança: Anti-Cheat Básico
Nunca confie no cliente. Sempre valide no servidor.
Server Authority
# MAU: Cliente processa gameplay crítico
@rpc("any_peer")
func take_damage(amount: int):
health -= amount # Cliente pode enviar amount = 0!
# BOM: Servidor valida
@rpc("any_peer")
func request_take_damage(attacker_id: int):
if not multiplayer.is_server():
return
# Servidor verifica se ataque é válido
if is_valid_attack(attacker_id, multiplayer.get_remote_sender_id()):
var damage = calculate_damage(attacker_id)
apply_damage(damage)
rpc("sync_health", health)
@rpc("authority")
func sync_health(new_health: int):
health = new_health
Validações Comuns
Movimento:
- Cliente envia input, servidor simula
- Servidor valida velocidade máxima
- Detecta teleporting impossível
Combat:
- Servidor verifica line of sight
- Valida range de ataque
- Verifica cooldowns no servidor
Resources/Inventory:
- Servidor mantém authoritative state
- Cliente apenas exibe
- Transações validadas no servidor
Debugging Multiplayer
Multiplayer bugs são notoriamente difíceis de debug.
Ferramentas
Godot Remote Debugger:
- Conecta a múltiplas instâncias simultaneamente
- Monitora networking traffic
- Debug > Deploy Remote Debug: Dois Clients
Print Statements Com Contexto:
func debug_print(message: String):
var role = "SERVER" if multiplayer.is_server() else "CLIENT"
var id = multiplayer.get_unique_id()
print("[%s %d] %s" % [role, id, message])
Network Profiler: Enable em Project Settings > Debug > Network > Monitor
Simulando Latência
# Adiciona lag artificial para testar
const SIMULATED_LAG_MS = 100
func _send_with_lag(callable: Callable):
await get_tree().create_timer(SIMULATED_LAG_MS / 1000.0).timeout
callable.call()
Otimização de Bandwidth
Multiplayer consome bandwidth. Otimize agressivamente.
Técnicas
1. Envie Apenas Mudanças:
var last_sent_position: Vector2
func _physics_process(delta):
if position.distance_to(last_sent_position) > 5.0:
rpc("sync_position", position)
last_sent_position = position
2. Use Unreliable Para Dados Frequentes:
@rpc("unreliable") # OK perder pacote ocasional
func sync_position(pos: Vector2):
position = pos
@rpc("reliable") # PRECISA chegar
func player_died():
health = 0
3. Comprima Dados:
# Ao invés de Vector2 (64 bits), use dois shorts (32 bits)
func compress_position(pos: Vector2) -> PackedInt32Array:
return PackedInt32Array([int(pos.x), int(pos.y)])
4. Reduza Tick Rate:
var tick_rate = 20 # 20 updates/segundo, não 60
var time_since_last_tick = 0.0
func _physics_process(delta):
time_since_last_tick += delta
if time_since_last_tick >= 1.0 / tick_rate:
sync_state()
time_since_last_tick = 0.0
Conclusão: Multiplayer é Jornada
Multiplayer networking é uma das áreas mais complexas de game development. Não espere dominar imediatamente.
Recapitulando conceitos essenciais:
- ✅ Cliente-Servidor é arquitetura recomendada
- ✅ MultiplayerSynchronizer automatiza sincronização de propriedades
- ✅ RPCs permitem chamar funções remotamente
- ✅ Interpolation suaviza movimento apesar de latência
- ✅ Server authority previne cheating
- ✅ Otimize bandwidth enviando apenas o necessário
- ✅ Test com latência real - localhost não revela problemas
Seu roadmap de aprendizado:
Semana 1: Implemente chat multiplayer básico Semana 2: Crie movimento de player sincronizado Semana 3: Adicione interpolation e prediction Semana 4: Implemente gameplay (combat, objectives)
Recursos adicionais:
Multiplayer é iterativo. Seu primeiro jogo terá lag, bugs e design questionável. Tudo bem. Cada projeto ensina lições cruciais para o próximo.
Os jogos multiplayer que você admira foram construídos por teams que falharam centenas de vezes antes de acertar. Persistência e iteração são chaves.
Então crie seu projeto multiplayer básico hoje. Conecte dois clients. Faça eles verem um ao outro. Comemore cada pequena vitória.
A jornada para criar experiências multiplayer incríveis começa com "Hello, World!" em rede. Envie esse primeiro pacote agora.
Índice do Conteúdo
Artigos Relacionados

Física de Jogos no Godot: Tutorial Completo de CharacterBody e RigidBody
01/10/2025

C# vs GDScript no Godot: Qual Linguagem Escolher em 2025?
01/10/2025

Como Criar Seu Primeiro Jogo Mobile em 2025: Guia Completo Android e iOS
01/10/2025

Como Fazer Um Jogo do Zero: Guia Completo Para Iniciantes em 2025
01/10/2025

Design de Levels: Princípios e Práticas Para Criar Níveis Memoráveis
01/10/2025