# Multiplayer Serverless

O Decentraland executa scenes localmente na instância do Explorer de um jogador. Por predefinição, os jogadores conseguem ver-se uns aos outros e interagir diretamente, mas cada jogador interage com o ambiente de forma independente. As alterações no ambiente não são partilhadas entre jogadores por predefinição.

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

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

* **Marcar uma entity como sincronizada**: A opção mais fácil. Ver [Marcar uma entity como sincronizada](#mark-an-entity-as-synced)
* **Enviar mensagens explícitas de MessageBus**: Envie e escute manualmente mensagens específicas. Ver [Enviar mensagens explícitas de MessageBus](#send-explicit-messagebus-messages)
* **Usar um Server**: Ver [Servers de terceiros](https://github.com/decentraland/docs/blob/main/creator/sdk7/sdk7/networking/authoritative-servers.md). Esta opção é mais complicada de configurar, mas é recomendável se os jogadores tiverem incentivos para explorar vulnerabilidades da sua Scene.

As duas primeiras opções são abordadas neste documento. São mais simples, pois não requerem Server. A desvantagem é que depende mais da velocidade de ligação dos jogadores, e o estado da Scene não é persistido quando todos os jogadores saem da Scene.

## Marcar uma Entity como sincronizada

No [Creator Hub](https://github.com/decentraland/docs/blob/main/creator/sdk7/scene-editor/get-started/about-editor.md), marque uma entity como sincronizada adicionando um **component Multiplayer** à mesma. Inclui uma caixa de verificação para cada um dos outros components na entity, permitindo-lhe selecionar quais atualizar.

![](https://2402076176-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-f64a1bf75807de2bc206e2b7faaacbee74d976d7%2Fmultiplayer-component.png?alt=media)

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

```ts
import { syncEntity } from '@dcl/sdk/network'

const doorEntity = engine.addEntity()

syncEntity(doorEntity, [Transform.componentId, Animator.componentId], 1)
```

A `syncEntity` função recebe os seguintes inputs:

* **entityId**: Uma referência para a entity a sincronizar
* **componentIds**: Uma lista dos components que precisam de ser sincronizados dessa entity. Isto é um array que pode conter tantas entities quantas forem necessárias. Todos os valores devem ser propriedades `componentId` .
* **entityEnumId**: (opcional) Um id único que é usado de forma consistente por todos os jogadores, ver [Sobre o enum id](#about-the-enum-id).

Nem todas as entities ou components precisam de ser sincronizados. Elementos estáticos, como uma árvore que permanece no mesmo lugar, não requerem sincronização. Nas entities que sincroniza, apenas os components que mudam ao longo do tempo devem ser sincronizados. Por exemplo, se um cubo mudar de cor quando recebe Click, deve sincronizar apenas o component Material, não o MeshRenderer nem o Transform, pois esses nunca mudarão.

{% hint style="info" %}
**💡 Dica**: Se os dados que pretende partilhar não existirem como um component, defina um [component personalizado](https://github.com/decentraland/docs/blob/main/creator/sdk7/sdk7/architecture/custom-components.md) que contenha esses dados.
{% endhint %}

### Sobre o enum id

A **entityEnumId** de uma entity tem de ser único. Não está relacionado com o entityId local atribuído em `engine.addEntity()`, que é gerado automaticamente e pode variar entre jogadores a executar a mesma Scene. O entityEnumId de uma entity tem de ser definido explicitamente no código e ser único.

Definir explicitamente este ID é importante para evitar inconsistências se uma race condition fizer com que uma parte da Scene carregue antes de outra. Talvez, para o jogador A, a porta na Scene seja a entity *512*, mas para o jogador B essa mesma porta seja a entity *513*. Nesse caso, se o jogador A abrir a porta, o jogador B verá antes o edifício inteiro mover-se.

{% hint style="info" %}
**💡 Dica**: Crie um enum na sua Scene, para manter referências claras a cada id sincronizável na sua Scene.

```ts
import { syncEntity } from '@dcl/sdk/network'

enum EntityEnumId {
	DOOR = 1,
	DRAW_BRIDGE = 2,
	ELEVATOR = 3,
}

syncEntity(
	doorEntity,
	[Transform.componentId, Animator.componentId],
	EntityEnumId.DOOR
)
```

Aqui, o enum EntityEnumId é usado para identificar entities com um identificador único, garantindo que cada client reconhece a entity modificada, independentemente da ordem de criação.
{% endhint %}

{% hint style="warning" %}
**📔 Nota**: Evite usar números superiores a **8001** se a sua Scene também incluir Smart Items. Os itens que são criados pelo [Creator Hub](https://github.com/decentraland/docs/blob/main/creator/sdk7/scene-editor/get-started/about-editor.md) com um component Multiplayer usarão IDs atribuídos automaticamente a partir de 8001. Qualquer ID inferior a 8001 é seguro para atribuir às suas entities sincronizadas.
{% endhint %}

**Entities criadas por um jogador**

Se uma entity for criada como resultado da interação de um jogador, e essa entity deva ser sincronizada com outros jogadores, a entity não precisa de um entityEnumId. Pode usar `syncEntity()` passando apenas a entity e a lista de components. Um valor único para entityEnumId é atribuído automaticamente nos bastidores.

Todas as entities instanciadas no início da Scene precisam de ter um ID atribuído manualmente. Isto serve para garantir que todos os jogadores usam o mesmo ID em cada uma delas. Quando um único jogador é responsável por instanciar uma entity, não são necessários IDs explícitos. Os outros jogadores recebem atualizações sobre esta nova entity com um ID já atribuído, por isso não existe risco de incompatibilidade de IDs.

Por exemplo, numa Scene de luta de bolas de neve, sempre que um jogador atira uma bola de neve, está a instanciar uma nova entity que é sincronizada com outros jogadores. A bola de neve não precisa de um entityEnumId único.

```ts
import { syncEntity } from '@dcl/sdk/network'

function onThrow() {
	const ball = engine.addEntity()
	Transform.create(ball, {})
	GLTFContainer.create(ball, { src: 'assets/snowBall.glb' })
	syncEntity(ball, [Transform.componentId, GLTFContainer.componentId])
}
```

**Entities com parent**

O parent de uma entity é normalmente definido através da propriedade `parent` no component `Transform` . Contudo, esta propriedade aponta para o id local da entity parent, que pode variar, ver [Sobre o enum id](#about-the-enum-id). Para fazer parent de entities que precisam de ser sincronizadas, ou que tenham children que precisem de ser sincronizados, use a função `parentEntity()` em vez da `Transform`.

```ts
import { syncEntity, parentEntity } from '@dcl/sdk/network'

const parent = engine.addEntity()
Transform.create(parent, { position: somePosition })
syncEntity(parent, [])

const child: Entity = engine.addEntity()
syncEntity(child, [Transform.componentId])

parentEntity(child, parent)
```

Note que tanto o parent como o child são sincronizados com `syncEntity`, por isso todos os jogadores têm um entendimento comum sobre que ids são usados por ambas as entities. Isto é necessário mesmo que os components do parent nunca precisem de mudar. Neste exemplo, o `syncEntity` inclui um array vazio de components, para evitar sincronizar components desnecessários.

{% hint style="warning" %}
**📔 Nota**: Se uma entity tiver parent definido tanto pelo `parentEntity()` como também pelo component `parent` no component `Transform` , a propriedade no component `Transform` é ignorada.
{% endhint %}

Quando as entities têm parent através da função `parentEntity()` , também pode usar as seguintes funções auxiliares:

* **removeParent()**: Anula os efeitos de `parentEntity()`. Requer que passe apenas a entity child. O novo parent da entity passa a ser a root entity da Scene. A entity parent original não é removida da Scene.
* **getParent()**: Devolve a entity parent da entity que passou.
* **getChildren()**: Devolve a lista de children da entity que passou, como um iterável.
* **getFirstChild()**: Devolve o primeiro child na lista da entity que passou.

```ts
import { syncEntity, parentEntity } from '@dcl/sdk/network'

const parent = engine.addEntity()
Transform.create(parent, { position: somePosition })
syncEntity(parent, [])

const child: Entity = engine.addEntity()
syncEntity(child, [Transform.componentId])

// define parent como parent
parentEntity(child, parent)

// getParent
const getParentResult = getParent(child)
// devolve parent

// getFirstChild
const getFirstChildResult = getFirstChild(parent)
// devolve child

// getChildren
const getChildrenResult = Array.from(getChildren(parent))
// devolve [child]

// remove parent de child
removeParent(child)
```

## Verificar o estado de sincronização

Quando um jogador entra numa Scene, pode ainda não estar sincronizado com os outros jogadores à sua volta. Se o jogador começar a alterar o estado do jogo antes de estar sincronizado, isso pode causar problemas no seu jogo. Recomendamos verificar sempre se um jogador está sincronizado antes de lhe permitir editar qualquer aspeto da Scene.

Se um jogador sair dos parcels da Scene, também ficará fora de sincronização com a Scene enquanto estiver no exterior. Por isso, também é importante que os systems da Scene tratem desse cenário, uma vez que a Scene continua em execução enquanto o jogador estiver por perto. Assim que o jogador voltar a entrar, é automaticamente atualizado com quaisquer alterações ao estado da Scene.

Pode verificar se o estado da Scene está atualmente sincronizado para um jogador através da função `isStateSyncronized()` . Esta função devolve um boolean, que é true se o jogador já estiver sincronizado com a Scene.

```ts
import { isStateSyncronized } from '@dcl/sdk/network'

const isConnected = isStateSyncronized()
```

Pode, por exemplo, incluir esta verificação num system e bloquear qualquer interação se esta função devolver false.

```ts
import { isStateSyncronized } from '@dcl/sdk/network'

engine.addSystem(() => {
	if (isStateSyncronized() && !button.enabled) {
		console.log('Enable Start Game')
		button.enable()
	}

	if (!isStateSyncronized() && button.enabled) {
		console.log(`Disable Start Game.`)
		button.disable()
	}
})
```

## Enviar mensagens explícitas de 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.

```ts
import { MessageBus } from '@dcl/sdk/message-bus'

const sceneMessageBus = new MessageBus()
```

**Enviar mensagens**

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

```ts
import { MessageBus } from '@dcl/sdk/message-bus'

const sceneMessageBus = new MessageBus()

const myEntity = engine.addEntity()
MeshRenderer.setBox(myEntity)
MeshCollider.setBox(myEntity)

pointerEventsSystem.onPointerDown(
	{
		entity: myEntity,
		opts: { button: InputAction.IA_PRIMARY, hoverText: 'Click' },
	},
	function () {
		sceneMessageBus.emit('box1Clicked', {})
	}
)
```

Cada mensagem pode conter um payload como segundo argumento. O payload é do tipo `Object`, e pode conter quaisquer dados relevantes que pretenda enviar.

```ts
import { MessageBus } from '@dcl/sdk/message-bus'

const sceneMessageBus = new MessageBus()

sceneMessageBus.emit('spawn', { position: { x: 10, y: 2, z: 10 } })
```

{% hint style="info" %}
**💡 Dica**: Se precisar que uma única mensagem inclua dados de mais do que uma variável, crie um tipo personalizado para conter todos esses dados num único objeto.
{% endhint %}

**Receber mensagens**

Para processar mensagens de todos os outros jogadores nessa Scene, use `.on`. Ao usar esta função, fornece uma string de mensagem e define uma função a executar. Cada vez que chega uma mensagem com uma string correspondente, a função indicada é executada uma vez.

```ts
import { MessageBus } from '@dcl/sdk/message-bus'

const sceneMessageBus = new MessageBus()

type NewBoxPosition = {
	position: { x: number; y: number; z: number }
}

sceneMessageBus.on('spawn', (info: NewBoxPosition) => {
	const myEntity = engine.addEntity()
	Transform.create(myEntity, {
		position: { x: info.position.x, y: info.position.y, z: info.position.z },
	})
	MeshRenderer.setBox(myEntity)
	MeshCollider.setBox(myEntity)
})
```

{% hint style="warning" %}
**📔 Nota**: As mensagens que são enviadas por um jogador também são recebidas por esse mesmo jogador. O método `.on` não consegue distinguir entre uma mensagem emitida por esse mesmo jogador e uma mensagem emitida por outros jogadores.
{% endhint %}

**Exemplo completo de MessageBus**

Este exemplo usa um message bus para enviar uma nova mensagem sempre que o cubo principal recebe Click, gerando um novo cubo numa 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.

```ts
import { MessageBus } from '@dcl/sdk/message-bus'

/// --- Criar message bus ---
const sceneMessageBus = new MessageBus()

// Fábrica de cubos
function createCube(x: number, y: number, z: number): Entity {
	const meshEntity = engine.addEntity()
	Transform.create(meshEntity, { position: { x, y, z } })
	MeshRenderer.setBox(meshEntity)
	MeshCollider.setBox(meshEntity)

	// Quando se faz Click num cubo, envia mensagem para gerar outro
	pointerEventsSystem.onPointerDown(
		{
			entity: myEntity,
			opts: { button: InputAction.IA_PRIMARY, hoverText: 'Press E to spawn' },
		},
		function () {
			sceneMessageBus.emit('spawn', {
				position: {
					x: 1 + Math.random() * 8,
					y: Math.random() * 8,
					z: 1 + Math.random() * 8,
				},
			})
		}
	)

	return meshEntity
}

// Init
createCube(8, 1, 8)

// definir tipo de dados
type NewBoxPosition = {
	position: { x: number; y: number; z: number }
}

// ao receber mensagem spawn, criar novo cubo
sceneMessageBus.on('spawn', (info: NewBoxPosition) => {
	createCube(info.position.x, info.position.y, info.position.z)
})
```

## Testar uma Scene multiplayer localmente

Se iniciar uma Preview da Scene e a abrir em duas (ou mais) janelas diferentes do Explorer, cada janela aberta será interpretada como um jogador separado, e um Server de comunicações simulado manterá esses jogadores sincronizados.

Interaja com a Scene numa janela e, 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 Explorer do Decentraland. Tem de ligar-se em ambas as janelas com endereços diferentes. As mesmas sessões permanecerão abertas à medida que a Scene recarrega.

![](https://2402076176-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-30317b50bc4b4e646a1b28effe1ff76939ab0f28%2Fpreview-button.png?alt=media)

Em alternativa, pode abrir uma segunda janela do Explorer do Decentraland escrevendo o seguinte num URL do browser:

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

## Scenes single player

Se a sua Scene estiver implementada num [Decentraland World](https://docs.decentraland.org/creator/content-creator-pt/worlds/about), pode transformá-la numa Scene single player. Os jogadores não se verão, não poderão conversar nem ver os efeitos das ações uns dos outros.

Para fazer isto, configure o ficheiro `scene.json` da Scene para definir o **fixedAdapter** como `offline:offline`. A Scene não terá qualquer Communication Service e cada utilizador que entrar nesse World estará sempre sozinho.

**Exemplo:**

```json
{
	"worldConfiguration": {
		"name": "my-name.dcl.eth",
		"fixedAdapter": "offline:offline"
	}
}
```
