Fazer photon unity multiplayer com o PUN 2 é um dos jeitos mais rápidos de botar dois jogadores na mesma partida pela internet sem montar servidor. O PUN (Photon Unity Networking) usa o Photon Cloud: você cria uma conta, gera um App ID, cola no projeto e as salas passam a rodar na infraestrutura da Photon. Ninguém abre porta no roteador, ninguém precisa de IP fixo, e você não paga máquina enquanto estiver dentro do plano gratuito.
Este tutorial monta o básico no Unity: instalar o SDK, ligar o App ID, conectar ao Master Server, entrar numa sala aleatória ou criar uma, instanciar o jogador em rede e sincronizar posição com PhotonView e IPunObservable. No fim, comparo rápido com a solução nativa da Unity (Netcode com Relay) para você saber quando cada uma faz sentido. Todo o código é C# real.
Photon Unity multiplayer: como o PUN 2 funciona
Antes do código, o mapa mental. O PUN tem uma hierarquia simples de três níveis.
O Master Server é a porta de entrada. Quando você conecta, cai primeiro nele. O Master não hospeda jogo: ele organiza o matchmaking, cuida da lista de salas e manda você para o servidor certo.
A sala (room) é onde a partida acontece. Todo mundo que está na mesma sala troca mensagens entre si. Uma sala tem nome, número máximo de jogadores e propriedades. Quem não está na sala não vê nada do que acontece lá.
O jogador (player) é cada cliente dentro da sala. Um deles é o MasterClient: o jogador com autoridade para decisões que precisam de um dono único, como gerar inimigos ou escolher a fase. Se o MasterClient sai, o Photon elege outro automaticamente.
Essa estrutura lembra a lógica de Relay e Lobby da Unity. Se você ainda não viu os conceitos gerais de host, cliente e objeto de rede, vale ler antes o post de como fazer jogo multiplayer na Unity e voltar aqui.
Instalar o PUN 2 e criar o App ID
O PUN 2 vem pela Asset Store. Procure por PUN 2 - FREE, adicione à sua conta e importe pelo Package Manager (aba My Assets). Na importação, deixe tudo marcado.
Enquanto o pacote importa, crie o App ID. Esse é o passo que mais trava iniciante.
- Crie conta no dashboard da Photon (o site de contas da Photon Engine).
- No dashboard, clique em Create a New App.
- Em tipo, escolha Photon PUN (não o Fusion nem o Quantum, que são outros produtos).
- Dê um nome e confirme. O dashboard mostra o App ID, uma sequência longa de letras e números.
- Copie esse App ID.
Quando a importação termina, o PUN abre sozinho a janela PUN Wizard (ou use Window > Photon Unity Networking > PUN Wizard). Cole o App ID no campo e clique em Setup Project. Isso grava o App ID no asset PhotonServerSettings, que fica no projeto. Sem esse passo, a conexão falha com erro de autenticação, mesmo com o código certo.
Conectar ao Master Server
Com o App ID no lugar, o primeiro script conecta. Toda classe que escuta eventos do PUN herda de MonoBehaviourPunCallbacks, que já traz os callbacks prontos para você sobrescrever.
Arquivo ConexaoPhoton.cs, num objeto vazio da cena de menu:
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
public class ConexaoPhoton : MonoBehaviourPunCallbacks
{
private void Start()
{
// Garante que todos entrem na mesma versão de jogo.
PhotonNetwork.GameVersion = "1.0";
// Carrega o App ID do PhotonServerSettings e conecta.
PhotonNetwork.ConnectUsingSettings();
Debug.Log("Conectando ao Photon...");
}
public override void OnConnectedToMaster()
{
Debug.Log("Conectado ao Master Server.");
// Entra em qualquer sala aberta; se não houver, o callback de falha cria uma.
PhotonNetwork.JoinRandomRoom();
}
public override void OnDisconnected(DisconnectCause cause)
{
Debug.LogWarning("Desconectado: " + cause);
}
}
PhotonNetwork.ConnectUsingSettings() lê o App ID do PhotonServerSettings e abre a conexão. Quando ela completa, o PUN chama OnConnectedToMaster(). O GameVersion serve para isolar builds: jogadores com versões diferentes não se encontram, o que evita bug de protocolo quando você atualiza o jogo.
Entrar numa sala ou criar uma
A estratégia mais comum para jogo casual é join random: tenta entrar em qualquer sala aberta e, se não achar nenhuma, cria uma. O PUN te avisa que não achou sala pelo callback OnJoinRandomFailed.
Acrescente ao mesmo script:
public override void OnJoinRandomFailed(short returnCode, string message)
{
Debug.Log("Nenhuma sala aberta. Criando uma nova.");
RoomOptions opcoes = new RoomOptions
{
MaxPlayers = 4,
IsVisible = true,
IsOpen = true
};
// Nome null faz o Photon gerar um nome único para a sala.
PhotonNetwork.CreateRoom(null, opcoes);
}
public override void OnJoinedRoom()
{
Room sala = PhotonNetwork.CurrentRoom;
Debug.Log("Entrou na sala: " + sala.Name +
" (" + sala.PlayerCount + "/" + sala.MaxPlayers + ")");
}
public override void OnPlayerEnteredRoom(Player novoJogador)
{
Debug.Log(novoJogador.NickName + " entrou na sala.");
}
OnJoinedRoom() roda tanto para quem criou quanto para quem entrou depois. É o gancho certo para instanciar o jogador e trocar da cena de menu para a de jogo. O MaxPlayers = 4 segura a sala em quatro pessoas; a quinta cai de novo no OnJoinRandomFailed e abre outra sala.
Se você quiser salas nomeadas (um amigo digita o nome e entra), use PhotonNetwork.JoinOrCreateRoom(nome, opcoes, null) no lugar do random. A lógica do OnJoinedRoom continua igual.
Instanciar o jogador em rede
Aqui está a diferença que confunde quem vem do single player. Para o objeto existir em todos os clientes da sala, não se usa o Instantiate normal da Unity, e sim PhotonNetwork.Instantiate.
Duas exigências:
- O prefab do jogador precisa estar numa pasta chamada Resources (o Photon o acha pelo nome em runtime).
- O prefab precisa ter um componente PhotonView na raiz.
Script SpawnJogador.cs, chamado quando a sala está pronta:
using Photon.Pun;
using UnityEngine;
public class SpawnJogador : MonoBehaviourPunCallbacks
{
[SerializeField] private string nomePrefabJogador = "PlayerNetwork";
[SerializeField] private float raioSpawn = 3f;
public override void OnJoinedRoom()
{
Vector3 pos = new Vector3(Random.Range(-raioSpawn, raioSpawn), 1f,
Random.Range(-raioSpawn, raioSpawn));
PhotonNetwork.Instantiate(nomePrefabJogador, pos, Quaternion.identity);
}
}
Quando esse código roda em cada cliente, cada um cria o seu personagem, e o Photon replica os quatro objetos para todo mundo na sala. O objeto que você criou pertence a você: é o que o PUN chama de IsMine. É por isso que, no script de movimento, você só move o personagem se o PhotonView for seu. Mexer num objeto que não é seu não propaga e vira bug.
using Photon.Pun;
using UnityEngine;
public class MovimentoJogador : MonoBehaviourPun
{
[SerializeField] private float velocidade = 5f;
private void Update()
{
// Só controla o personagem que pertence a este cliente.
if (!photonView.IsMine) return;
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 dir = new Vector3(x, 0f, z) * velocidade * Time.deltaTime;
transform.Translate(dir, Space.World);
}
}
Conectar dois personagens na tela é a parte que todo tutorial mostra. O que vem depois quase nunca aparece: decidir quem tem autoridade sobre cada coisa, sincronizar vida e pontuação sem abrir brecha para trapaça, tratar o jogador que cai no meio da partida e encaixar isso num jogo com menu, fases e placar. Multiplayer multiplica o trabalho de cada sistema, e é onde quem aprende sozinho mais costuma empacar.
Sincronizar com PhotonView e IPunObservable
O MovimentoJogador move o personagem na máquina do dono, mas os outros clientes ainda não veem esse movimento. Para isso serve o PhotonView com um Observed Component que implementa IPunObservable.
O método OnPhotonSerializeView roda nos dois lados: no dono, ele escreve o estado no fluxo (stream.IsWriting); nos outros, ele lê o estado que chegou. Entre um pacote e outro, você interpola para o movimento não ficar aos saltos.
using Photon.Pun;
using UnityEngine;
public class SyncTransform : MonoBehaviourPun, IPunObservable
{
private Vector3 posRede;
private Quaternion rotRede;
[SerializeField] private float suavizacao = 10f;
private void Start()
{
posRede = transform.position;
rotRede = transform.rotation;
}
private void Update()
{
// Só os clientes remotos interpolam; o dono já se move sozinho.
if (photonView.IsMine) return;
transform.position = Vector3.Lerp(transform.position, posRede,
Time.deltaTime * suavizacao);
transform.rotation = Quaternion.Lerp(transform.rotation, rotRede,
Time.deltaTime * suavizacao);
}
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting)
{
// Dono envia o estado atual.
stream.SendNext(transform.position);
stream.SendNext(transform.rotation);
}
else
{
// Remoto recebe e guarda para interpolar no Update.
posRede = (Vector3)stream.ReceiveNext();
rotRede = (Quaternion)stream.ReceiveNext();
}
}
}
Para ligar tudo: no prefab, adicione o PhotonView e arraste o componente SyncTransform para o campo Observed Components do PhotonView. Deixe o Synchronization em Unreliable On Change, que só manda pacote quando o valor muda e economiza banda. O PUN já traz um PhotonTransformView pronto se você não quiser escrever o seu, mas entender o IPunObservable é o que te deixa sincronizar vida, animação e qualquer outro estado depois.
Para disparar eventos pontuais (um tiro, um dano, abrir uma porta), o caminho não é o stream contínuo, e sim o [PunRPC]: um método marcado que você chama em todos os clientes com photonView.RPC("NomeDoMetodo", RpcTarget.All). Use stream para o que muda todo frame e RPC para o que acontece de vez em quando.
Limites do plano gratuito
O Photon Cloud é gratuito até um teto de usuários conectados ao mesmo tempo (CCU). Segundo a página de preços da Photon, consultada em outubro de 2026, o plano de entrada trabalha nessa base de CCU, e acima do teto você migra para um plano pago. Como o número e as regras mudam com frequência, confirme o valor atual direto no dashboard antes de contar com ele num lançamento.
CCU é usuário simultâneo, não total de jogadores. Um jogo com mil pessoas espalhadas pelo dia, mas nunca mais de vinte ao mesmo tempo, usa vinte CCU. Para teste, protótipo e jogo com comunidade pequena, o plano gratuito costuma sobrar. A conta aperta quando o jogo cresce ou quando você manda dados demais por segundo: sincronizar muitos objetos a cada frame queima CCU e banda rápido. Mande só o necessário e prefira Unreliable On Change.
Photon ou a solução nativa da Unity
A Unity tem a própria pilha: o Netcode for GameObjects com Relay e Lobby. A escolha honesta depende do projeto.
- Photon PUN entrega mais rápido o primeiro multiplayer funcionando, tem anos de tutorial e a sala roda no Photon Cloud sem você pensar em infraestrutura. O custo é depender de um serviço externo e da precificação por CCU.
- Netcode com Relay é da própria Unity, integra com o resto dos serviços Unity e dá mais controle sobre autoridade e topologia, mas exige mais montagem no começo. O passo a passo está no post de multiplayer online na Unity com Relay e Lobby.
- Servidor dedicado é o caminho quando o jogo é competitivo e você não pode deixar um jogador rodando a simulação (risco de trapaça e de a partida cair quando o host sai).
Nas duas primeiras opções, um jogador hospeda a partida, então o host ainda é um ponto fraco para jogo competitivo. Se o seu objetivo é co-op entre amigos e partidas curtas, PUN resolve e te tira do zero mais rápido. Para comparar os caminhos lado a lado antes de decidir, veja o post sobre backend para jogo multiplayer: opções.
Próximo passo
Faça o teste mínimo hoje: dois clientes (um no editor, um numa build), os dois conectam, caem na mesma sala e cada um vê o personagem do outro se mexer. Quando isso funcionar, o próximo problema é fazer o movimento parecer suave na tela do outro, e a teoria de interpolação e latência está no post sobre netcode e otimização de latência. Conexão resolvida é só o começo; o jogo de verdade mora no que vem depois dela.
Perguntas frequentes
PUN 2 ainda é a opção recomendada pela Photon?
A Photon mantém o PUN 2 funcionando e disponível na Asset Store, mas hoje posiciona o Fusion e o Quantum como os produtos novos para projetos que estão começando. Para aprender os conceitos de sala, host e sincronização, o PUN 2 ainda é o caminho mais simples e com mais tutorial. Para um jogo comercial novo, avalie Fusion antes de fechar a escolha.
Preciso de servidor próprio para usar o Photon?
Não. O Photon Cloud hospeda os servidores. Você só cria um App ID no dashboard, cola no projeto e as salas rodam na infraestrutura deles. Não precisa abrir porta no roteador nem alugar máquina enquanto estiver dentro do plano gratuito.
Qual o limite do plano gratuito do Photon?
O plano gratuito do Photon, consultado em outubro de 2026, trabalha com um teto de usuários conectados ao mesmo tempo (CCU). Confirme o número atual na página de preços da Photon antes de contar com ele, porque o valor e as regras mudam. Acima do teto, você passa para um plano pago por CCU ou por mês.
PhotonNetwork.Instantiate é igual ao Instantiate da Unity?
Não. O Instantiate normal cria o objeto só na máquina local. O PhotonNetwork.Instantiate cria o objeto em todos os clientes da sala e dá a ele um PhotonView com dono, que é quem controla aquele objeto. O prefab precisa estar numa pasta Resources para o Photon encontrar pelo nome.



