Voltar para o Blog
Quest Log

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

Duas janelas do mesmo jogo Godot lado a lado, cada uma controlando um personagem que aparece na tela da outra

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.

Próximo nível
Quer aprender isso na prática?

No CursoGame.Dev você sai dos tutoriais soltos e constrói jogos publicáveis, com trilha progressiva, quests práticas e feedback real.

Conhecer a plataforma
+500 alunos4.9/5Garantia 7 dias

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.