Práticas recomendadas de Mesh Visual Scripting para rede
Descrição geral
No Mesh, a maioria das propriedades de cena são, por padrão, compartilhadas automaticamente entre todos os clientes conectados à mesma sala. Por exemplo, a posição e a rotação Transform de um objeto de cena, o estado habilitado de um componente ou o texto de um TextMeshPro.
Como regra geral, as propriedades do componente e as variáveis Object que têm os seguintes tipos de valor são compartilhadas automaticamente por padrão:
- Booleano, inteiro, flutuante e string
- Cor
- Rect
- Vetor 2, Vetor 3 e Vetor 4
- Quaternião
- Matriz 4x4
Os tipos de coleção (listas e conjuntos) e as referências de objetos de cena não são compartilhados.
Os nós de script visual que acessam ou modificam propriedades no Mesh são marcados com um rótulo que indica se eles são "Compartilhado por todos os clientes" ou "Local para este cliente":
As variáveis de objeto também são compartilhadas por padrão se você as tiver declarado com um dos tipos de valor listados acima:
O Mesh não oferece suporte a variáveis de cena, mas você pode usar componentes de variáveis autônomas no ambiente para armazenar variáveis que podem ser compartilhadas independentemente de qualquer componente específico da máquina de scripts.
Se você não quiser o compartilhamento automático de propriedades ou variáveis de objeto, poderá adicionar um componente Escopo de script local à sua cena. Isso tornará locais todas as propriedades de cena e variáveis de script neste objeto de jogo e em qualquer um de seus descendentes.
Dica: Você pode ver vários exemplos de como o componente Escopo de Script Local é usado no Capítulo 3 do nosso tutorial do Mesh 101, que se concentra em scripts visuais.
Para variáveis de script locais que você está usando apenas em uma única máquina de script, é melhor usar variáveis de gráfico, que nunca são compartilhadas entre clientes pelo Mesh.
O compartilhamento por meio do Mesh Visual Scripting oferece as seguintes garantias:
Consistência final garantida: Todos os clientes acabarão por chegar ao mesmo estado partilhado.
Atomicidade garantida por componente: Todas as atualizações das propriedades do mesmo componente de cena (ou do mesmo componente Variáveis ) na mesma atualização serão aplicadas atomicamente em cada cliente.
No entanto:
Sem garantia de encomenda: As atualizações aplicadas por um cliente a vários componentes de cena diferentes podem chegar em ordens diferentes em clientes diferentes.
Sem garantia de pontualidade: o Mesh tentará ao máximo replicar as alterações de estado entre os clientes o mais rápido possível, mas as condições de rede podem atrasar a chegada de qualquer atualização de estado em alguns ou em todos os clientes.
Sem garantia de granularidade: qualquer cliente pode não ver todas as atualizações incrementais individuais para o estado compartilhado. Isso pode acontecer quando as condições de rede forçam o servidor Mesh a limitar as atualizações de taxa. Também acontece quando um cliente entra tarde numa sala.
O estado é compartilhado - não eventos
Não é possível enviar ou receber mensagens de rede explícitas com o Mesh Visual Scripting. Isso pode ser surpreendente no início, mas ajuda a estabelecer um paradigma de rede que facilita o tratamento uniforme de alterações de tempo de execução, bem como a adesão tardia. Em vez de mensagens, há estado compartilhado em propriedades de cena e variáveis de script.
Seus scripts podem responder a atualizações de estado compartilhadas de maneira uniforme, independentemente de essas atualizações terem sido feitas por um script ou usuário local, por outro cliente que compartilha a experiência na mesma sala ou por outros clientes que já estavam na sala antes mesmo de você mesmo ingressar nela.
Não ser capaz de enviar explicitamente mensagens de rede significa que você terá que começar a pensar no estado compartilhado que recebe atualizações em vez de eventos compartilhados que causam atualizações de estado. Os eventos compartilhados são uma consequência da atualização do estado compartilhado, e não o contrário.
Felizmente, o Mesh Visual Scripting facilita a reação dos scripts visuais às atualizações de estado. Use o nó de evento On State Changed e conecte suas entradas do lado esquerdo com qualquer variável de script ou propriedade de componente que você gostaria de observar para alterações, e o nó de evento acionará seu script (conectado ao seu lado direito) sempre que qualquer uma das variáveis ou propriedades conectadas a ele alterar seu valor.
Isso funciona com o estado compartilhado, bem como com o estado local. O evento On State Changed será acionado independentemente de as variáveis ou propriedades que está observando terem sido alteradas pelo cliente local, por um cliente remoto ou até mesmo por um cliente remoto antes mesmo de o cliente local entrar na sala.
Usar On State Changed para responder a alterações de estado é eficiente: não há largura de banda ociosa ou custo de desempenho. Você pode fazer com que qualquer número de scripts visuais escute passivamente as atualizações de estado dessa maneira sem afetar negativamente a taxa de quadros ou o uso da largura de banda do seu ambiente.
Adesão tardia
A adesão tardia acontece quando um cliente entra em uma sala que já tem outros clientes conectados a ela.
Na entrada tardia, o Mesh recebe o estado atual da sala do servidor - por exemplo, quem já está na sala e onde seus avatares estão atualmente localizados - e prepara rapidamente a versão local do cliente de ingresso do ambiente compartilhado para que corresponda ao estado compartilhado por todos na sala.
Em grande parte, o Mesh Visual Scripting faz o mesmo. Todas as propriedades de componentes compartilhados e variáveis de script visual que foram alteradas na sala antes de o cliente recém-ingressar são atualizadas localmente para corresponder ao estado compartilhado e, em seguida, quaisquer nós de evento On State Changed que observem essas propriedades ou variáveis são acionados.
Os joiners tardios não reproduzem eventos compartilhados - eles recebem um estado compartilhado.
Do ponto de vista do cliente local, o ambiente sempre evolui do estado inicial que tinha logo após carregar a cena que você carregou para o Mesh. No caso de ingresso tardio, a primeira mudança de estado pode ser maior do que o que acontece enquanto o usuário local está interagindo com a sala em uma sessão contínua, mas é exatamente a mesma coisa em princípio.
Tudo isso acontece à medida que o ambiente se carrega antes mesmo de desaparecer do preto. Assim que o usuário pode realmente ver e interagir com o ambiente, a adesão tardia já está feita.
Fazer com que o estado local siga o estado compartilhado
Muitas vezes, o "estado compartilhado" que um usuário pode observar em um ambiente é, na verdade, uma combinação de estado compartilhado diretamente pelo Mesh e estado local que foi estabelecido por scripts visuais em resposta a um evento que ocorreu na sala. Por exemplo, quando um usuário vira um interruptor no ambiente (estado compartilhado), um script visual pode alterar a cor da skybox (estado local). Você pode ficar tentado a aplicar a alteração local (atualizar a cor da skybox) diretamente em resposta ao usuário que interage com o switch. No entanto, mesmo que o evento de interação ocorra em todos os clientes atualmente na sala, qualquer cliente que entre na sala mais tarde não receberá esse evento simplesmente porque não estava lá quando aconteceu. Em vez disso, você deve fazer com que o estado local siga o estado compartilhado assim:
- Quando o usuário interage (por exemplo, vira o interruptor), faça desse disparo um evento local que atualiza uma variável compartilhada (por exemplo, o estado ligado/desligado do switch).
- Use On State Changed para observar a variável compartilhada.
- Quando o evento On State Changed for acionado (porque a variável compartilhada mudou seu valor), aplique qualquer alteração local desejada (por exemplo, atualize a cor da skybox).
Desta forma, o estado local (a cor skybox) está seguindo o estado compartilhado (o estado do switch). O que é bom sobre isso é que ele funciona sem alteração para o cliente local que virou o interruptor, para todos os outros clientes remotos que estão presentes na sala ao mesmo tempo, e para quaisquer clientes futuros que se juntarão à sala mais tarde.
Fazer com que o estado local siga o estado compartilhado: práticas recomendadas
Eventos locais: Por exemplo, um nó de evento On State Changed observando a propriedade Is Selected Locally de um componente Mesh Interactable Body :
- 🆗 Pode alterar o estado local que é privado para um cliente. Essas alterações de estado permanecerão estritamente no cliente local e desaparecerão quando o cliente sair da sessão.
- 🆗 Pode alterar o estado compartilhado.
- ❌Não é possível alterar o estado local para ser consistente entre os clientes. Um evento local só é executado em um cliente, portanto, as atualizações necessárias para manter o estado local consistente entre os clientes simplesmente não acontecerão em nenhum outro cliente.
Eventos compartilhados: por exemplo, um nó de evento On Trigger Enter anexado a um colisor de gatilho de física compartilhado:
- 🆗 Pode alterar o estado local para efeitos momentâneos: Por exemplo, um efeito de partícula ou um efeito de áudio curto. Somente os clientes presentes na sala quando o evento compartilhado ocorrer poderão ver o efeito local; qualquer cliente que se junte à sala mais tarde não o fará.
- ❌Não é possível alterar o estado local para ser consistente entre os clientes. Um evento compartilhado só é executado em clientes que estão presentes no momento em que ocorre, mas não será repetido para clientes que ingressarem na sessão mais tarde.
- ⛔ Não deve alterar o estado compartilhado. Como um evento compartilhado é executado em todos os clientes, tudo o que ele faz é feito por todos os clientes muito próximos no tempo. Dependendo da natureza da alteração, ela pode acabar sendo repetida várias vezes (por exemplo, um contador de pontuação pode ser incrementado em mais de um em resposta a um único evento).
Em eventos de Estado Alterado que observam o estado compartilhado em variáveis compartilhadas ou propriedades de componentes compartilhados:
- 🆗 Pode alterar o estado local para ser consistente com o estado compartilhado entre os clientes. Para que isso funcione bem de forma repetível e consistente para todos os clientes, você deve traduzir todos os novos valores possíveis do estado compartilhado observado para o estado local, não apenas algumas transições de estado escolhidas a dedo (como Is Selected becoming true).
Fazer com que o estado local siga o estado compartilhado: Exemplo
Neste exemplo, há dois botões interativos neste ambiente: um rotulado com "Star", o outro rotulado com "Sponge". Selecionar um dos botões deve fazer duas coisas:
- Armazene o rótulo correspondente em uma variável de cadeia de caracteres compartilhada chamada ObjectKind.
- Armazene a referência a um objeto de cena correspondente em uma variável de referência GameObject local chamada ObjectRef.
Aqui estão os dois fluxos de script, um para cada botão. Cada um ouve a propriedade compartilhada Is Selected do componente Mesh Interactable Body de um botão e atualiza ObjectKind e ObjectRef dependendo de qual botão foi selecionado:
Tudo parece funcionar bem, mas apenas para usuários que já estão na sala quando um dos botões é selecionado. Qualquer usuário que ingresse na sessão posteriormente encontre um estado inconsistente em sua versão local do ambiente compartilhado: Somente ObjectKind é definido corretamente de acordo com o botão selecionado mais recentemente, mas ObjectRef permanece nulo.
O que há de errado com esses dois fluxos de script?
Primeiro, observe que esses fluxos de script são acionados por um evento compartilhado porque ambos estão ouvindo a alteração da propriedade compartilhada Is Selected de cada botão. Isso parece fazer sentido porque é a única maneira de fazer com que a variável ObjectRef local seja atualizada em todos os clientes.
No entanto:
- Os eventos compartilhados não devem alterar o estado compartilhado, mas esses fluxos de script estão atualizando a variável ObjectKind compartilhada.
- Os eventos compartilhados não podem alterar o estado local para serem consistentes entre os clientes, mas esses fluxos de script estão atualizando a variável ObjectRef local, que pretendemos ser consistente em todos os clientes, assim como ObjectKind.
Então, da forma como isso está configurado atualmente, na verdade não deveríamos estar fazendo nenhuma das coisas que precisamos que os botões façam.
A única maneira óbvia de sair desse problema é tornar locais os eventos que desencadeiam esses fluxos. Podemos fazer isso fazendo com que o nó do evento On State Changed observe a propriedade Is Selected Locally em vez de Is Selected.
Com o evento agora sendo local, isso significa...
- Os eventos locais podem alterar o estado compartilhado para que agora possamos atualizar com segurança a variável ObjectKind compartilhada e seu valor será automaticamente compartilhado entre clientes pela rede interna do Mesh Visual Scripting.
- Os eventos locais não podem alterar o estado local para serem consistentes entre os clientes, portanto, ainda não podemos atualizar a variável ObjectRef local nesses fluxos de script. Teremos de encontrar outro caminho.
É assim que os dois fluxos de script ficam após essas alterações:
O que podemos fazer para definir a variável ObjectRef local para que ela permaneça consistente com isso? Felizmente, esses dois fluxos de script já estabelecem algum estado compartilhado que poderíamos seguir: a variável ObjectKind compartilhada. Tudo o que precisamos fazer é usar um evento On State Changed que observa essa variável e atualiza a variável ObjectRef local dependendo de seu valor:
Esta é uma boa maneira de fazê-lo porque os eventos On State Changed que observam o estado compartilhado podem mudar o estado local para serem consistentes com ele. Isso funcionará para o cliente que pressionou o botão, para todos os outros clientes presentes na mesma sala ao mesmo tempo e para todos os clientes que participarão da sessão mais tarde.
Armadilhas da rede
Atualizações compartilhadas de alta frequência
Quase todo o estado da cena é compartilhado pelo Mesh Visual Scripting por padrão. Isso é ótimo para compartilhar, mas também pode entrar sorrateiramente por acidente e causar carga de rede desnecessária. Por exemplo, o fluxo de script a seguir inundará a rede com atualizações redundantes para a rotação da Transformação. No entanto, como todos os clientes estão executando ao mesmo tempo, nenhuma das atualizações remotas terá um impacto real em qualquer cliente localmente:
Nesse caso, você provavelmente deve usar um escopo de script local para tornar o componente Transformar local para cada cliente. Além disso, você provavelmente deve usar um componente do Animator em vez de um fluxo de script On Update para começar.
O painel Mesh Visual Scripting Diagnostics e o Content Performance Analyzer (CPA), a partir do Mesh Toolkit 5.2411, mostram um aviso de "Atualização compartilhada de alta frequência" para esse tipo de construção.
Na tela inicial é executado em cada cliente
Você pode ficar tentado a pensar no evento On Start como algo que é executado na inicialização da sessão, mas na verdade é acionado em cada cliente, localmente, quando eles entram na sessão. É perfeitamente adequado para inicializar o estado local:
No entanto, ao tentar usar Na tela inicial para inicializar o estado compartilhado, você descobrirá que o estado compartilhado será reinicializado involuntariamente para todos sempre que alguém ingressar na sessão:
O painel Mesh Visual Scripting Diagnostics (a partir do Mesh Toolkit 5.2410) e o Content Performance Analyzer (CPA) (a partir do Mesh Toolkit 5.2411) mostram um aviso "Atualização compartilhada na associação à sessão" quando detetam isso.
O compartilhamento é digitado, mas a atribuição variável não é
Por motivos de segurança, as variáveis de script visual compartilhadas são fortemente tipadas. Isso significa que o tipo selecionado no componente Variáveis para as variáveis de script declaradas define qual tipo de valor exato será sincronizado entre os clientes.
Infelizmente, o Unity Visual Scripting ignora completamente o tipo declarado de uma variável quando você atualiza seu valor. Por exemplo, é fácil armazenar acidentalmente um valor do tipo Float em uma variável que foi declarada para o tipo Integer. Dentro do cliente local, seus scripts visuais não notarão esse erro porque o Visual Scripting converterá automaticamente o Float errôneo para o Integer esperado onde necessário. No entanto, quando se trata de sincronizar esse valor entre clientes, o Mesh Visual Scripting não pode tomar as mesmas liberdades: a garantia de "consistência eventual" impede qualquer conversão de valor em voo, e considerações de segurança tornam desaconselhável aceitar um tipo de valor diferente de um cliente remoto do que foi declarado para a variável.
Por exemplo, considere esta declaração de uma variável compartilhada chamada MyIntegerVar:
Aqui está um fluxo de script que atualiza essa variável:
O que poderia dar errado? Infelizmente, o nó de script Random | Range usado neste exemplo vem em dois sabores: um que produz um valor Integer aleatório e outro que produz um valor Float aleatório. A diferença entre esses dois nós de script no painel seletor de nós é sutil:
Portanto, se você selecionar acidentalmente o nó de script Random | Range errado lá, seu script pode acabar armazenando involuntariamente um valor Float em sua variável do tipo Integer, mas esse valor Float incorreto não será replicado para nenhum outro cliente.
Tenha isso em mente como um motivo potencial pelo qual uma variável compartilhada que você criou pode parecer ter parado de ser compartilhada. Versões futuras do Mesh Visual Scripting podem avisar sobre esse tipo de erro de script quando puderem detetá-lo.