Autoridade de Servidor no Godot Multiplayer (Anti-Trapaça)

Autoridade de servidor no Godot multiplayer: set_multiplayer_authority, modelo servidor-autoritativo e por que validar o input para evitar trapaça.
Se você já montou o básico de rede e agora alguém consegue "andar" pela parede ou aparecer com vida infinita, o assunto que falta é autoridade de servidor no Godot multiplayer. Autoridade é a resposta para a pergunta "quem tem o direito de decidir o estado deste objeto?", e é a diferença entre um jogo que resiste a trapaça e um que qualquer cliente modificado desmonta. Neste post a gente aprofunda o conceito: o que é autoridade por node, como usar set_multiplayer_authority, get_multiplayer_authority e is_multiplayer_authority, a diferença entre o modelo servidor-autoritativo e o cliente-autoritativo, por que o servidor precisa validar o input, e o padrão em que cada cliente tem autoridade só sobre o próprio input.
Este é um aprofundamento. Se os termos @rpc, rpc_id e MultiplayerPeer ainda soam novos, vale primeiro passar pelo multiplayer no Godot com RPC, que monta a conexão do zero. Aqui a gente parte do princípio de que você já tem duas máquinas conversando e quer que essa conversa seja confiável.
O que é autoridade, de fato
No sistema de rede high-level da Godot, todo node tem uma autoridade: um peer (identificado por um id numérico) que é considerado o dono daquele node. A autoridade é quem pode, por exemplo, mandar os valores de um MultiplayerSynchronizer para os outros, e quem pode disparar RPCs marcadas com o modo authority.
Por padrão, a autoridade de qualquer node é o id 1, que é sempre o servidor/host. Ou seja, num projeto recém-criado, o servidor manda em tudo. Isso já é o começo de um desenho saudável.
Três funções formam o núcleo da API:
set_multiplayer_authority(id: int, recursive: bool = true): define qual peer é dono do node. Orecursive(ligado por padrão) propaga a autoridade para os nodes filhos também.get_multiplayer_authority() -> int: retorna o id do peer que é dono daquele node, naquela máquina.is_multiplayer_authority() -> bool: atalho que retornatruese o peer local é a autoridade do node. É o que você mais usa no dia a dia.
Um detalhe crucial: is_multiplayer_authority() responde de forma diferente em cada máquina. O mesmo node de jogador existe em todas as telas, mas is_multiplayer_authority() só devolve true na máquina que de fato manda nele. É exatamente isso que permite escrever um script que roda em todo mundo mas só age onde deve:
extends CharacterBody2D
func _physics_process(delta: float) -> void:
# so a maquina dona deste personagem le o teclado dele
if not is_multiplayer_authority():
return
var direcao: Vector2 = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
velocity = direcao * 200.0
move_and_slide()
Esse if not is_multiplayer_authority(): return é o padrão mais repetido em código de rede na Godot. Ele garante que cada máquina só processe o input de quem ela controla, e não fique tentando mover os personagens dos outros.
Definindo a autoridade por node
A autoridade não é global, é por node. Isso é poderoso: o personagem do jogador 2 pode ter o jogador 2 como autoridade, enquanto os inimigos e o placar continuam com o servidor. A definição costuma acontecer no momento em que o jogador nasce na cena.
O ponto delicado é que set_multiplayer_authority precisa ser chamado de forma determinística em todas as máquinas, para que todas concordem sobre quem é o dono. Um jeito comum e seguro é usar o próprio nome do node como o id do peer dono:
# ao instanciar o personagem, nomeie-o com o id do peer dono
var jogador := CENA_JOGADOR.instantiate()
jogador.name = str(id_do_peer)
contei_jogadores.add_child(jogador)
# dentro do script do jogador, no _enter_tree:
func _enter_tree() -> void:
var id_dono: int = str(name).to_int()
set_multiplayer_authority(id_dono)
Como o MultiplayerSpawner replica o node com o mesmo nome em todas as telas, o _enter_tree roda em cada máquina e todas chegam ao mesmo id_dono. Ninguém precisa mandar uma RPC extra só para combinar quem manda: o nome do node já carrega essa informação.
Quer conferir quem é o dono em tempo de execução? get_multiplayer_authority() responde. É útil em logs de depuração, quando você está caçando aquele bug clássico de "duas máquinas discordando sobre quem controla o quê".
Servidor-autoritativo x cliente-autoritativo
Aqui mora a decisão de arquitetura mais importante do seu multiplayer. Existem dois modelos, e a escolha define o quanto o seu jogo resiste a trapaça.
No modelo cliente-autoritativo, cada cliente é a autoridade sobre o próprio personagem. O cliente decide sua posição, sua vida, e simplesmente informa os outros ("estou aqui, com tanta vida"). É simples de implementar, dá resposta instantânea, e funciona bem quando os jogadores confiam uns nos outros. O problema é óbvio quando existe competição: o cliente roda na máquina do jogador, e a máquina do jogador pode ser modificada. Se o cliente diz "minha vida é 9999" e o resto acredita, acabou a graça.
No modelo servidor-autoritativo, o servidor é a fonte da verdade sobre o estado do jogo. O cliente não decide sua posição real nem sua vida; ele manda a intenção de input ("quero andar para a direita", "apertei o botão de atirar") e o servidor processa, valida e devolve o estado oficial. É mais trabalhoso, adiciona latência (o round-trip até o servidor e de volta) e exige técnicas como predição para parecer responsivo, mas é o único modelo que aguenta jogadores com incentivo para trapacear.
A escolha honesta é por risco. Coop PvE entre amigos? Cliente-autoritativo pode ser suficiente e muito mais barato de construir. PvP ranqueado, com placar que importa? Servidor-autoritativo não é opcional. Não copie a arquitetura de um FPS competitivo para o seu jogo de fazenda coop só porque leu que "é o certo"; o certo é o que o seu risco pede.
Por que o servidor precisa validar o input
O coração do modelo servidor-autoritativo é uma frase: nunca confie no cliente. O cliente manda intenção, o servidor valida e aplica. A palavra que faz todo o trabalho ali é "valida".
Imagine um cliente pedindo para se mover. A versão ingênua manda a posição final direto:
# RUIM: cliente manda a posicao pronta, servidor obedece
@rpc("any_peer", "unreliable")
func mover_para(nova_pos: Vector2) -> void:
position = nova_pos # confiou cegamente, deu teleporte/wallhack
Qualquer cliente modificado manda mover_para(Vector2(99999, 0)) e atravessa o mapa. O servidor engoliu. A versão correta recebe só a direção (a intenção) e o próprio servidor calcula o movimento, respeitando a velocidade máxima e as colisões que ele conhece:
# BOM: cliente manda intencao, servidor calcula e valida
@rpc("any_peer", "unreliable")
func pedir_movimento(direcao: Vector2, delta: float) -> void:
if not multiplayer.is_server():
return
# o servidor so aceita um vetor normalizado e aplica a SUA velocidade
var passo: Vector2 = direcao.normalized() * VELOCIDADE_MAX * delta
velocity = passo / delta
move_and_slide()
O servidor normaliza a direção (então mandar um vetor gigante não adianta), aplica a velocidade que ele define, e roda a colisão na própria simulação. O cliente não consegue impor uma posição; ele só sugere para onde quer ir.
O mesmo raciocínio vale para tudo que dá vantagem. Tiro? O cliente avisa que atirou, o servidor checa cooldown, munição e se o alvo está mesmo na mira antes de aplicar dano. Compra na loja? O servidor confere se o jogador tem o ouro, não o cliente. A pergunta que você faz em cada ação é: "se o cliente mentir neste número, ele ganha vantagem?". Se a resposta é sim, a decisão é do servidor.
Validação de input é uma das camadas de proteção, não a única. Para ver o quadro completo de como blindar um jogo competitivo, vale ler sobre anti-cheat em jogos online indie, que trata das outras frentes além da autoridade de rede.
O padrão: cada cliente é dono só do próprio input
Juntando tudo, surge o desenho que a maioria dos jogos sérios usa. Ele parece contraditório à primeira vista, mas é elegante: cada cliente tem autoridade sobre o próprio input, e o servidor tem autoridade sobre o estado resultante.
Na prática, isso significa separar duas coisas que iniciantes costumam misturar:
- Input (o que o jogador quer): direção do movimento, botão de atirar, comando de pular. Isso o cliente lê localmente e manda para o servidor. É barato e dá a sensação de resposta imediata.
- Estado (o que de fato aconteceu): posição real, vida, munição, placar. Isso só o servidor decide, e os clientes recebem via sincronizador.
No Godot, um jeito de montar isso é dar ao cliente autoridade sobre um node de input (ou um MultiplayerSynchronizer configurado para enviar só as teclas, do cliente para o servidor), enquanto o node do personagem em si fica com autoridade do servidor:
extends Node
# node de input, autoridade do cliente dono do personagem
var direcao: Vector2 = Vector2.ZERO
var atirando: bool = false
func _process(_delta: float) -> void:
if not is_multiplayer_authority():
return
# so o dono le o teclado e preenche a intencao
direcao = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
atirando = Input.is_action_pressed("atirar")
O servidor, dono do personagem, lê esse input já sincronizado e aplica a lógica no _physics_process, validando tudo. Repare na inversão de fluxo em relação ao sincronizador de posição: o input flui do cliente para o servidor, enquanto a posição flui do servidor para os clientes. São dois MultiplayerSynchronizer com donos diferentes, e entender essa separação é o pulo do gato.
O benefício é duplo. O jogador sente controle imediato, porque o input dele é local. E o jogo continua honesto, porque nenhum cliente consegue impor posição, vida ou dano: ele só preenche uma caixinha de intenção que o servidor é livre para ignorar se não fizer sentido. Um cheater que mande "atirando = true" o tempo todo esbarra no cooldown que o servidor controla.
Se você quiser esconder a latência e fazer o personagem reagir no mesmo frame em que a tecla é apertada, o próximo passo é a predição de cliente com reconciliação, em que o cliente simula o movimento localmente e corrige quando o servidor discorda. Isso é um tema por si só, e dá para viver bem sem ele num jogo de ritmo mais calmo. Comece pelo servidor-autoritativo puro, meça se a latência incomoda, e só então adicione predição.
Fechando
A autoridade de servidor no Godot multiplayer cabe em poucas ideias que se reforçam. Todo node tem uma autoridade (por padrão o servidor, id 1), que você consulta com get_multiplayer_authority() e is_multiplayer_authority() e define com set_multiplayer_authority(), de forma determinística em todas as máquinas. A autoridade é por node, então você mistura e combina: o personagem de cada jogador pode ser dele, enquanto inimigos e placar ficam com o servidor.
A decisão que importa é entre cliente-autoritativo (simples, mas confia no cliente) e servidor-autoritativo (a fonte da verdade é o servidor, que valida tudo). Em qualquer jogo com incentivo para trapaça, a regra é não confiar no cliente: ele manda a intenção de input, e o servidor valida antes de aplicar. O padrão que amarra isso é dar a cada cliente autoridade apenas sobre o próprio input, deixando o estado resultante sempre nas mãos do servidor.
Para seguir daqui, revise o multiplayer no Godot com RPC com esses olhos novos (vai reparar que os modos authority e any_peer do @rpc são exatamente sobre autoridade), fuce o resto do ecossistema da engine no hub do Godot, e quando o jogo já aguentar jogadores curiosos, aprofunde as defesas com anti-cheat em jogos online indie. Autoridade bem desenhada é a fundação; o resto é construir em cima dela sem nunca deixar o cliente mandar no que não deve.
Perguntas frequentes
O que é autoridade de servidor no Godot multiplayer?
É o peer que tem o direito de decidir o estado de um node. No Godot 4 cada node tem uma autoridade de rede, consultada com get_multiplayer_authority() e definida com set_multiplayer_authority(id). No modelo servidor-autoritativo, o servidor (id 1) é a fonte da verdade: ele valida o input dos clientes e aplica o resultado. Os outros pares só recebem e exibem.
Como definir a autoridade de um node?
Com node.set_multiplayer_authority(id), passando o id do peer que deve mandar naquele node. Por padrão todo node tem autoridade 1 (o servidor). Você chama isso nos dois lados da conexão, de forma determinística, para que todas as máquinas concordem sobre quem é o dono. Dentro do código, is_multiplayer_authority() retorna true só na máquina que tem a autoridade.
Qual a diferença entre servidor-autoritativo e cliente-autoritativo?
No cliente-autoritativo, cada cliente decide seu próprio estado (posição, vida) e os outros confiam nele. É simples, mas abre a porta para trapaça, porque um cliente modificado pode mentir. No servidor-autoritativo, o servidor valida tudo e só ele muda o estado oficial; o cliente só manda intenção de input. É mais trabalhoso e tem mais latência, mas é o único modelo seguro contra trapaça.
Por que o servidor deve validar o input do cliente?
Porque o cliente roda na máquina do jogador e pode ser modificado. Se o servidor aceitar cegamente "andei 500px" ou "causei 9999 de dano", um cheater explora isso. A regra é não confiar no cliente: ele manda a intenção (direção, "atirei") e o servidor checa se faz sentido (velocidade, munição, cooldown, linha de visão) antes de aplicar.
Cada jogador tem autoridade sobre o quê?
No padrão recomendado, cada cliente tem autoridade apenas sobre o próprio input, não sobre o resultado. Ou seja, o cliente é dono de dizer para onde quer andar e quando quer atirar, mas o servidor continua dono da posição real, da vida e do placar. Isso dá resposta imediata ao jogador sem entregar o estado do jogo para quem pode mentir.
Preciso de servidor-autoritativo num jogo coop entre amigos?
Nem sempre. Se é um coop PvE entre amigos que confiam uns nos outros, o custo de validar tudo no servidor pode não valer a pena, e um modelo mais frouxo resolve. O servidor-autoritativo é obrigatório quando existe competição e incentivo para trapacear, como PvP ranqueado. Escolha o modelo pelo risco real do seu jogo, não por dogma.


