Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Multijogador de alto nível (High-level multiplayer)
API de alto nível vs de baixo nível
O que se segue explica as diferenças entre redes de alto e baixo nível na Godot, bem como alguns fundamentos. Se você quiser mergulhar de cabeça e adicionar redes aos seus primeiros nós, pule para Inicializando a rede abaixo. Mas certifique-se de ler o restante mais tarde!
O Godot sempre suportou rede padrão de baixo nível via UDP, TCP e alguns protocolos de nível superior, como HTTP e SSL. Esses protocolos são flexíveis e podem ser usados para quase tudo. No entanto, usá-los para sincronizar o estado do jogo manualmente pode dar um grande trabalho. Às vezes, esse trabalho não pode ser evitado ou vale a pena, por exemplo, ao trabalhar com uma implementação de servidor customizada no backend. Mas, na maioria dos casos, vale a pena considerar a API de rede de alto nível do Godot, que sacrifica um pouco do controle refinado da rede de baixo nível em prol de uma maior facilidade de uso.
Isso se deve às limitações inerentes aos protocolos de baixo nível:
O TCP garante que os pacotes sempre chegarão de forma confiável e em ordem, mas a latência geralmente é maior devido à correção de erros. Também é um protocolo bastante complexo porque ele entende o que é uma "conexão" e otimiza para objetivos que frequentemente não se adequam a aplicações como jogos multijogador. Os pacotes são armazenados em buffer para serem enviados em lotes maiores, trocando um menor overhead por pacote por uma latência mais alta. Isso pode ser útil para coisas como HTTP, mas geralmente não para jogos. Parte disso pode ser configurado e desativado (por exemplo, desativando o "algoritmo de Nagle" para a conexão TCP).
O UDP é un protocolo mais simples, que apenas envia pacotes (e não possui o conceito de uma "conexão"). A ausência de correção de erros o torna bastante rápido (baixa latência), mas os pacotes podem se perder pelo caminho ou serem recebidos na ordem errada. Somado a isso, o MTU (tamanho máximo do pacote) para o UDP é geralmente baixo (apenas algumas centenas de bytes), de modo que transmitir pacotes maiores significa dividi-los, reorganizá-los e tentar novamente se uma parte falhar.
Em geral, o TCP pode ser pensado como confiável, ordenado e lento; o UDP como não confiável, não ordenado e rápido. Devido à grande diferença de desempenho, frequentemente faz sentido reconstruir as partes do TCP desejadas para os jogos (confiabilidade opcional e ordem dos pacotes), evitando as partes indesejadas (recursos de controle de congestionamento/tráfego, algoritmo de Nagle, etc.). Por conta disso, a maioria das engines de jogos vem com uma implementação desse tipo, e o Godot não é exceção.
Em resumo, você pode usar la API de rede de baixo nível para obter o controle máximo e implementar tudo sobre protocolos de rede puros, ou usar a API de alto nível baseada na SceneTree, que faz a maior parte do trabalho pesado nos bastidores de uma forma geralmente otimizada.
Nota
A maioria das plataformas suportadas pelo Godot oferece todos ou a maior parte dos recursos de rede de alto e baixo nível mencionados. Como a rede é sempre amplamente dependente do hardware e do sistema operacional, no entanto, alguns recursos podem mudar ou não estar disponíveis em algumas plataformas de destino. Mais notavelmente, a plataforma HTML5 atualmente oferece suporte a WebSockets e WebRTC, mas carece de alguns dos recursos de nível mais alto, bem como de acesso direto a protocolos de baixo nível como TCP e UDP.
Nota
Mais sobre TCP/IP, UDP e redes: https://gafferongames.com/post/udp_vs_tcp/
O Gaffer On Games possui muitos artigos úteis sobre redes em jogos (aqui), incluindo uma abrangente introdução aos modelos de redes em jogos.
Aviso
Adicionar rede ao seu jogo traz uma certa responsabilidade. Isso pode tornar sua aplicação vulnerável se for feito incorretamente e pode levar a trapaças (cheats) ou exploits. Pode até permitir que um invasor comprometa as máquinas em que sua aplicação roda e use seus servidores para enviar spam, atacar outros ou roubar os dados dos seus usuários se eles jogarem o seu jogo.
Este é sempre o caso quando há rede envolvida e não tem nada a ver com o Godot. Você pode, claro, experimentar, mas quando lançar uma aplicação em rede, sempre tome cuidado com quaisquer possíveis problemas de segurança.
Abstração de nível médio
Antes de entrar em detalhes sobre como gostaríamos de sincronizar um jogo através da rede, pode ser útil entender como a API de rede base para sincronização funciona.
O Godot usa um objeto de nível médio chamado MultiplayerPeer. Este objeto não é feito para ser criado diretamente, mas foi projetado para que várias implementações em C++ possam fornecê-lo.
Este objeto estende de PacketPeer, portanto, herda todos os métodos úteis para serializar, enviar e receber dados. Além disso, ele adiciona métodos para definir um peer, modo de transferência, etc. Ele também inclui sinais que o avisarão quando os peers se conectarem ou desconectarem.
A interface desta classe pode abstrair a maioria dos tipos de camadas de rede, topologias e bibliotecas. Por padrão, o Godot fornece uma implementação baseada em ENet (ENetMultiplayerPeer), uma baseada em WebRTC (WebRTCMultiplayerPeer) e uma baseada em WebSocket (WebSocketMultiplayerPeer), mas isso poderia ser usado para implementar APIs móveis (para WiFi ad hoc, Bluetooth) ou APIs de rede personalizadas específicas de dispositivos/consoles.
Para a maioria dos casos comuns, o uso direto deste objeto é desencorajado, já que o Godot fornece estruturas de rede de nível ainda mais alto. Este objeto ainda é disponibilizado caso um jogo tenha necessidades específicas para uma API de nível mais baixo.
Considerações de hospedagem
Ao hospedar um servidor, os clientes em sua LAN podem se conectar usando o endereço IP interno, que geralmente tem o formato 192.168.*.*. Este endereço IP interno não é acessível por clientes fora da LAN / na Internet.
No Windows, você pode encontrar seu endereço IP interno abrindo um prompt de comando e digitando ipconfig. No macOS, abra um Terminal e digite ifconfig. No Linux, abra um terminal e digite ip addr.
Se você estiver hospedando um servidor em sua própria máquina e quiser que clientes de fora da LAN se conectem a ele, provavelmente terá que redirecionar (forward) a porta do servidor no seu roteador. Isso é necessário para tornar seu servidor acessível a partir da Internet, já que a maioria das conexões residenciais usa NAT. A API de multijogador de alto nível do Godot usa apenas UDP, portanto você deve redirecionar a porta em UDP, não apenas em TCP.
Depois de redirecionar uma porta UDP e certificar-se de que seu servidor usa essa porta, você pode usar este site para encontrar seu endereço IP público. Em seguida, informe esse endereço IP público a quaisquer clientes da Internet que desejem se conectar ao seu servidor.
A API de multijogador de alto nível do Godot usa uma versão modificada do ENet que permite suporte completo a IPv6.
Inicializando a rede
A rede de alto nível no Godot é gerenciada pela SceneTree.
Cada nó possui uma propriedade multiplayer, que é uma referência à instância de MultiplayerAPI configurada para ele pela árvore de cenas. Inicialmente, cada nó é configurado com o mesmo objeto MultiplayerAPI padrão.
É possível criar um novo objeto MultiplayerAPI e atribuí-lo a um NodePath na árvore de cenas, o que substituirá o multiplayer para o nó naquele caminho e para todos os seus descendentes. Isso permite que nós irmãos sejam configurados com diferentes peers, tornando possível rodar um servidor e um cliente simultaneamente em uma única instância do Godot.
# By default, these expressions are interchangeable.
multiplayer # Get the MultiplayerAPI object configured for this node.
get_tree().get_multiplayer() # Get the default MultiplayerAPI object.
// By default, these expressions are interchangeable.
Multiplayer; // Get the MultiplayerAPI object configured for this node.
GetTree().GetMultiplayer(); // Get the default MultiplayerAPI object.
Para inicializar a rede, um objeto MultiplayerPeer deve ser criado, inicializado como um servidor ou cliente, e passado para a MultiplayerAPI.
# Create client.
var peer = ENetMultiplayerPeer.new()
peer.create_client(IP_ADDRESS, PORT)
multiplayer.multiplayer_peer = peer
# Create server.
var peer = ENetMultiplayerPeer.new()
peer.create_server(PORT, MAX_CLIENTS)
multiplayer.multiplayer_peer = peer
// Create client.
var peer = new ENetMultiplayerPeer();
peer.CreateClient(IPAddress, Port);
Multiplayer.MultiplayerPeer = peer;
// Create server.
var peer = new ENetMultiplayerPeer();
peer.CreateServer(Port, MaxClients);
Multiplayer.MultiplayerPeer = peer;
Para encerrar a rede:
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
Multiplayer.MultiplayerPeer = null;
Aviso
Ao exportar para o Android, certifique-se de ativar a permissão INTERNET no preset de exportação do Android antes de exportar o projeto ou usar o deploy de um clique. Caso contrário, qualquer tipo de comunicação de rede será bloqueada pelo Android.
Gerenciando conexões
Cada peer recebe um ID exclusivo. O ID do servidor é sempre 1, e aos clientes é atribuído um número inteiro positivo aleatório.
Responder a conexões ou desconexões é possível conectando-se aos sinais da MultiplayerAPI:
peer_connected(id: int)Este sinal é emitido com o ID do peer recém-conectado em cada um dos outros peers, e no novo peer várias vezes, uma vez com o ID de cada um dos outros peers.peer_disconnected(id: int)Este sinal é emitido em todos os peers restantes quando um se desconecta.
O restante é emitido apenas nos clientes:
connected_to_server()connection_failed()server_disconnected()
Para obter o ID exclusivo do peer associado:
multiplayer.get_unique_id()
Multiplayer.GetUniqueId();
Para verificar se o peer é servidor ou cliente:
multiplayer.is_server()
Multiplayer.IsServer();
Chamadas de procedimento remoto
Chamadas de procedimento remoto, ou RPCs, são funções que podem ser chamadas em outros peers. Para criar uma, use a anotação @rpc antes da definição de uma função. Para chamar uma RPC, use o método rpc() do Callable para chamar em todos os peers, ou rpc_id() para chamar em um peer específico.
func _ready():
if multiplayer.is_server():
print_once_per_client.rpc()
@rpc
func print_once_per_client():
print("I will be printed to the console once per each connected client.")
public override void _Ready()
{
if (Multiplayer.IsServer())
{
Rpc(MethodName.PrintOncePerClient);
}
}
[Rpc]
private void PrintOncePerClient()
{
GD.Print("I will be printed to the console once per each connected client.");
}
As RPCs não serializarão Objetos ou Callables.
Para que uma chamada remota seja bem-sucedida, o nó de envio e o de recebimento precisam ter o mesmo NodePath, o que significa que devem ter o mesmo nome. Ao usar add_child() para nós que devem usar RPCs, defina o argumento force_readable_name como true.
Aviso
Se uma função for anotada com @rpc no script do cliente (resp. script do servidor), então esta função também deve ser declarada no script do servidor (resp. script do cliente). Ambas as RPCs devem ter a mesma assinatura, que é avaliada com um checksum de todas as RPCs. Todas as RPCs em um script são verificadas de uma só vez, e todas as RPCs devem ser declaradas tanto nos scripts do cliente quanto nos scripts do servidor, mesmo as funções que não estão em uso no momento.
A assinatura da RPC inclui a declaração @rpc(), a função, o tipo de retorno e o NodePath. Se uma RPC reside em um script anexado a /root/Main/Node1, então ela deve residir precisamente no mesmo caminho e nó tanto no script do cliente quanto no script do servidor. Os argumentos da função não são verificados quanto à correspondência entre o código do servidor e do cliente (exemplo: func sendstuff(): e func sendstuff(arg1, arg2): passarão na correspondência de assinatura).
Se essas condições não forem atendidas (se todas as RPCs não passarem na correspondência de assinatura), o script pode exibir um erro ou causar um comportamento indesejado. A mensagem de erro pode não estar relacionada à função RPC que você está construindo e testando no momento.
Veja mais explicações e soluções de problemas neste post.
A anotação pode receber vários argumentos, que possuem valores padrão. @rpc é equivalente a:
@rpc("authority", "call_remote", "reliable", 0)
[Rpc(MultiplayerApi.RpcMode.Authority, CallLocal = false, TransferMode = MultiplayerPeer.TransferModeEnum.Reliable, TransferChannel = 0)]
Os parâmetros e suas funções são os seguintes:
mode:
"authority": Apenas a autoridade multijogador pode chamar remotamente. A autoridade é o servidor por padrão, mas pode ser alterada por nó usando Node.set_multiplayer_authority."any_peer": Os clientes têm permissão para chamar remotamente. Útil para transferir entradas do usuário.
remotesync:
"call_remote": A função não será chamada no peer local."call_local": A função pode ser chamada no peer local. Útil quando o servidor também é um jogador.
transfer_mode:
"unreliable"Os pacotes não são confirmados, podem ser perdidos e podem chegar em qualquer ordem."unreliable_ordered"Os pacotes são recebidos na ordem em que foram enviados. Isso é alcançado ignorando os pacotes que chegam mais tarde se outro que foi enviado depois deles já tiver sido recebido. Pode causar perda de pacotes se usado incorretamente."reliable"Tentativas de reenvio são feitas até que os pacotes sejam confirmados, e sua ordem é preservada. Possui uma penalidade de desempenho significativa.
transfer_channel é o índice do canal.
Os 3 primeiros podem ser passados em qualquer ordem, mas transfer_channel deve ser sempre o último.
A função multiplayer.get_remote_sender_id() pode ser usada para obter o identificador exclusivo do remetente quando utilizada dentro da função chamada por uma rpc.
func _on_some_input(): # Connected to some input.
transfer_some_input.rpc_id(1) # Send the input only to the server.
# Call local is required if the server is also a player.
@rpc("any_peer", "call_local", "reliable")
func transfer_some_input():
# The server knows who sent the input.
var sender_id = multiplayer.get_remote_sender_id()
# Process the input and affect game logic.
private void OnSomeInput() // Connected to some input.
{
RpcId(1, MethodName.TransferSomeInput); // Send the input only to the server.
}
// Call local is required if the server is also a player.
[Rpc(MultiplayerApi.RpcMode.AnyPeer, CallLocal = true, TransferMode = MultiplayerPeer.TransferModeEnum.Reliable)]
private void TransferSomeInput()
{
// The server knows who sent the input.
int senderId = Multiplayer.GetRemoteSenderId();
// Process the input and affect game logic.
}
Nota
Os métodos RPC devem ser definidos em classes derivadas de Node. Tentar usar chamadas RPC de alto nível em métodos definidos apenas em classes que não sejam Node (como Resource) resultará em erros de tempo de execução.
Canais
Os protocolos de rede modernos suportam canais, que são conexões separadas dentro da conexão. Isso permite múltiplos fluxos de pacotes que não interferem uns nos outros.
Por exemplo, mensagens relacionadas ao chat do jogo e algumas das mensagens principais do gameplay devem ser enviadas de forma confiável, mas uma mensagem de gameplay não deve esperar que uma mensagem de chat seja confirmada. Isso pode ser alcançado usando canais diferentes.
Os canais também são úteis quando usados com o modo de transferência não confiável ordenado (unreliable ordered). Enviar pacotes de tamanho variável com este modo de transferência pode causar perda de pacotes, já que os pacotes que demoram mais para chegar são ignorados. Separá-los em múltiplos fluxos de pacotes homogêneos usando canais permite a transferência ordenada com pouca perda de pacotes e sem a penalidade de latência causada pelo modo confiável.
O canal padrão com índice 0 é, na verdade, três canais diferentes — um para cada modo de transferência.
Exemplo de implementação de lobby
Este é um exemplo de lobby que pode gerenciar a entrada e saída de peers, notificar cenas de UI por meio de sinais e iniciar o jogo após todos os clientes terem carregado a cena do jogo.
extends Node
# Autoload named Lobby
# These signals can be connected to by a UI lobby scene or the game scene.
signal player_connected(peer_id, player_info)
signal player_disconnected(peer_id)
signal server_disconnected
const PORT = 7000
const DEFAULT_SERVER_IP = "127.0.0.1" # IPv4 localhost
const MAX_CONNECTIONS = 20
# This will contain player info for every player,
# with the keys being each player's unique IDs.
var players = {}
# This is the local player info. This should be modified locally
# before the connection is made. It will be passed to every other peer.
# For example, the value of "name" can be set to something the player
# entered in a UI scene.
var player_info = {"name": "Name"}
var players_loaded = 0
func _ready():
multiplayer.peer_connected.connect(_on_player_connected)
multiplayer.peer_disconnected.connect(_on_player_disconnected)
multiplayer.connected_to_server.connect(_on_connected_ok)
multiplayer.connection_failed.connect(_on_connected_fail)
multiplayer.server_disconnected.connect(_on_server_disconnected)
func join_game(address = ""):
if address.is_empty():
address = DEFAULT_SERVER_IP
var peer = ENetMultiplayerPeer.new()
var error = peer.create_client(address, PORT)
if error:
return error
multiplayer.multiplayer_peer = peer
func create_game():
var peer = ENetMultiplayerPeer.new()
var error = peer.create_server(PORT, MAX_CONNECTIONS)
if error:
return error
multiplayer.multiplayer_peer = peer
players[1] = player_info
player_connected.emit(1, player_info)
func remove_multiplayer_peer():
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
players.clear()
# When the server decides to start the game from a UI scene,
# do Lobby.load_game.rpc(filepath)
@rpc("call_local", "reliable")
func load_game(game_scene_path):
get_tree().change_scene_to_file(game_scene_path)
# Every peer will call this when they have loaded the game scene.
@rpc("any_peer", "call_local", "reliable")
func player_loaded():
if multiplayer.is_server():
players_loaded += 1
if players_loaded == players.size():
$/root/Game.start_game()
players_loaded = 0
# When a peer connects, send them my player info.
# This allows transfer of all desired data for each player, not only the unique ID.
func _on_player_connected(id):
_register_player.rpc_id(id, player_info)
@rpc("any_peer", "reliable")
func _register_player(new_player_info):
var new_player_id = multiplayer.get_remote_sender_id()
players[new_player_id] = new_player_info
player_connected.emit(new_player_id, new_player_info)
func _on_player_disconnected(id):
players.erase(id)
player_disconnected.emit(id)
func _on_connected_ok():
var peer_id = multiplayer.get_unique_id()
players[peer_id] = player_info
player_connected.emit(peer_id, player_info)
func _on_connected_fail():
remove_multiplayer_peer()
func _on_server_disconnected():
remove_multiplayer_peer()
players.clear()
server_disconnected.emit()
using Godot;
public partial class Lobby : Node
{
public static Lobby Instance { get; private set; }
// These signals can be connected to by a UI lobby scene or the game scene.
[Signal]
public delegate void PlayerConnectedEventHandler(int peerId, Godot.Collections.Dictionary<string, string> playerInfo);
[Signal]
public delegate void PlayerDisconnectedEventHandler(int peerId);
[Signal]
public delegate void ServerDisconnectedEventHandler();
private const int Port = 7000;
private const string DefaultServerIP = "127.0.0.1"; // IPv4 localhost
private const int MaxConnections = 20;
// This will contain player info for every player,
// with the keys being each player's unique IDs.
private Godot.Collections.Dictionary<long, Godot.Collections.Dictionary<string, string>> _players = new Godot.Collections.Dictionary<long, Godot.Collections.Dictionary<string, string>>();
// This is the local player info. This should be modified locally
// before the connection is made. It will be passed to every other peer.
// For example, the value of "name" can be set to something the player
// entered in a UI scene.
private Godot.Collections.Dictionary<string, string> _playerInfo = new Godot.Collections.Dictionary<string, string>()
{
{ "Name", "PlayerName" },
};
private int _playersLoaded = 0;
public override void _Ready()
{
Instance = this;
Multiplayer.PeerConnected += OnPlayerConnected;
Multiplayer.PeerDisconnected += OnPlayerDisconnected;
Multiplayer.ConnectedToServer += OnConnectOk;
Multiplayer.ConnectionFailed += OnConnectionFail;
Multiplayer.ServerDisconnected += OnServerDisconnected;
}
private Error JoinGame(string address = "")
{
if (string.IsNullOrEmpty(address))
{
address = DefaultServerIP;
}
var peer = new ENetMultiplayerPeer();
Error error = peer.CreateClient(address, Port);
if (error != Error.Ok)
{
return error;
}
Multiplayer.MultiplayerPeer = peer;
return Error.Ok;
}
private Error CreateGame()
{
var peer = new ENetMultiplayerPeer();
Error error = peer.CreateServer(Port, MaxConnections);
if (error != Error.Ok)
{
return error;
}
Multiplayer.MultiplayerPeer = peer;
_players[1] = _playerInfo;
EmitSignal(SignalName.PlayerConnected, 1, _playerInfo);
return Error.Ok;
}
private void RemoveMultiplayerPeer()
{
Multiplayer.MultiplayerPeer = null;
_players.Clear();
}
// When the server decides to start the game from a UI scene,
// do Rpc(Lobby.MethodName.LoadGame, filePath);
[Rpc(CallLocal = true,TransferMode = MultiplayerPeer.TransferModeEnum.Reliable)]
private void LoadGame(string gameScenePath)
{
GetTree().ChangeSceneToFile(gameScenePath);
}
// Every peer will call this when they have loaded the game scene.
[Rpc(MultiplayerApi.RpcMode.AnyPeer,CallLocal = true,TransferMode = MultiplayerPeer.TransferModeEnum.Reliable)]
private void PlayerLoaded()
{
if (Multiplayer.IsServer())
{
_playersLoaded += 1;
if (_playersLoaded == _players.Count)
{
GetNode<Game>("/root/Game").StartGame();
_playersLoaded = 0;
}
}
}
// When a peer connects, send them my player info.
// This allows transfer of all desired data for each player, not only the unique ID.
private void OnPlayerConnected(long id)
{
RpcId(id, MethodName.RegisterPlayer, _playerInfo);
}
[Rpc(MultiplayerApi.RpcMode.AnyPeer,TransferMode = MultiplayerPeer.TransferModeEnum.Reliable)]
private void RegisterPlayer(Godot.Collections.Dictionary<string, string> newPlayerInfo)
{
int newPlayerId = Multiplayer.GetRemoteSenderId();
_players[newPlayerId] = newPlayerInfo;
EmitSignal(SignalName.PlayerConnected, newPlayerId, newPlayerInfo);
}
private void OnPlayerDisconnected(long id)
{
_players.Remove(id);
EmitSignal(SignalName.PlayerDisconnected, id);
}
private void OnConnectOk()
{
int peerId = Multiplayer.GetUniqueId();
_players[peerId] = _playerInfo;
EmitSignal(SignalName.PlayerConnected, peerId, _playerInfo);
}
private void OnConnectionFail()
{
Multiplayer.MultiplayerPeer = null;
}
private void OnServerDisconnected()
{
Multiplayer.MultiplayerPeer = null;
_players.Clear();
EmitSignal(SignalName.ServerDisconnected);
}
}
O nó raiz da cena do jogo deve ser nomeado Game. No script anexado a ele:
extends Node3D # Or Node2D.
func _ready():
# Preconfigure game.
Lobby.player_loaded.rpc_id(1) # Tell the server that this peer has loaded.
# Called only on the server.
func start_game():
# All peers are ready to receive RPCs in this scene.
using Godot;
public partial class Game : Node3D // Or Node2D.
{
public override void _Ready()
{
// Preconfigure game.
Lobby.Instance.RpcId(1, Lobby.MethodName.PlayerLoaded); // Tell the server that this peer has loaded.
}
// Called only on the server.
public void StartGame()
{
// All peers are ready to receive RPCs in this scene.
}
}
Exportando para servidores dedicados
Depois de criar um jogo multijogador, você pode querer exportá-lo para rodar em um servidor dedicado sem GPU disponível. Veja Exportando para servidores dedicados para mais informações.
Nota
Os exemplos de código nesta página não foram projetados para rodar em um servidor dedicado. Você terá que modificá-los para que o servidor não seja considerado um jogador. Você também terá que modificar o mecanismo de início do jogo para que o primeiro jogador que entrar possa iniciar o jogo.
Autenticação
Antes de hospedar seu jogo online para um público público, você pode considerar adicionar autenticação e proteger suas RPCs contra acesso não autenticado. Você pode usar o mecanismo de autenticação integrado da SceneMultiplayer para isso.
No servidor:
# This goes after `multiplayer.multiplayer_peer = peer`.
multiplayer.auth_timout = 3
multiplayer.auth_callback = func(peer_id: int, payload: PackedByteArray):
var auth_data: Dictionary = JSON.parse_string(payload.get_string_from_utf8())
# Your authentication logic (such as checking the supplied username/password against a database)
# Tell the MultiplayerAPI that the authentication was successful
if authentication_successful:
multiplayer.complete_auth(peer_id)
No cliente:
# This goes after `multiplayer.multiplayer_peer = peer`.
multiplayer.auth_callback = func:
# We have to set this on the client for the `peer_authenticating`
# signal to emit.
pass
multiplayer.peer_authenticating.connect(func(peer_id: int):
var auth_data = {
"username": "username",
"password": "password",
}
multiplayer.send_auth(1, JSON.stringify(auth_data).to_utf8_buffer())
# Tell the MultiplayerAPI that the authentication was successful.
multiplayer.complete_auth(peer_id)
Assim que os métodos complete_auth() tanto do cliente quanto do servidor forem chamados, a conexão é considerada estabelecida e os sinais connected_to_server e peer_connected são disparados.
Design de multiplayer seguro
A API de multijogador de alto nível do Godot facilita a criação de jogos em rede, mas não torna a lógica do jogo segura automaticamente. Para jogos multijogador competitivos ou persistentes, trate todas as entradas do cliente como não confiáveis.
Um erro comum é permitir que os clientes decidam com autoridade estados importantes do jogo, como posição do jogador, resultados de combate, alterações de inventário ou desfechos de partidas. Isso pode tornar a trapaça muito mais fácil e resultar em dessincronizações ("desync") mais frequentes.
No geral, prefira os seguintes padrões:
Use lógica de autoridade do servidor para decisões críticas de gameplay.
Valide os argumentos de RPC antes de aplicá-los ao estado do jogo.
Evite confiar em posições, temporizadores, cooldowns ou valores de recursos relatados pelo cliente sem verificações.
Adicione verificações de segurança e limites de taxa (rate limits) para ações que podem ser acionadas com frequência.
Em resumo, você deve projetar sua rede de forma que o servidor continue sendo a fonte da verdade para estados importantes.
Por exemplo, em vez de aceitar a posição final de um cliente diretamente, considere enviar a entrada do jogador ou a intenção de movimento para a autoridade/servidor, para então validar e aplicar o resultado lá. Isso traz algumas desvantagens (como desempenho do lado do servidor e complexidade devido à necessidade de previsão do lado do cliente), mas tornará muito mais difícil para os atacantes trapacearem enviando dados falsificados.
Veja Choosing the right network model for your multiplayer game para mais informações sobre diferentes modelos de multijogador e suas implicações de segurança.