> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/networking/serverless-multiplayer.md).

# Multiplayer sin servidor

Sincroniza el estado de la escena entre jugadores.

Decentraland ejecuta las scenes localmente en la instancia del Explorer de un jugador. Por defecto, los jugadores pueden verse entre sí e interactuar directamente, pero cada jugador interactúa con el entorno de forma independiente. Los cambios en el entorno no se comparten entre jugadores por defecto.

Ver el mismo contenido en el mismo estado es extremadamente importante para que los jugadores interactúen de maneras más significativas.

Hay tres formas de sincronizar el estado de la scene, para que todos los jugadores vean lo mismo:

* **Marcar una Entity como sincronizada**: La opción más sencilla. Ver [Entity marcada como sincronizada](#mark-an-entity-as-synced)
* **Enviar mensajes explícitos de MessageBus**: Enviar y escuchar manualmente mensajes específicos. Ver [Enviar mensajes explícitos de MessageBus](#send-explicit-messagebus-messages)
* **Usar un Multiplayer Server**: Ver [Servidor multijugador](/creator/content-creator-es/scenes-sdk7/networking/authoritative-servers.md). El servidor valida todos los cambios de estado y es la única fuente de verdad. Requiere más configuración, pero se recomienda encarecidamente cuando los jugadores tienen incentivos para explotar tu scene.

Las dos primeras opciones se cubren en este documento. Son más sencillas, ya que no requieren servidor. La desventaja es que dependes más de las velocidades de conexión de los jugadores, y el estado de la scene no se conserva cuando todos los jugadores la abandonan.

## Marcar una Entity como sincronizada

En el [Creator Hub](/creator/content-creator-es/scene-editor/primeros-pasos/about-editor.md), marca una entity como sincronizada añadiendo un **Multiplayer component** a ella. Incluye una casilla para cada uno de los otros components de la entity, lo que te permite seleccionar cuáles actualizar.

![](https://1216664193-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-f64a1bf75807de2bc206e2b7faaacbee74d976d7%2Fmultiplayer-component.png?alt=media\&token=c69c63c3-ebfb-4c39-8088-ac431969f56f)

Para marcar una entity como sincronizada mediante código, usa la `syncEntity` función:

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

export function main() {
  const doorEntity = engine.addEntity();

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

{% hint style="warning" %}
**📔 Nota**: Llama siempre a `syncEntity()` dentro de la `main()` función, o en una función que se ejecute después de `main()` (como un callback o un system). Llamarla en el nivel superior de un archivo arroja un error, porque en ese punto el perfil del jugador aún no está inicializado.
{% endhint %}

{% hint style="warning" %}
**📔 Nota**: En el multiplayer sin servidor, cada client llama a `syncEntity` por su cuenta. Si actualizas a un [Servidor multijugador](/creator/content-creator-es/scenes-sdk7/networking/authoritative-servers.md), el patrón cambia: solo el servidor debe llamar a `syncEntity`, protegido por `isServer()`. Los clients deberían evitar declarar la sincronización de shared entities, a menos que sean creadas por el client.
{% endhint %}

El `syncEntity` function toma las siguientes entradas:

* **entityId**: Una referencia a la entity que se va a sincronizar
* **componentIds**: Una lista de los components que necesitan sincronizarse de esa entity. Es un array que puede contener tantos components como sea necesario. Todos los valores deben ser propiedades `componentId` .
* **entityEnumId**: (opcional) Un id único que se usa de forma consistente por todos los jugadores; consulta [Acerca del enum id](#about-the-enum-id).

No todas las entities o components necesitan sincronizarse. Los elementos estáticos como un árbol que permanece en el mismo sitio no requieren sincronización. En las entities que sí sincronizas, solo los components que cambian con el tiempo deberían sincronizarse. Por ejemplo, si un cubo cambia de color al hacer clic, solo deberías sincronizar el Material component, no el MeshRenderer ni el Transform, ya que esos no cambiarán nunca.

{% hint style="info" %}
**💡 Consejo**: Si los datos que quieres compartir no existen como un component, define un [custom component](/creator/content-creator-es/scenes-sdk7/arquitectura/custom-components.md) que contenga esos datos.
{% endhint %}

### Acerca del enum id

El **entityEnumId** de una entity debe ser único. No está relacionado con el local entityId asignado en `engine.addEntity()`, que se genera automáticamente y puede variar entre jugadores que ejecutan la misma scene. El entityEnumId de una entity debe definirse explícitamente en el código y ser único.

Establecer explícitamente este ID es importante para evitar inconsistencias si una condición de carrera hace que una parte de la scene cargue antes que otra. Tal vez para el player A la puerta de la scene sea la entity *512*, pero para el player B esa misma puerta sea la entity *513*. En ese caso, si el player A abre la puerta, el player B verá en cambio cómo se mueve todo el edificio.

{% hint style="info" %}
**💡 Consejo**: Crea un enum en tu scene, para mantener claras las referencias a cada id sincronizable en tu scene.

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

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

export function main() {
  syncEntity(
    doorEntity,
    [Transform.componentId, Animator.componentId],
    EntityEnumId.DOOR
  );
}
```

Aquí el enum EntityEnumId se usa para etiquetar entities con un identificador único, asegurando que cada client reconozca la entity modificada, independientemente del orden de creación.
{% endhint %}

{% hint style="warning" %}
**📔 Nota**: Evita usar números mayores que **8001** si tu scene también incluye Smart Items. Los items que son creados por el [Creator Hub](/creator/content-creator-es/scene-editor/primeros-pasos/about-editor.md) con un Multiplayer component utilizarán IDs asignados automáticamente a partir de 8001. Cualquier ID menor que 8001 es seguro asignarlo a tus entities sincronizadas.
{% endhint %}

**Entities creadas por un jugador**

Si una entity se crea como resultado de la interacción de un jugador, y esta entity debe sincronizarse con otros jugadores, la entity no necesita un entityEnumId. Puedes usar `syncEntity()` pasando solo la entity y la lista de components. Un valor único para entityEnumId se asigna automáticamente entre bastidores.

Todas las entities instanciadas al iniciar la scene necesitan tener un ID asignado manualmente. Eso es para garantizar que todos los jugadores usen el mismo ID en cada una. Cuando un solo jugador se encarga de instanciar una entity, no se necesitan IDs explícitos. Los demás jugadores reciben actualizaciones sobre esta nueva entity con un ID ya asignado, por lo que no hay riesgo de desajustes de ID.

Por ejemplo, en una scene de pelea de bolas de nieve, cada vez que un jugador lanza una bola de nieve, está instanciando una nueva entity que se sincroniza con otros jugadores. La bola de nieve no necesita un 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 con parent**

El parent de una entity normalmente se define mediante la `propiedad parent` en el `Transform` component. Sin embargo, esta propiedad apunta al local entity id del parent, que podría variar; consulta [Acerca del enum id](#about-the-enum-id). Para parentar entities que necesitan sincronizarse, o que tienen children que necesitan sincronizarse, usa la `parentEntity()` función en lugar de la `Transform`.

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

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

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

  parentEntity(child, parent);
}
```

Ten en cuenta que tanto el parent como el child se sincronizan con `syncEntity`, por lo que todos los jugadores tienen una comprensión común de qué ids usan ambas entities. Esto es necesario incluso si los components del parent quizá nunca necesiten cambiar. En este ejemplo, la `syncEntity` incluye un array vacío de components, para evitar sincronizar components innecesarios.

{% hint style="warning" %}
**📔 Nota**: Si una entity es parentada tanto por la `parentEntity()` y también por el `propiedad parent` en el `Transform` component, la propiedad en el `Transform` component se ignora.
{% endhint %}

Cuando las entities se parentan mediante la `parentEntity()` función, también puedes hacer uso de las siguientes funciones auxiliares:

* **removeParent()**: Deshace los efectos de `parentEntity()`. Requiere que solo pases la entity child. El nuevo parent de la entity pasa a ser el RootEntity de la scene. El parent original no se elimina de la scene.
* **getParent()**: Devuelve el parent de una entity que hayas pasado.
* **getChildren()**: Devuelve la lista de children de la entity que hayas pasado, como un iterable.
* **getFirstChild()**: Devuelve el primer child de la lista para la entity que hayas pasado.

```ts
import {
  syncEntity,
  parentEntity,
  getParent,
  getFirstChild,
  getChildren,
  removeParent,
} from "@dcl/sdk/network";

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

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

  // establece parent como parent
  parentEntity(child, parent);

  // getParent
  const getParentResult = getParent(child);
  // devuelve parent

  // getFirstChild
  const getFirstChildResult = getFirstChild(parent);
  // devuelve child

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

  // quita el parent del child
  removeParent(child);
}
```

## Comprobar el estado de sincronización

Cuando un jugador acaba de cargar en una scene, es posible que todavía no esté sincronizado con los demás jugadores que lo rodean. Si el jugador empieza a alterar el estado del juego antes de estar sincronizado, esto podría causar problemas en tu juego. Recomendamos comprobar siempre que un jugador esté sincronizado antes de permitirle editar cualquier cosa de la scene.

Si un jugador sale de los parcels de la scene, también quedará fuera de sync con la scene mientras permanezca fuera. Por eso también es importante que los systems de la scene manejen ese escenario, ya que la scene sigue ejecutándose mientras el jugador esté cerca. Una vez que el jugador vuelva a entrar, se actualizará automáticamente con cualquier cambio del estado de la scene.

Puedes comprobar si el estado de la scene está sincronizado actualmente para un jugador mediante la `isStateSyncronized()` función. Esta función devuelve un booleano, que es true si el jugador ya está sincronizado con la scene.

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

const isConnected = isStateSyncronized();
```

Por ejemplo, podrías incluir esta comprobación en un system y bloquear cualquier interacción si esta función devuelve false.

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

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

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

## Enviar mensajes explícitos de MessageBus

{% hint style="warning" %}
**📔 Nota**: La `MessageBus` API está marcada como obsoleta en el SDK, y puede eliminarse en una versión futura. Para la mayoría de los casos de uso, prefiere [marcar entities como sincronizadas](#mark-an-entity-as-synced).
{% endhint %}

**Iniciar un message bus**

Crea un objeto message bus para gestionar los métodos necesarios para enviar y recibir mensajes entre jugadores.

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

const sceneMessageBus = new MessageBus();
```

**Enviar mensajes**

Usa el `.emit` comando del message bus para enviar un mensaje a todos los demás jugadores de la 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: "Clic" },
  },
  function () {
    sceneMessageBus.emit("box1Clicked", {});
  }
);
```

Cada mensaje puede contener un payload como segundo argumento. El payload es de tipo `Object`, y puede contener cualquier dato relevante que desees 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" %}
**💡 Consejo**: Si necesitas que un solo mensaje incluya datos de más de una variable, crea un tipo personalizado para contener todos estos datos en un solo objeto.
{% endhint %}

**Recibir mensajes**

Para gestionar mensajes de todos los demás jugadores en esa scene, usa `.on`. Cuando uses esta función, proporcionas una cadena de mensaje y defines una función a ejecutar. Cada vez que llega un mensaje con una cadena coincidente, la función dada se ejecuta una 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**: Los mensajes enviados por un jugador también son recibidos por ese mismo jugador. El `.on` método no puede distinguir entre un mensaje emitido por ese mismo jugador y un mensaje emitido por otros jugadores.
{% endhint %}

**Ejemplo completo de MessageBus**

Este ejemplo usa un message bus para enviar un nuevo mensaje cada vez que se hace clic en el cubo principal, generando un nuevo cubo en una posición aleatoria. El mensaje incluye la posición del nuevo cubo, para que todos los jugadores vean estos nuevos cubos en las mismas posiciones.

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

/// --- Crear 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);

  // Cuando se hace clic en un cubo, envía un mensaje para generar otro
  pointerEventsSystem.onPointerDown(
    {
      entity: meshEntity,
      opts: { button: InputAction.IA_PRIMARY, hoverText: "Pulsa E para generar" },
    },
    function () {
      sceneMessageBus.emit("spawn", {
        position: {
          x: 1 + Math.random() * 8,
          y: Math.random() * 8,
          z: 1 + Math.random() * 8,
        },
      });
    }
  );

  return meshEntity;
}

// Inicialización
createCube(8, 1, 8);

// define el tipo de datos
type NewBoxPosition = {
  position: { x: number; y: number; z: number };
};

// al recibir el mensaje spawn, crea un nuevo cubo
sceneMessageBus.on("spawn", (info: NewBoxPosition) => {
  createCube(info.position.x, info.position.y, info.position.z);
});
```

## Prueba una escena multijugador localmente

Si inicias una vista previa de la escena y la abres en dos (o más) ventanas diferentes de Explorer, cada ventana abierta se interpretará como un jugador separado, y un servidor simulado de comunicaciones mantendrá sincronizados a estos jugadores.

Interactúa con la escena en una ventana, luego cambia a la otra para ver que los efectos de esa interacción también son visibles allí.

Usando Creator Hub, activa **Vista previa con múltiples instancias** en el **Desktop Client** el submenú de **Opciones de reproducción**, luego haz clic en el **Play** botón una segunda vez para abrir una segunda ventana del explorer de Decentraland. Debes conectarte en ambas ventanas con direcciones diferentes. Las mismas sesiones permanecerán abiertas mientras la escena se recarga.

![](https://1216664193-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-30317b50bc4b4e646a1b28effe1ff76939ab0f28%2Fpreview-button.png?alt=media\&token=fbed0f2f-8063-4283-9b07-2d964c9c0e68)

Como alternativa, puedes abrir una segunda ventana del explorer de Decentraland escribiendo lo siguiente en la URL de un navegador:

> `decentraland://realm=http://127.0.0.1:8000&local-scene=true&debug=true&multi-instance=true`

## Escenas de un solo jugador

Si tu scene se despliega en un [Decentraland World](/creator/content-creator-es/scenes-sdk7/publicacion/publishing-options.md#decentraland-worlds), puedes hacer que sea una scene de un solo jugador. Los jugadores no se verán entre sí, no podrán chatear ni ver los efectos de las acciones de los demás.

Para hacer esto, configura el `scene.json` archivo de la scene para establecer el **fixedAdapter** en `offline:offline`. La scene no tendrá ningún Communication Service en absoluto y cada usuario que se una a ese world estará siempre solo.

**Ejemplo:**

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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/networking/serverless-multiplayer.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
