Voltar para o Blog
Quest Log

Multiplayer Online na Unity com Relay e Lobby

Dois computadores em lugares diferentes ligados por uma linha de luz que passa por uma nuvem central com um código de seis letras

Unity Netcode Relay e Lobby com Multiplayer Services: crie sessão, gere código de entrada, liste salas e conecte jogadores sem servidor próprio nem abrir porta.

Ferramenta:UnityC#

Multiplayer Online na Unity com Relay e Lobby

O jeito mais simples de fazer unity netcode relay hoje é usar o pacote Multiplayer Services com a API de Sessions: um jogador cria a sessão, a Unity aloca um servidor de Relay e devolve um código de entrada de poucas letras, o amigo digita o código e entra. Ninguém abre porta no roteador, ninguém precisa de IP, e você não paga servidor próprio enquanto estiver dentro da franquia gratuita.

Este tutorial monta isso com o Netcode for GameObjects no Unity 6.3 LTS: os pacotes certos, o projeto ligado ao Unity Cloud, o código para criar e entrar por código, uma lista de salas no estilo lobby, os limites do plano gratuito e os erros que aparecem no caminho.

Unity Netcode e Relay: por que Relay e Lobby existem

Quando você faz multiplayer em rede local, o cliente conecta direto no IP do host. Pela internet, isso quase nunca funciona: o host está atrás de um roteador doméstico (NAT) e às vezes atrás do NAT da operadora, que bloqueia conexões de fora. Pedir ao jogador para abrir porta no roteador é perder boa parte de quem tentou jogar.

O Relay resolve isso com um servidor intermediário. Host e clientes conectam para fora, no Relay, e ele repassa os pacotes entre eles. O host continua rodando o jogo; o Relay não executa lógica nenhuma.

O Lobby resolve o outro lado: como o cliente descobre a partida. Ele guarda as salas abertas, com nome, número de vagas e dados dos jogadores, e entrega o código de conexão do Relay para quem entra.

Na API de Sessions, as duas coisas viram uma só: sessão é a sala (Lobby) com a rede (Relay) já ligada. Se você ainda não conhece host, cliente e NetworkObject, comece pelo post de como fazer jogo multiplayer na Unity e volte aqui.

Pacotes: Netcode for GameObjects e Multiplayer Services

No Package Manager (Unity Registry), instale:

  • Netcode for GameObjects (com.unity.netcode.gameobjects). Para o Unity 6.3, use a linha 2.x (o manual do 6.3 lista a 2.13.3). Se o Package Manager oferecer uma linha mais nova, confira a compatibilidade com a sua versão do editor antes de trocar.
  • Multiplayer Services (com.unity.services.multiplayer), versão 2.4.1 segundo o manual do Unity 6.3. Ele reúne Relay, Lobby e matchmaking e traz como dependência os pacotes de Authentication e Core.

Não instale os SDKs antigos separados de Relay e Lobby junto com o Multiplayer Services: a funcionalidade já está nele, e misturar gera classes duplicadas.

Na cena, crie um objeto com NetworkManager e Unity Transport, e configure o Player Prefab como no tutorial básico. A sessão encontra o NetworkManager sozinha e inicia host ou cliente quando a rede sobe.

Conectar o projeto ao Unity Cloud

Os serviços precisam saber de qual projeto é cada chamada.

  1. Abra Edit > Project Settings > Services.
  2. Faça login com a conta Unity e escolha Link project (ou crie um projeto novo no Unity Cloud).
  3. No painel do Unity Cloud (cloud.unity.com), abra o projeto e confira que Relay e Lobby estão habilitados nos produtos de multiplayer.
  4. Volte ao editor e confirme que o Project ID aparece em Services.

Sem esse vínculo, a inicialização funciona, mas a criação de sessão falha com erro de projeto.

Criar sessão e gerar código de entrada

Tudo começa com inicializar os serviços e logar o jogador. O login anônimo não pede nada: gera um ID e guarda no aparelho.

Arquivo SessionLauncher.cs, num objeto do menu:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Services.Authentication;
using Unity.Services.Core;
using Unity.Services.Multiplayer;
using UnityEngine;

public class SessionLauncher : MonoBehaviour
{
    [SerializeField] private int maxPlayers = 4;
    [SerializeField] private string sessionName = "Sala do Juan";

    public ISession CurrentSession { get; private set; }

    private async Task EnsureSignedInAsync()
    {
        if (UnityServices.State != ServicesInitializationState.Initialized)
        {
            await UnityServices.InitializeAsync();
        }

        if (!AuthenticationService.Instance.IsSignedIn)
        {
            await AuthenticationService.Instance.SignInAnonymouslyAsync();
        }
    }

    public async Task<string> CreateSessionAsync()
    {
        await EnsureSignedInAsync();

        SessionOptions options = new SessionOptions
        {
            MaxPlayers = maxPlayers,
            Name = sessionName,
            IsPrivate = false
        }.WithRelayNetwork();

        try
        {
            CurrentSession = await MultiplayerService.Instance.CreateSessionAsync(options);
            Debug.Log("Sessão criada. Código: " + CurrentSession.Code);
            return CurrentSession.Code;
        }
        catch (SessionException e)
        {
            Debug.LogError("Falha ao criar sessão: " + e.Error + " " + e.Message);
            return null;
        }
    }

    public async Task<bool> JoinByCodeAsync(string code)
    {
        await EnsureSignedInAsync();

        try
        {
            CurrentSession = await MultiplayerService.Instance.JoinSessionByCodeAsync(code.Trim().ToUpperInvariant());
            return true;
        }
        catch (SessionException e)
        {
            Debug.LogError("Falha ao entrar: " + e.Error + " " + e.Message);
            return false;
        }
    }

    public async Task<IList<ISessionInfo>> ListSessionsAsync()
    {
        await EnsureSignedInAsync();

        QuerySessionsOptions query = new QuerySessionsOptions { Count = 20 };
        QuerySessionsResults results = await MultiplayerService.Instance.QuerySessionsAsync(query);
        return results.Sessions;
    }

    public async Task<bool> JoinByIdAsync(string sessionId)
    {
        await EnsureSignedInAsync();

        try
        {
            CurrentSession = await MultiplayerService.Instance.JoinSessionByIdAsync(sessionId);
            return true;
        }
        catch (SessionException e)
        {
            Debug.LogError("Falha ao entrar: " + e.Error + " " + e.Message);
            return false;
        }
    }

    public async Task LeaveAsync()
    {
        if (CurrentSession != null)
        {
            await CurrentSession.LeaveAsync();
            CurrentSession = null;
        }
    }
}

O ponto central é .WithRelayNetwork(). Segundo a documentação do Multiplayer Services, quando o host escolhe o tipo de rede na criação, a rede sobe automaticamente assim que a sessão existe, e quem entra depois é conectado no mesmo fluxo. Você não chama StartHost nem StartClient na mão: o pacote aloca o Relay, configura o Unity Transport e inicia o NetworkManager como host.

Multiplayer online na Unity: entrar na sessão e iniciar o host

No menu, ligue dois botões e um campo de texto. Arquivo SessionMenu.cs:

using TMPro;
using UnityEngine;

public class SessionMenu : MonoBehaviour
{
    [SerializeField] private SessionLauncher launcher;
    [SerializeField] private TMP_InputField codeInput;
    [SerializeField] private TMP_Text codeLabel;

    public async void OnCreateClicked()
    {
        string code = await launcher.CreateSessionAsync();
        codeLabel.text = string.IsNullOrEmpty(code) ? "Erro ao criar" : "Código: " + code;
    }

    public async void OnJoinClicked()
    {
        bool joined = await launcher.JoinByCodeAsync(codeInput.text);
        codeLabel.text = joined ? "Conectado" : "Código inválido ou sala cheia";
    }
}

O async void só é aceitável aqui porque são handlers de botão; o resto usa Task. Quem cria a sessão vira host e vê o código na tela. Quem digita o código entra como cliente, e o Player Prefab aparece para os dois.

Para testar sem fazer build, use o Multiplayer Play Mode: ele abre jogadores virtuais no mesmo editor. Como configurar, e como fazer o movimento sincronizar depois que todos entram, está no post sobre NetworkTransform e sincronizar movimento.

Conectar dois jogadores é a parte que tem tutorial. O que vem depois não tem: decidir quem é autoridade de cada coisa, sincronizar vida, inventário e pontuação sem trapaça, tratar quem cai no meio da partida, e encaixar isso num jogo com menu, fases e fim. Multiplayer multiplica o trabalho de cada sistema, e é onde quem faz sozinho mais costuma parar.

Curso de Unity
Quer fazer seu primeiro jogo na Unity?

No curso de Unity 6 da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Unity
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

Lobby: lista de salas e dados do jogador

Entrar por código serve para jogar com amigo. Para quem quer achar partida aberta, use a consulta de sessões. Pela documentação, a consulta devolve as sessões públicas e não cheias; sessão com IsPrivate = true só entra por código.

Cada resultado é um ISessionInfo com Id, Name, MaxPlayers, AvailableSlots e HasPassword. Um item de lista simples:

using TMPro;
using Unity.Services.Multiplayer;
using UnityEngine;

public class SessionListItem : MonoBehaviour
{
    [SerializeField] private TMP_Text label;
    private SessionLauncher launcher;
    private string sessionId;

    public void Setup(ISessionInfo info, SessionLauncher owner)
    {
        launcher = owner;
        sessionId = info.Id;
        int taken = info.MaxPlayers - info.AvailableSlots;
        label.text = info.Name + "  (" + taken + "/" + info.MaxPlayers + ")";
    }

    public async void OnJoinClicked()
    {
        await launcher.JoinByIdAsync(sessionId);
    }
}

Atenção: o ID da sessão não é o código de entrada. ID serve para a lista; código serve para digitar.

Para mostrar nomes dos jogadores na sala, a documentação mostra o WithPlayerName(VisibilityPropertyOptions.Member) nas opções da sessão, junto com AuthenticationService.Instance.UpdatePlayerNameAsync(nome) depois do login. Dados de jogo (personagem escolhido, pronto ou não) vão como propriedades do jogador ou da sessão. Mantenha nesses campos só o que a sala precisa mostrar antes da partida; o estado do jogo em si fica no Netcode.

Limites do plano gratuito e custos

Segundo a página de preços dos Unity Gaming Services, consultada em outubro de 2026:

  • Relay: os primeiros 50 CCU médios por mês são gratuitos, e cada CCU médio adicional custa US$ 0,16. Banda: 3 GiB por CCU, até 150 GiB por mês, somando as regiões. Acima disso, US$ 0,09 por GiB em EUA e Europa e US$ 0,16 por GiB em Ásia e Austrália.
  • Lobby: 10 GiB por mês por grupo de regiões (EUA e Europa; Ásia e Austrália), com o mesmo preço por GiB acima disso.

CCU médio é a média de usuários conectados simultaneamente no mês, não o pico. Um jogo indie em teste ou com comunidade pequena cabe com folga. A conta muda quando o jogo cresce ou quando você manda dados demais por segundo: sincronizar posição 60 vezes por segundo de 20 objetos consome banda rápido. Cadastre um método de pagamento só quando for necessário e configure alertas de uso no painel.

Dinheiro não é o único limite. Com Relay, o host é um jogador comum: se ele fecha o jogo, cai a internet ou o celular bloqueia a tela, a partida acaba para todos. Para co-op entre amigos e partidas curtas, isso é aceitável. Para jogo competitivo, com ranking e partidas longas, o host vira ponto fraco e também pode trapacear, porque roda a simulação na máquina dele. Nesses casos o caminho é servidor dedicado, que custa por hora de máquina. Comece pelo Relay, valide se as pessoas jogam, e só migre quando o jogo justificar.

Erros comuns: autenticação, região, timeout

  • "UnityServices not initialized" ou erro de autenticação: você chamou a sessão antes de InitializeAsync e SignInAnonymouslyAsync. O EnsureSignedInAsync do script resolve.
  • Erro de projeto ou de permissão: o projeto não está vinculado em Project Settings > Services, ou Relay e Lobby não estão habilitados no Unity Cloud.
  • Dois jogadores com o mesmo ID no Multiplayer Play Mode: o login anônimo guarda o ID no aparelho, e instâncias da mesma máquina podem dividir esse cache. Use perfis diferentes com AuthenticationService.Instance.SwitchProfile antes do login.
  • Código inválido: códigos expiram quando o host sai. Peça um novo e confira maiúsculas.
  • Latência alta: o Relay escolhe a região pelo host. Se o host está longe dos outros, todos sofrem. A documentação mostra como passar RelayNetworkOptions para escolher protocolo e região na criação.
  • Cliente entra e não aparece: Player Prefab não registrado no NetworkManager ou faltando NetworkObject.
  • Timeout ao conectar: firewall corporativo ou rede que bloqueia UDP. O Relay aceita outros protocolos via opções da sessão; em build Web, o protocolo precisa ser WebSocket.

Próximo passo

Faça o teste mínimo hoje: dois jogadores no Multiplayer Play Mode, um cria, o outro entra por código, e os dois veem o Player Prefab. Depois mande uma build para um amigo em outra cidade e repita pela internet de verdade. Com a conexão resolvida, o próximo problema é fazer o movimento parecer suave na tela do outro, e a teoria de latência e interpolação está no post sobre netcode e otimização de latência.

Perguntas frequentes

Preciso dos pacotes separados de Relay e Lobby?

Não. O pacote Multiplayer Services (com.unity.services.multiplayer), na versão 2.4.1 para o Unity 6.3, reúne Relay, Lobby e matchmaking na API de Sessions. Você cria uma sessão com WithRelayNetwork e ela já cuida da alocação no Relay e da sala.

Relay e Lobby da Unity são gratuitos?

Há franquia gratuita. A página de preços dos Unity Gaming Services, consultada em outubro de 2026, lista no Relay os primeiros 50 CCU médios por mês e 3 GiB por CCU até 150 GiB por mês, e no Lobby 10 GiB por mês por grupo de regiões. Acima disso, cobra por uso.

Qual a diferença entre Relay e servidor dedicado?

No Relay, um dos jogadores é o host e roda a simulação; o Relay só repassa pacotes entre ele e os outros, sem rodar lógica do jogo. No servidor dedicado, uma máquina na nuvem roda o jogo sem jogador. Relay é mais barato e simples, mas o jogo depende da conexão do host.

Por que preciso de login para usar Relay e Lobby?

Todo serviço da Unity identifica o jogador por uma conta de Authentication. O login anônimo resolve sem pedir nada ao jogador: ele gera um ID salvo no aparelho. Sem chamar SignInAnonymouslyAsync antes, as chamadas de sessão falham.