Multiplayer no Godot 4: RPC e Rede High-Level para Iniciantes

Multiplayer no Godot 4 do zero: servidor x cliente, ENetMultiplayerPeer, autoridade, MultiplayerSpawner e o anotador @rpc para chamar funções remotas com rpc() e rpc_id().
Multiplayer no Godot 4: RPC e Rede High-Level para Iniciantes
Fazer multiplayer no Godot é uma daquelas coisas que parecem mágica quando funciona e um pesadelo enquanto não funciona. A boa notícia é que a Godot 4 tem um sistema de rede high-level bem amigável, e o coração dele, o RPC (a chamada de função remota), é mais simples de entender do que o nome assusta. Neste post a gente monta o básico do zero: os conceitos de servidor e cliente, como abrir uma conexão com o ENetMultiplayerPeer, o que é autoridade, como sincronizar jogadores e, principalmente, como usar o anotador @rpc para uma máquina mandar a outra executar algo, tipo um chat ou um "fulano atirou".
Vou ser honesto logo de cara: multiplayer é difícil de testar e de depurar, muito mais do que um jogo single player. Comece pequeno, com dois jogadores na mesma rede local, e só depois pense em servidor na nuvem. Guardar esse conselho vai te poupar de muita frustração.
Servidor e cliente: quem manda em quê
Antes de qualquer código, o conceito mais importante. Numa partida em rede existe sempre um servidor (também chamado de host quando é um dos jogadores) e um ou mais clientes. O servidor é a fonte da verdade: ele decide o que é real no jogo. Os clientes mandam suas intenções ("quero andar para a direita", "atirei") e recebem de volta o estado oficial do mundo.
No começo, o mais prático é o modelo host. Um dos jogadores roda servidor e cliente ao mesmo tempo, na própria máquina, e os outros conectam nele. Não precisa de computador separado nem de nuvem para testar. Isso já te dá tudo que você precisa para aprender.
Ligado a isso vem a ideia de autoridade (authority). Cada node em rede tem um dono, um peer que tem autoridade sobre ele. A regra de ouro é: só a autoridade decide o estado daquele node, os outros só recebem. O personagem do jogador 2 tem o jogador 2 como autoridade sobre os controles dele, mas normalmente é o servidor que tem autoridade sobre coisas compartilhadas, como a vida dos inimigos ou o placar. Guardar isso na cabeça evita metade dos bugs de multiplayer, que quase sempre são duas máquinas discordando sobre quem manda em quê.
Abrindo a conexão com ENetMultiplayerPeer
O objeto que carrega a conexão de rede se chama MultiplayerPeer, e a implementação padrão para começar é o ENetMultiplayerPeer. Você cria um, manda ele hospedar ou conectar, e entrega ele para o multiplayer.multiplayer_peer. A partir daí a Godot cuida do transporte dos dados.
Hospedar (ser o servidor/host) fica assim:
extends Node
const PORTA: int = 8910
const MAX_JOGADORES: int = 4
func hospedar() -> void:
var peer := ENetMultiplayerPeer.new()
var erro: int = peer.create_server(PORTA, MAX_JOGADORES)
if erro != OK:
push_error("Nao consegui abrir o servidor na porta %d" % PORTA)
return
multiplayer.multiplayer_peer = peer
print("Servidor no ar. Meu id: %d" % multiplayer.get_unique_id())
Conectar como cliente é o espelho disso. Você precisa do endereço IP da máquina que está hospedando (na rede local, algo como 192.168.0.10) e da mesma porta:
func conectar(ip: String) -> void:
var peer := ENetMultiplayerPeer.new()
var erro: int = peer.create_client(ip, PORTA)
if erro != OK:
push_error("Nao consegui conectar em %s" % ip)
return
multiplayer.multiplayer_peer = peer
print("Tentando conectar...")
Repare no multiplayer.get_unique_id(). Todo peer conectado ganha um id numérico único. O servidor/host é sempre o id 1. Os clientes recebem ids diferentes. Esse número é a peça que te deixa mandar mensagem para um jogador específico mais adiante.
O objeto multiplayer também emite sinais úteis que você vai querer conectar para saber quando alguém entra ou sai:
func _ready() -> void:
multiplayer.peer_connected.connect(_ao_entrar)
multiplayer.peer_disconnected.connect(_ao_sair)
func _ao_entrar(id: int) -> void:
print("Jogador %d entrou" % id)
func _ao_sair(id: int) -> void:
print("Jogador %d saiu" % id)
Com isso você já tem uma conexão viva entre duas máquinas. Agora falta fazer elas trocarem informação útil, e é aí que entram o RPC e os sincronizadores.
Sincronizando jogadores: Spawner e Synchronizer
Antes de mergulhar no RPC manual, vale conhecer dois nodes que automatizam a parte mais chata: fazer os personagens aparecerem em todas as telas e manterem posição parecida.
O MultiplayerSpawner resolve o "aparecer". Você coloca ele na cena, diz qual cena de jogador ele pode instanciar e sob qual node pai. Quando o servidor cria um jogador embaixo desse pai, o spawner replica essa criação em todos os clientes automaticamente, sem você mandar RPC para cada um. Vale para quando alguém sai também: some em todo mundo.
O MultiplayerSynchronizer resolve o "manter igual". Você adiciona ele como filho do personagem e configura, num painel de propriedades, quais valores devem ser espelhados pela rede, tipo position e rotation. Ele fica enviando esses valores da autoridade para os outros pares, continuamente. É o jeito recomendado de sincronizar movimento, bem melhor que ficar mandando a posição na mão a cada frame.
A dica prática de organização: deixe o MultiplayerSpawner cuidar de quem existe, o MultiplayerSynchronizer cuidar do estado contínuo (posição), e reserve o RPC para eventos pontuais, como "atirei", "abri o baú" ou uma mensagem de chat. Misturar tudo em RPC manual é um caminho conhecido para o caos.
O coração da coisa: o anotador @rpc
Chegamos na parte que dá nome ao post. RPC quer dizer Remote Procedure Call, chamada de procedimento remoto. A ideia é linda de simples: você chama uma função numa máquina e ela roda nas outras também. No Godot 4 isso se faz marcando a função com o anotador @rpc e chamando ela com .rpc() em vez de chamar direto.
Um exemplo mínimo, um chat:
extends Node
func enviar_mensagem(texto: String) -> void:
# dispara a RPC: roda essa funcao nos outros pares
receber_mensagem.rpc(texto)
@rpc("any_peer", "call_local", "reliable")
func receber_mensagem(texto: String) -> void:
var quem: int = multiplayer.get_remote_sender_id()
print("[%d] disse: %s" % [quem, texto])
O que acontece aqui: quando alguém chama enviar_mensagem, a linha receber_mensagem.rpc(texto) faz a função receber_mensagem executar em todos os pares conectados. Dentro dela, multiplayer.get_remote_sender_id() diz qual peer disparou aquela chamada, então dá para saber quem falou.
Aquele @rpc("any_peer", "call_local", "reliable") em cima da função é onde mora toda a configuração, e cada palavra ali significa uma escolha. Vamos por partes.
any_peer x authority: quem pode disparar
O primeiro parâmetro define quem tem permissão de chamar aquela RPC.
authority(o padrão, se você não escrever nada): só a autoridade do node pode disparar essa função para os outros. Na prática, quase sempre isso quer dizer "só o servidor manda". Use para coisas que o servidor decide, tipo "o inimigo morreu".any_peer: qualquer par, incluindo os clientes, pode disparar. Você precisa disso quando o cliente é quem inicia a ação, como mandar uma mensagem de chat ou avisar que atirou.
Essa distinção é de segurança, não só de organização. Se você deixar any_peer numa função que aplica dano, um cliente mal-intencionado poderia mandar dano em qualquer um. A regra saudável é: o cliente avisa a intenção com any_peer, mas quem valida e aplica o efeito de verdade é o servidor. Nunca confie cegamente no que o cliente manda.
call_local: rodar também em quem chamou
Por padrão, uma RPC roda só nas outras máquinas, não em quem disparou. Faz sentido: você já está na sua máquina, então normalmente já executou a lógica localmente antes de avisar os outros.
Mas às vezes você quer que a mesma função rode em todo mundo, inclusive em quem chamou, para não escrever a lógica duas vezes. É para isso que serve o call_local. No exemplo do chat, colocamos call_local porque quem envia a mensagem também quer vê-la aparecer no próprio chat. Sem ele, você veria as mensagens dos outros mas não as suas.
reliable x unreliable: garantia x leveza
O último parâmetro é sobre a garantia de entrega, e é um equilíbrio entre confiabilidade e desempenho.
reliable: a Godot garante que a mensagem chega, e na ordem certa, reenviando se um pacote se perder. É mais custoso. Use para eventos que não podem falhar: um tiro, uma mensagem de chat, "o jogador morreu".unreliable: mais leve e rápido, mas pode perder pacotes e não garante ordem. Perfeito para dados que você manda o tempo todo e onde o próximo update logo corrige um que se perdeu, como a posição de um personagem. Se um frame de posição sumir, o próximo já conserta, então não vale o custo de garantir cada um.
Existe ainda unreliable_ordered, que não garante entrega mas garante a ordem, um meio-termo para casos específicos. No começo, pense só em reliable para eventos e unreliable para fluxo contínuo, e você acerta na maioria das vezes.
Mandando para um jogador só: rpc_id
O .rpc() fala com todos os pares. Mas às vezes você quer falar com um só, e aí entra o .rpc_id(), que recebe o id de destino como primeiro argumento.
O uso clássico é o cliente avisando o servidor de algo. Como o servidor é sempre o id 1, você faz assim:
func atirei(direcao: Vector2) -> void:
# cliente avisa SO o servidor que atirou
servidor_processa_tiro.rpc_id(1, direcao)
@rpc("any_peer", "reliable")
func servidor_processa_tiro(direcao: Vector2) -> void:
# roda so no servidor: aqui ele valida e cria o projetil
var atirador: int = multiplayer.get_remote_sender_id()
print("Jogador %d atirou na direcao %s" % [atirador, direcao])
# aqui o servidor spawna o projetil de verdade
Nesse desenho, o cliente não cria o tiro sozinho, ele pede ao servidor. O servidor recebe, valida (o jogador está vivo? tem munição?) e só então cria o projétil, que o MultiplayerSpawner replica para todo mundo. É o padrão correto: cliente pede, servidor decide. Note que aqui não usei call_local, porque a intenção é justamente rodar só no servidor, não na máquina do cliente que atirou.
Seja honesto: multiplayer é difícil, comece pequeno
Preciso reforçar a parte incômoda. Multiplayer é bem mais difícil de testar e depurar que single player, porque você tem duas cópias do jogo rodando, com latência entre elas, e bugs que só aparecem quando a rede engasga. Um erro comum é a lógica funcionar perfeitamente na sua máquina de teste (onde host e cliente estão no mesmo PC, com zero latência) e quebrar na hora que tem internet de verdade no meio.
Por isso o roteiro sensato é este. Primeiro, faça dois jogadores conectarem na mesma rede local e se moverem. Isso já é uma vitória enorme, comemore. Depois adicione um evento via RPC, tipo o chat, e entenda cada @rpc que você escreve. Só muito depois, quando o jogo funciona de verdade entre amigos na mesma rede, pense em botar um servidor na nuvem para gente distante jogar, e aí venha o assunto de conta, escala e dinheiro. Aliás, antes de assumir esse compromisso, vale entender quanto custa manter um jogo online no ar, porque servidor rodando o mês inteiro não é de graça e essa conta surpreende muita gente.
Duas outras funções que costumam vir logo depois do básico de conexão: um ranking online para comparar pontuações entre jogadores e um sistema de matchmaking para as pessoas encontrarem partidas sem precisar trocar IP na mão. Ambos são camadas por cima do que a gente viu aqui, então domine o RPC primeiro.
E se você sentiu que faltou base para acompanhar os exemplos, sem problema, é sinal de que dá para reforçar o fundamento antes. Um curso de criação de jogos para iniciantes te dá a estrutura de GDScript e cenas que torna o multiplayer bem menos assustador, porque metade da dificuldade de rede é, na verdade, dificuldade de organização de código que já existia antes.
Fechando
O básico de multiplayer no Godot 4 cabe em poucas ideias. Existe um servidor e clientes, cada node tem uma autoridade que decide seu estado, e você abre a conexão criando um ENetMultiplayerPeer com create_server() ou create_client() e o entregando ao multiplayer.multiplayer_peer. Para replicar quem existe e onde está, use MultiplayerSpawner e MultiplayerSynchronizer. Para eventos pontuais, use o @rpc: .rpc() fala com todos, .rpc_id() fala com um, any_peer e authority dizem quem pode disparar, call_local faz rodar também em quem chamou, e reliable contra unreliable troca garantia por leveza.
O resto é prática e paciência. Monte a conexão local com dois jogadores, faça um chat com @rpc, e só cresça a partir do que já funciona. Multiplayer recompensa quem começa pequeno e testa cada passo, e castiga quem tenta fazer tudo de uma vez.
Perguntas frequentes
Como fazer multiplayer no Godot 4?
Você usa o sistema high-level de rede. Cria um ENetMultiplayerPeer, chama create_server() no host ou create_client() em quem conecta, e atribui o peer em multiplayer.multiplayer_peer. Depois sincroniza jogadores com MultiplayerSpawner e MultiplayerSynchronizer e troca mensagens entre as máquinas com funções anotadas com @rpc.
O que é RPC no Godot?
RPC significa Remote Procedure Call, ou chamada de procedimento remoto. É quando você chama uma função numa máquina e ela executa também nas outras conectadas. No Godot 4 você marca a função com o anotador @rpc e a dispara com metodo.rpc() para todos os pares ou metodo.rpc_id(id) para um par específico.
Qual a diferença entre call_local e chamar remoto?
Por padrão uma RPC roda só nas outras máquinas, não em quem a disparou. Se você adicionar call_local no anotador @rpc, a função também executa localmente em quem chamou. Isso é útil quando quem atira também precisa ver o próprio tiro, por exemplo, sem escrever a lógica duas vezes.
Preciso de servidor dedicado para testar multiplayer?
Não. Para começar, um dos jogadores é host (roda servidor e cliente ao mesmo tempo) e o outro conecta pelo IP na mesma rede local. Servidor dedicado na nuvem só vale a pena mais tarde, quando o jogo funciona e você entende o custo de mantê-lo online.
O que é MultiplayerSpawner?
É um node que replica automaticamente a criação e a remoção de cenas nas outras máquinas. Você diz a ele qual cena pode nascer e sob qual pai; quando o servidor instancia essa cena, o spawner faz ela aparecer em todos os clientes, sem você mandar RPC manual para cada um.
Qual a diferença entre reliable e unreliable numa RPC?
Reliable garante que a mensagem chega e na ordem, ao custo de reenvios se ela se perder. Use para eventos que não podem falhar, como um tiro ou uma mensagem de chat. Unreliable é mais leve e pode perder pacotes, ideal para dados enviados o tempo todo, como a posição de um personagem, onde o próximo update logo corrige.


