Voltar para o Blog
Quest Log

Sincronizar Movimento no Netcode: NetworkTransform

Dois personagens idênticos em telas lado a lado ligados por setas de movimento e pontos de rastro que mostram a posição sincronizada

Unity Network Transform no Netcode 2.x: autoridade do servidor ou do dono, por que o cliente não se move, interpolação, eixos e Multiplayer Play Mode.

Ferramenta:UnityC#

Sincronizar Movimento no Netcode: NetworkTransform

O unity network transform é o componente do Netcode for GameObjects que copia posição, rotação e escala de um objeto da máquina que tem autoridade para todas as outras. O detalhe que trava quase todo mundo: por padrão, a autoridade é do servidor, então o cliente que move o próprio player vê o personagem voltar para trás. A correção é escolher Authority Mode: Owner no componente ou mandar o input para o servidor mover.

Este post explica as duas autoridades com código C# para o Netcode for GameObjects 2.x no Unity 6.3 LTS, mostra como ajustar interpolação e limites para economizar banda, quando usar NetworkRigidbody e como testar até quatro jogadores no mesmo editor com o Multiplayer Play Mode, sem gerar build.

NetworkObject, NetworkBehaviour e autoridade

Três peças, rapidamente:

  • NetworkObject: marca o GameObject como existente na rede. Tem um ID e um dono (o servidor ou um cliente).
  • NetworkBehaviour: script que sabe se está rodando no servidor (IsServer), no dono (IsOwner) ou em outra máquina.
  • Autoridade: quem tem a palavra final sobre um dado. No NetworkTransform, é quem envia a posição. Os outros só recebem e interpolam.

O Player Prefab spawnado pelo NetworkManager tem como dono o cliente que conectou. Mas ser dono não dá autoridade sobre o transform automaticamente: isso depende do Authority Mode. Se a diferença entre host, servidor e cliente ainda está nebulosa, o post como fazer jogo multiplayer na Unity cobre a base.

Unity Network Transform: o que sincroniza

Adicione NetworkTransform ao prefab que já tem NetworkObject. No Inspector, as seções principais (nomes do componente no Netcode 2.13, a versão para o Unity 6.3):

  • Axis to Synchronize: caixas por eixo para Position, Rotation e Scale (SyncPositionX, SyncRotAngleY, SyncScaleZ e assim por diante, no código).
  • Authority Mode: Server (padrão) ou Owner.
  • Thresholds: o quanto o valor precisa mudar para gerar atualização. Padrões no código: PositionThreshold = 0.001, RotAngleThreshold = 0.01 e ScaleThreshold = 0.01.
  • Interpolate: ligado por padrão, suaviza a posição em quem recebe.
  • In Local Space, Use Half Float Precision, Use Quaternion Synchronization e Use Quaternion Compression: opções de precisão e compactação.

O NetworkTransform só envia quando algo passou do limite. Objeto parado não gasta banda.

Server authority vs owner authority

Server (padrão): só o servidor envia posição. Para o jogador se mover, o cliente manda o input e o servidor aplica. Vantagem: o cliente não consegue trapacear teleportando o próprio personagem. Desvantagem: o jogador sente a latência entre apertar a tecla e ver o movimento.

Owner: o dono envia a posição. O cliente se move na hora, sem esperar o servidor, e os outros recebem. Vantagem: controle responsivo, código simples. Desvantagem: o cliente decide onde está, então um cliente modificado pode se teleportar.

Regra prática: jogo cooperativo entre amigos, use Owner para o player. Jogo competitivo, use Server e aceite o trabalho extra (ou implemente predição, assunto do post sobre netcode e otimização de latência). Objetos que não pertencem a nenhum jogador (inimigos, plataformas móveis) ficam sempre com o servidor.

O Authority Mode substitui o ClientNetworkTransform dos tutoriais da linha 1.x, que era uma classe de exemplo que sobrescrevia OnIsServerAuthoritative() para devolver false. No 2.x você só troca o campo no Inspector.

Mover o player do cliente: o erro mais comum

O sintoma: no host, o player anda normal. No cliente, o player treme e volta à posição de partida. Causa: Authority Mode em Server com um script de movimento que roda no cliente.

Opção 1: autoridade do dono

No prefab, NetworkTransform > Authority Mode: Owner. E o script de movimento só lê input no dono. Arquivo OwnerPlayerMover.cs:

using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;

public class OwnerPlayerMover : NetworkBehaviour
{
    [SerializeField] private float speed = 5f;

    private void Update()
    {
        if (!IsOwner)
        {
            return;
        }

        Vector2 input = ReadMoveInput();
        Vector3 delta = new Vector3(input.x, 0f, input.y) * (speed * Time.deltaTime);
        transform.position += delta;
    }

    private Vector2 ReadMoveInput()
    {
        Keyboard keyboard = Keyboard.current;
        if (keyboard == null)
        {
            return Vector2.zero;
        }

        float x = (keyboard.dKey.isPressed ? 1f : 0f) - (keyboard.aKey.isPressed ? 1f : 0f);
        float y = (keyboard.wKey.isPressed ? 1f : 0f) - (keyboard.sKey.isPressed ? 1f : 0f);
        return Vector2.ClampMagnitude(new Vector2(x, y), 1f);
    }
}

O if (!IsOwner) return; é o que importa. Sem ele, cada máquina move todos os players com o mesmo teclado.

Opção 2: autoridade do servidor

Authority Mode fica em Server. O dono envia o input por RPC e o servidor move. Arquivo ServerPlayerMover.cs:

using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;

public class ServerPlayerMover : NetworkBehaviour
{
    [SerializeField] private float speed = 5f;
    private Vector2 lastInput;
    private Vector2 serverInput;

    private void Update()
    {
        if (IsOwner)
        {
            Vector2 input = ReadMoveInput();
            if (input != lastInput)
            {
                lastInput = input;
                SubmitInputRpc(input);
            }
        }

        if (IsServer)
        {
            Vector3 delta = new Vector3(serverInput.x, 0f, serverInput.y) * (speed * Time.deltaTime);
            transform.position += delta;
        }
    }

    [Rpc(SendTo.Server)]
    private void SubmitInputRpc(Vector2 input)
    {
        serverInput = Vector2.ClampMagnitude(input, 1f);
    }

    private Vector2 ReadMoveInput()
    {
        Keyboard keyboard = Keyboard.current;
        if (keyboard == null)
        {
            return Vector2.zero;
        }

        float x = (keyboard.dKey.isPressed ? 1f : 0f) - (keyboard.aKey.isPressed ? 1f : 0f);
        float y = (keyboard.wKey.isPressed ? 1f : 0f) - (keyboard.sKey.isPressed ? 1f : 0f);
        return new Vector2(x, y);
    }
}

Dois cuidados. O RPC só é enviado quando o input muda, não todo frame. E o servidor faz ClampMagnitude no que recebe: ele não confia no cliente, nem para o tamanho do vetor. O nome do método termina em Rpc, exigência do atributo [Rpc] no Netcode 2.x.

Fazer dois cubos andarem sincronizados é o começo. O jogo de verdade pede sincronizar animação, tiro, dano, morte e respawn, cada um com a autoridade certa, sem duplicar efeito nem deixar brecha. Essa arquitetura de jogo multiplayer completo é onde a maioria trava estudando por conta própria.

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

Interpolação e thresholds

Atualizações chegam em intervalos, não a cada frame. Sem interpolação, o player remoto anda aos saltos. Com Interpolate ligado, quem recebe desenha o objeto um pouco no passado, deslizando entre os dois últimos estados.

O Netcode 2.x oferece três tipos por propriedade (PositionInterpolationType, RotationInterpolationType, ScaleInterpolationType):

  • Legacy Lerp (padrão): o modelo original de interpolação do Netcode.
  • Lerp: interpolação linear mais direta.
  • Smooth Dampening: suaviza mudanças bruscas de direção, bom para personagens que viram rápido.

Teste os três no seu jogo: a diferença aparece com latência simulada, não em rede local.

Os thresholds controlam quando enviar. Para um jogo de plataforma em pixels, PositionThreshold de 0,001 envia mudanças invisíveis; subir para 0,01 corta atualizações sem diferença visual. Rotação que só gira em Y com RotAngleThreshold de 1 grau é suficiente para a maioria dos jogos de cima.

Sincronizar só o necessário

Cada eixo marcado é dado enviado. Ajuste o prefab ao jogo:

  • Jogo 2D: Position X e Y, Rotation Z. Desmarque Position Z, Rotation X e Y e toda a escala.
  • Personagem 3D que só gira no eixo vertical: Position X, Y, Z e Rotation Y.
  • Escala: quase nunca muda. Desmarque os três eixos, a menos que você anime tamanho.
  • Use Half Float Precision: reduz cada eixo de 4 para 2 bytes. Ótimo para mapas pequenos; em mapas enormes, perde precisão longe da origem.
  • Use Quaternion Compression (exige Use Quaternion Synchronization): a documentação informa que reduz a rotação de 16 para 4 bytes por atualização.

Segundo a documentação, alterar Interpolate, In Local Space, Slerp Position ou as opções de compressão em tempo de execução gera uma atualização completa que age como teleporte. Defina no prefab e não mexa durante o jogo.

NetworkRigidbody e física em rede

Se o objeto tem Rigidbody (ou Rigidbody2D), adicione também NetworkRigidbody (ou NetworkRigidbody2D). Com Auto Update Kinematic State ligado (padrão), ele deixa o corpo cinemático em quem não tem autoridade. Assim a física local não empurra o objeto para um lado enquanto a rede manda para outro.

A opção Use Rigid Body For Motion faz o NetworkTransform aplicar posição e rotação pelo Rigidbody e permite usar a interpolação do próprio Rigidbody. No dono, mova pelo Rigidbody (rb.linearVelocity no Unity 6, não velocity) ou com MovePosition dentro de FixedUpdate, nunca pelo transform direto.

Testar com Multiplayer Play Mode

O Multiplayer Play Mode (pacote com.unity.multiplayer.playmode, versão 2.0.2, compatível com o Unity 6.3) abre instâncias adicionais do editor que compartilham o projeto. A documentação informa até quatro jogadores de editor no total: o principal mais três adicionais.

  1. Instale pelo Package Manager (Unity Registry > Multiplayer Play Mode).
  2. Abra Window > Play Mode > Scenarios e crie um cenário.
  3. Deixe o Editor marcado e clique em + em Additional Editor Instances para adicionar jogadores.
  4. Crie tags em Edit > Project Settings > Multiplayer > PlayMode > Player Tags (ex.: Host e Client) e atribua uma a cada instância no cenário.
  5. Dê Play. Use Ctrl+F9 a Ctrl+F12 para focar cada jogador.

Para não clicar em Host e Client toda vez, inicie pela tag. Arquivo PlayModeAutoStart.cs:

using System.Linq;
using Unity.Netcode;
using UnityEngine;
#if UNITY_EDITOR
using Unity.Multiplayer.PlayMode;
#endif

public class PlayModeAutoStart : MonoBehaviour
{
    private void Start()
    {
#if UNITY_EDITOR
        // Tags devolve a lista de tags do jogador atual (pode ter mais de uma).
        if (CurrentPlayer.Tags.Contains("Host"))
        {
            NetworkManager.Singleton.StartHost();
        }
        else if (CurrentPlayer.Tags.Contains("Client"))
        {
            NetworkManager.Singleton.StartClient();
        }
#endif
    }
}

O bloco fica dentro de #if UNITY_EDITOR para não afetar a build. Ainda vale gerar build quando: você testa internet de verdade (Relay, NAT), desempenho, input de controle físico em várias máquinas ou plataforma diferente. Para conectar por internet sem servidor próprio, veja o post sobre Relay e Lobby na Unity.

Erros comuns: jitter, player teleportando

  • Player do cliente volta para trás: Authority Mode Server com movimento no cliente. Troque para Owner ou use RPC.
  • Todos os players se movem com o mesmo teclado: falta if (!IsOwner) return;.
  • Jitter no player remoto: Interpolate desligado, ou script movendo o objeto também nas máquinas que só recebem. Só a autoridade move.
  • Teleporte a cada poucos segundos: alguma opção do NetworkTransform mudando em runtime, ou física local brigando (falta NetworkRigidbody).
  • Objeto filho fora de sincronia: filho com NetworkTransform próprio e pai se movendo. Marque In Local Space no filho.
  • Movimento travado em rede lenta: thresholds altos demais ou intervalo de envio baixo. Teste com o Network Simulator antes de culpar o código.

Próximo passo

Monte um prefab com NetworkTransform em Owner, rode três jogadores no Multiplayer Play Mode e ande com cada um. Depois troque para Server com o script de RPC e compare a sensação de controle. Fazer os dois na mesma tarde deixa clara a troca entre responsividade e segurança, e você escolhe com conhecimento para o seu jogo.

Perguntas frequentes

O ClientNetworkTransform ainda é necessário no Netcode 2.x?

Não. No Netcode for GameObjects 2.x o próprio NetworkTransform tem a propriedade Authority Mode, com as opções Server e Owner. Escolher Owner faz o dono do objeto enviar a posição, que é o que o antigo ClientNetworkTransform fazia sobrescrevendo um método.

Por que o player do cliente volta para trás quando ele anda?

Porque o NetworkTransform está com Authority Mode em Server, que é o padrão. O cliente move o objeto localmente, mas só o servidor tem permissão de enviar a posição, e a próxima atualização dele puxa o objeto de volta. Use Authority Mode Owner ou mande o input para o servidor mover.

Quantos jogadores o Multiplayer Play Mode simula?

Até quatro jogadores de editor no total: o editor principal mais três instâncias adicionais, segundo a documentação do pacote 2.0. Ele também permite incluir instâncias de build local. Serve para teste pequeno na sua máquina, não para teste de carga.

NetworkTransform ou NetworkRigidbody para objeto com física?

Os dois juntos. O NetworkTransform sincroniza a posição e o NetworkRigidbody, adicionado no mesmo objeto, deixa o Rigidbody cinemático em quem não tem autoridade, para a física local não brigar com a posição que chega pela rede.