Multijogador Serverless

Sincronize o estado da cena entre jogadores.

Decentraland executa cenas localmente no navegador do jogador. Por padrão, os jogadores conseguem se ver e interagir diretamente, mas cada jogador interage com o ambiente de forma independente. Alterações no ambiente não são compartilhadas entre jogadores por padrão.

Ver o mesmo conteúdo no mesmo estado é extremamente importante para que os jogadores interajam de maneiras mais significativas.

Existem três maneiras de sincronizar o estado da cena, para que todos os jogadores vejam o mesmo:

As duas primeiras opções são cobertas neste documento. Elas são mais simples, pois não exigem servidor. A desvantagem é que você depende mais das velocidades de conexão dos jogadores, e o estado da cena não é persistido quando todos os jogadores deixam a cena.

Marcar uma Entidade como Sincronizada

No Creator Hubarrow-up-right, marque uma entidade como sincronizada adicionando um componente Multiplayer a ela. Ele inclui uma caixa de seleção para cada um dos outros componentes na entidade, permitindo que você selecione quais devem ser atualizados.



Para marcar uma entidade como sincronizada via código, use a syncEntity função:

A syncEntity função recebe as seguintes entradas:

  • entityId: Uma referência à entidade a ser sincronizada

  • componentIds: Uma lista dos componentes que precisam ser sincronizados dessa entidade. Este é um array que pode conter quantas entradas forem necessárias. Todos os valores devem ser componentId propriedades.

  • entityEnumId: (opcional) Um id único que é usado de forma consistente por todos os jogadores, veja Sobre o enum id.

Nem todas as entidades ou componentes precisam ser sincronizados. Elementos estáticos como uma árvore que permanece no mesmo lugar não exigem sincronização. Nas entidades que você sincroniza, apenas os componentes que mudam ao longo do tempo devem ser sincronizados. Por exemplo, se um cubo muda de cor ao ser clicado, você deve sincronizar apenas o componente Material, não o MeshRenderer ou o Transform, pois estes nunca mudariam.

circle-info

💡 Dica: Se os dados que você quer compartilhar não existirem como um componente, defina um componente personalizadoarrow-up-right que contenha esses dados.

Sobre o enum id

A entityEnumId de uma entidade deve ser único. Não está relacionado ao entityId local atribuído em engine.addEntity(), que é gerado automaticamente e pode variar entre os jogadores executando a mesma cena. O entityEnumId de uma entidade deve ser definido explicitamente no código e ser único.

Definir explicitamente esse ID é importante para evitar inconsistências se uma condição de corrida fizer uma parte da cena carregar antes de outra. Talvez para o jogador A a porta na cena seja a entidade 512, mas para o jogador B essa mesma porta seja a entidade 513. Nesse caso, se o jogador A abrir a porta, o jogador B verá em vez disso todo o prédio se mover.

circle-info

💡 Dica: Crie um enum na sua cena, para manter referências claras a cada id sincronizável na sua cena.

Aqui o enum EntityEnumId é usado para marcar entidades com um identificador único, garantindo que cada cliente reconheça a entidade modificada, independentemente da ordem de criação.

circle-exclamation

Entidades criadas por um jogador

Se uma entidade é criada como resultado de uma interação de um jogador, e essa entidade deve ser sincronizada com outros jogadores, a entidade não precisa de um entityEnumId. Você pode usar syncEntity() passando apenas a entidade e a lista de componentes. Um valor único para entityEnumId é atribuído automaticamente nos bastidores.

Todas as entidades instanciadas na iniciação da cena precisam ter um ID atribuído manualmente. Isso é para garantir que todos os jogadores usem o mesmo ID em cada uma. Quando um único jogador está encarregado de instanciar uma entidade, IDs explícitos não são necessários. Outros jogadores recebem atualizações sobre essa nova entidade com um ID já atribuído, então não há risco de incompatibilidade de IDs.

Por exemplo, em uma cena de batalha de bolas de neve, toda vez que um jogador arremessa uma bola de neve, ele instancia uma nova entidade que é sincronizada com outros jogadores. A bola de neve não precisa de um entityEnumId único.

Entidades com parent

O pai de uma entidade normalmente é definido via propriedade parent no componente Transform . Esta propriedade entretanto aponta para o id local da entidade pai, que pode variar, veja Sobre o enum id. Para parentear entidades que precisam ser sincronizadas, ou que tenham filhos que precisam ser sincronizados, use a função parentEntity() em vez do componente Transform.

Note que tanto o pai quanto o filho são sincronizados com syncEntity, então todos os jogadores têm uma compreensão comum de quais ids são usados por ambas as entidades. Isso é necessário mesmo se os componentes do pai podem nunca precisar mudar. Neste exemplo, o syncEntity inclui um array vazio de componentes, para evitar sincronizar quaisquer componentes desnecessários.

circle-exclamation

Quando entidades são parentadas via a função parentEntity() função, você também pode fazer uso das seguintes funções auxiliares:

  • removeParent(): Desfazer os efeitos de função parentEntity(). Requer que você passe apenas a entidade filha. O novo pai da entidade torna-se a entidade raiz da cena. A entidade pai original não é removida da cena.

  • getParent(): Retorna a entidade pai da entidade que você passou.

  • getChildren(): Retorna a lista de filhos da entidade que você passou, como um iterável.

  • getFirstChild(): Retorna o primeiro filho na lista para a entidade que você passou.

Verificar o estado de sincronização

Quando um jogador acaba de carregar em uma cena, ele pode ainda não estar sincronizado com outros jogadores ao seu redor. Se o jogador começar a alterar o estado do jogo antes de estar sincronizado, isso pode causar problemas no seu jogo. Recomendamos sempre checar se um jogador está sincronizado antes de permitir que ele edite qualquer coisa sobre a cena.

Se um jogador sair dos lotes (parcels) da cena, ele também ficará fora de sincronia com a cena enquanto estiver do lado de fora. Portanto, também é importante que os sistemas da cena tratem esse cenário, já que a cena continua rodando enquanto o jogador está por perto. Uma vez que o jogador volte, ele é atualizado automaticamente com quaisquer mudanças do estado da cena.

Você pode verificar se o estado da cena está atualmente sincronizado para um jogador via a função isStateSyncronized() . Esta função retorna um booleano, que é verdadeiro se o jogador já estiver sincronizado com a cena.

Você poderia, por exemplo, incluir essa verificação em um sistema, e bloquear qualquer interação se esta função retornar false.

Enviar Mensagens Explícitas do MessageBus

Iniciar um message bus

Crie um objeto message bus para lidar com os métodos necessários para enviar e receber mensagens entre jogadores.

Enviar mensagens

Use o comando .emit do message bus para enviar uma mensagem a todos os outros jogadores na cena.

Cada mensagem pode conter uma carga útil como segundo argumento. A carga útil é do tipo Object, e pode conter quaisquer dados relevantes que você deseje enviar.

circle-info

💡 Dica: Se você precisar que uma única mensagem inclua dados de mais de uma variável, crie um tipo personalizado para agrupar todos esses dados em um único objeto.

Receber mensagens

Para lidar com mensagens de todos os outros jogadores naquela cena, use .on. Ao usar essa função, você fornece uma string de mensagem e define uma função para executar. Cada vez que uma mensagem com a string correspondente chegar, a função fornecida é executada uma vez.

circle-exclamation

Exemplo completo do MessageBus

Este exemplo usa um message bus para enviar uma nova mensagem toda vez que o cubo principal é clicado, gerando um novo cubo em uma posição aleatória. A mensagem inclui a posição do novo cubo, para que todos os jogadores vejam esses novos cubos nas mesmas posições.

Testar uma cena multiplayer localmente

Se você iniciar uma pré-visualização da cena e abri-la em duas (ou mais) janelas do Explorer diferentes, cada janela aberta será interpretada como um jogador separado, e um servidor de comunicações simulado manterá esses jogadores sincronizados.

Interaja com a cena em uma janela, depois mude para a outra para ver que os efeitos dessa interação também são visíveis lá.

Usando o Creator Hub, clique no botão Preview uma segunda vez, e isso abrirá uma segunda janela do Decentraland Explorer. Você deve conectar-se em ambas as janelas com endereços diferentes. As mesmas sessões permanecerão abertas conforme a cena recarrega.



Como alternativa, você pode abrir uma segunda janela do Decentraland Explorer escrevendo o seguinte na URL do navegador:

decentraland://realm=http://127.0.0.1:8000&local-scene=true&debug=true&open-deeplink-in-new-instance=true

Cenas para jogador único

Se sua cena estiver implantada em um Decentraland World, você pode torná-la uma cena de jogador único. Os jogadores não se verão, não poderão conversar nem ver os efeitos das ações uns dos outros.

Para fazer isso, configure o arquivo scene.json da cena para definir o fixedAdapter como offline:offline. A cena não terá nenhum Serviço de Comunicação e cada usuário que entrar nesse mundo estará sempre sozinho.

Exemplo:

Atualizado