> 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-ko/sdk7/networking/serverless-multiplayer.md).

# 서버리스 멀티플레이어

플레이어 간에 씬 상태를 동기화합니다.

Decentraland는 플레이어의 익스플로러 인스턴스에서 씬을 로컬로 실행합니다. 기본적으로 플레이어들은 서로를 볼 수 있고 직접 상호작용할 수 있지만, 각 플레이어는 환경과 독립적으로 상호작용합니다. 기본적으로 환경의 변경 사항은 플레이어 간에 공유되지 않습니다.

같은 상태의 같은 콘텐츠를 보는 것은 플레이어들이 더 의미 있는 방식으로 상호작용하는 데 매우 중요합니다.

모든 플레이어가 동일한 것을 보도록 씬 상태를 동기화하는 방법은 세 가지가 있습니다:

* **엔티티를 동기화됨으로 표시**: 가장 쉬운 옵션입니다. 다음을 참조하세요. [엔티티를 동기화됨으로 표시](#mark-an-entity-as-synced)
* **명시적 MessageBus 메시지 보내기**: 특정 메시지를 수동으로 보내고 수신합니다. 다음을 참조하세요. [명시적 MessageBus 메시지 보내기](#send-explicit-messagebus-messages)
* **멀티플레이어 서버 사용**: 다음을 참조하세요. [멀티플레이어 서버](/creator/content-creator-ko/sdk7/networking/authoritative-servers.md). 서버는 모든 상태 변경을 검증하며 단일 진실 공급원입니다. 더 많은 설정이 필요하지만, 플레이어에게 여러분의 씬을 악용할 동기가 있을 때는 강력히 권장됩니다.

처음 두 가지 옵션은 이 문서에서 다룹니다. 서버가 필요 없으므로 더 간단합니다. 단점은 플레이어의 연결 속도에 더 많이 의존하게 되고, 모든 플레이어가 씬을 떠나면 씬 상태가 유지되지 않는다는 점입니다.

## 엔티티를 동기화됨으로 표시

에서 [Creator Hub](/creator/content-creator-ko/scene-editor/get-started/about-editor.md)에서 다음을 추가하여 엔티티를 동기화됨으로 표시합니다. **멀티플레이어 컴포넌트** 를 추가하세요. 여기에는 엔티티의 다른 각 컴포넌트에 대한 체크박스가 포함되어 있어, 어떤 것을 업데이트할지 선택할 수 있습니다.

![](https://3980763956-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)

코드로 엔티티를 동기화됨으로 표시하려면 다음을 사용하세요. `syncEntity` 함수:

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

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

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

{% hint style="warning" %}
**📔 참고**: 항상 `syncEntity()` 를 `main()` 함수 내부에서 호출하거나, 그 뒤에 실행되는 함수(예: 콜백 또는 시스템)에서 호출하세요. 파일의 최상위 수준에서 호출하면 오류가 발생하는데, 그 시점에는 아직 플레이어 프로필이 초기화되지 않았기 때문입니다. `main()` (예: 콜백 또는 시스템)에서 호출하세요. 파일의 최상위 수준에서 호출하면 오류가 발생하는데, 그 시점에는 아직 플레이어 프로필이 초기화되지 않았기 때문입니다.
{% endhint %}

{% hint style="warning" %}
**📔 참고**: 서버가 없는 멀티플레이어에서는 모든 클라이언트가 `syncEntity` 를 자체적으로 호출합니다. 만약 [멀티플레이어 서버](/creator/content-creator-ko/sdk7/networking/authoritative-servers.md)로 업그레이드하면 패턴이 바뀝니다. 서버만 `syncEntity`를 호출해야 하며, 이는 `isServer()`로 보호되어야 합니다. 클라이언트는, 클라이언트가 생성한 경우가 아니라면, 공유 엔티티의 동기화를 선언하지 않아야 합니다.
{% endhint %}

다음 `syncEntity` 함수는 다음 입력을 받습니다:

* **entityId**: 동기화할 엔티티에 대한 참조
* **componentIds**: 해당 엔티티에서 동기화되어야 하는 컴포넌트 목록입니다. 필요한 만큼 많은 컴포넌트를 포함할 수 있는 배열입니다. 모든 값은 다음이어야 합니다. `componentId` 속성.
* **entityEnumId**: (선택 사항) 모든 플레이어가 일관되게 사용하는 고유 ID입니다. 다음을 참조하세요. [enum id에 대하여](#about-the-enum-id).

모든 엔티티나 컴포넌트가 동기화되어야 하는 것은 아닙니다. 같은 자리에 그대로 남아 있는 나무 같은 정적 요소는 동기화가 필요하지 않습니다. 동기화하는 엔티티에서는 시간이 지남에 따라 변경되는 컴포넌트만 동기화하면 됩니다. 예를 들어, 큐브가 클릭되면 색이 바뀌는 경우 MeshRenderer나 Transform은 절대 변하지 않으므로 동기화하지 말고, Material 컴포넌트만 동기화해야 합니다.

{% hint style="info" %}
**💡 팁**: 공유하려는 데이터가 컴포넌트로 존재하지 않는다면, 그 데이터를 담는 [사용자 정의 컴포넌트](/creator/content-creator-ko/sdk7/architecture/custom-components.md) 를 정의하세요.
{% endhint %}

### 엔티티의 enum id에 대하여

다음 **entityEnumId** 는 고유해야 합니다. 이는 `engine.addEntity()`에서 할당되는 로컬 entityId와는 관련이 없으며, 이는 자동으로 생성되고 같은 씬을 실행하는 플레이어마다 다를 수 있습니다. 엔티티의 entityEnumId는 코드에서 명시적으로 정의해야 하며 고유해야 합니다.

이 ID를 명시적으로 설정하는 것은, 경쟁 조건 때문에 씬의 한 부분이 다른 부분보다 먼저 로드될 경우 생길 수 있는 불일치를 방지하는 데 중요합니다. 예를 들어 플레이어 A에게는 씬의 문이 엔티티 *512*일 수 있지만, 플레이어 B에게는 같은 문이 엔티티 *513*일 수 있습니다. 그런 경우 플레이어 A가 문을 열면, 플레이어 B는 대신 전체 건물이 움직이는 것을 보게 됩니다.

{% hint style="info" %}
**💡 팁**: 씬에서 enum을 만들어, 씬의 각 동기화 가능한 ID에 대한 참조를 명확하게 유지하세요.

```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
  );
}
```

여기서 EntityEnumId enum은 엔티티를 고유 식별자로 태그하는 데 사용되며, 생성 순서와 관계없이 모든 클라이언트가 수정된 엔티티를 인식하도록 보장합니다.
{% endhint %}

{% hint style="warning" %}
**📔 참고**: 다음보다 큰 숫자를 사용하는 것은 피하세요. **8001** 씬에 Smart Items도 포함되어 있다면 [Creator Hub](/creator/content-creator-ko/scene-editor/get-started/about-editor.md) 멀티플레이어 컴포넌트가 있는 항목은 8001부터 자동 할당된 ID를 사용합니다. 8001보다 낮은 ID는 동기화된 엔티티에 안전하게 할당할 수 있습니다.
{% endhint %}

**플레이어가 생성한 엔티티**

플레이어 상호작용의 결과로 엔티티가 생성되었고 이 엔티티를 다른 플레이어와 동기화해야 한다면, entityEnumId가 필요하지 않습니다. 다음을 사용할 수 있습니다. `syncEntity()` 엔티티와 컴포넌트 목록만 전달하면 됩니다. entityEnumId의 고유 값은 뒤에서 자동으로 할당됩니다.

씬 시작 시 인스턴스화되는 모든 엔티티에는 수동으로 할당된 ID가 있어야 합니다. 이는 모든 플레이어가 각 엔티티에 대해 동일한 ID를 사용하도록 보장하기 위함입니다. 한 명의 플레이어만 엔티티 인스턴스화를 담당하는 경우에는 명시적 ID가 필요하지 않습니다. 다른 플레이어들은 이미 할당된 ID가 있는 이 새 엔티티에 대한 업데이트를 받으므로 ID 불일치 위험이 없습니다.

예를 들어, 눈싸움 씬에서는 플레이어가 눈덩이를 던질 때마다 다른 플레이어와 동기화되는 새 엔티티를 인스턴스화합니다. 눈덩이에는 고유한 entityEnumId가 필요하지 않습니다.

```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]);
}
```

**부모가 있는 엔티티**

엔티티의 부모는 일반적으로 `parent` 속성을 통해 `Transform` 컴포넌트에서 정의됩니다. 그러나 이 속성은 부모의 로컬 entity id를 가리키며, 이는 달라질 수 있습니다. 다음을 참조하세요. [enum id에 대하여](#about-the-enum-id). 동기화가 필요하거나, 동기화가 필요한 자식을 가진 엔티티에 부모를 설정하려면 `parentEntity()` 함수를 `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);
}
```

parent와 child 모두 `syncEntity`와 함께 동기화된다는 점에 유의하세요. 따라서 모든 플레이어는 두 엔티티가 어떤 ID를 사용하는지 공통적으로 이해하게 됩니다. 이는 부모의 컴포넌트가 실제로 변경될 필요가 없더라도 필요합니다. 이 예시에서는 `syncEntity` 불필요한 컴포넌트를 동기화하지 않도록 빈 컴포넌트 배열을 포함합니다.

{% hint style="warning" %}
**📔 참고**: 엔티티가 `parentEntity()` 과 `parent` 속성을 통해 `Transform` 컴포넌트에 의해 모두 부모가 설정되어 있다면, `Transform` 컴포넌트의 속성은 무시됩니다.
{% endhint %}

엔티티가 `parentEntity()` 함수를 통해 부모가 설정되면, 다음 도우미 함수도 사용할 수 있습니다:

* **removeParent()**: `parentEntity()`의 효과를 되돌립니다. 자식 엔티티만 전달해야 합니다. 엔티티의 새 부모는 씬의 루트 엔티티가 됩니다. 원래 부모 엔티티는 씬에서 제거되지 않습니다.
* **getParent()**: 전달한 엔티티의 부모 엔티티를 반환합니다.
* **getChildren()**: 전달한 엔티티의 자식 목록을 iterable로 반환합니다.
* **getFirstChild()**: 전달한 엔티티 목록의 첫 번째 자식을 반환합니다.

```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]);

  // parent를 부모로 설정
  parentEntity(child, parent);

  // getParent
  const getParentResult = getParent(child);
  // 부모를 반환

  // getFirstChild
  const getFirstChildResult = getFirstChild(parent);
  // 자식을 반환

  // getChildren
  const getChildrenResult = Array.from(getChildren(parent));
  // [child]를 반환

  // child에서 parent를 제거
  removeParent(child);
}
```

## 동기화 상태 확인

플레이어가 씬에 막 들어왔을 때는 주변의 다른 플레이어들과 아직 동기화되지 않았을 수 있습니다. 플레이어가 동기화되기 전에 게임 상태를 변경하기 시작하면 게임에서 문제가 발생할 수 있습니다. 플레이어가 씬의 어떤 것이라도 편집할 수 있도록 허용하기 전에 항상 동기화되었는지 확인하는 것을 권장합니다.

플레이어가 씬의 구역 밖으로 나가면, 밖에 있는 동안에도 씬과 동기화되지 않은 상태가 됩니다. 따라서 플레이어가 근처에 있는 동안 씬은 계속 실행되므로, 씬의 시스템이 그 상황을 처리하는 것도 중요합니다. 플레이어가 다시 들어오면 씬 상태의 변경 사항이 자동으로 업데이트됩니다.

다음을 통해 플레이어의 씬 상태가 현재 동기화되어 있는지 확인할 수 있습니다. `isStateSyncronized()` 함수입니다. 이 함수는 불리언을 반환하며, 플레이어가 이미 씬과 동기화되어 있으면 true입니다.

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

const isConnected = isStateSyncronized();
```

예를 들어 이 검사를 시스템에 포함하고, 함수가 false를 반환하면 모든 상호작용을 차단할 수 있습니다.

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

engine.addSystem(() => {
  if (isStateSyncronized() && !button.enabled) {
    console.log("시작 게임 활성화");
    button.enable();
  }

  if (!isStateSyncronized() && button.enabled) {
    console.log(`시작 게임 비활성화.`);
    button.disable();
  }
});
```

## 명시적 MessageBus 메시지 보내기

{% hint style="warning" %}
**📔 참고**: `MessageBus` API는 SDK에서 사용 중단으로 표시되어 있으며, 향후 버전에서 제거될 수 있습니다. 대부분의 사용 사례에서는 [엔티티를 동기화됨으로 표시하는 것](#mark-an-entity-as-synced).
{% endhint %}

**메시지 버스 시작**

플레이어 간에 메시지를 보내고 받는 데 필요한 메서드를 처리할 메시지 버스 객체를 생성합니다.

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

const sceneMessageBus = new MessageBus();
```

**메시지 보내기**

다음을 사용하세요. `.emit` 명령을 사용하여 씬의 다른 모든 플레이어에게 메시지를 보냅니다.

```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: "클릭" },
  },
  function () {
    sceneMessageBus.emit("box1Clicked", {});
  }
);
```

각 메시지는 두 번째 인수로 payload를 포함할 수 있습니다. payload의 타입은 `Object`이며, 보내고 싶은 관련 데이터를 무엇이든 포함할 수 있습니다.

```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" %}
**💡 팁**: 하나의 메시지에 둘 이상의 변수에서 온 데이터를 포함해야 한다면, 이 데이터를 모두 하나의 객체에 담는 사용자 정의 타입을 만드세요.
{% endhint %}

**메시지 수신**

해당 씬의 다른 모든 플레이어로부터 오는 메시지를 처리하려면 다음을 사용하세요. `.on`을 사용합니다. 이 함수를 사용할 때는 메시지 문자열을 제공하고 실행할 함수를 정의합니다. 일치하는 문자열의 메시지가 도착할 때마다 주어진 함수가 한 번씩 실행됩니다.

```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" %}
**📔 참고**: 플레이어가 보낸 메시지는 그 플레이어 자신도 받게 됩니다. `.on` 메서드는 같은 플레이어가 보낸 메시지와 다른 플레이어가 보낸 메시지를 구분할 수 없습니다.
{% endhint %}

**전체 MessageBus 예시**

이 예시는 메인 큐브가 클릭될 때마다 메시지 버스를 사용해 새 메시지를 보내고, 무작위 위치에 새 큐브를 생성합니다. 이 메시지에는 새 큐브의 위치가 포함되어 있어 모든 플레이어가 이 새 큐브를 같은 위치에서 보게 됩니다.

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

/// --- 메시지 버스 생성 ---
const sceneMessageBus = new MessageBus();

// 큐브 팩토리
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);

  // 큐브가 클릭되면, 다른 큐브를 생성하기 위한 메시지 전송
  pointerEventsSystem.onPointerDown(
    {
      entity: meshEntity,
      opts: { button: InputAction.IA_PRIMARY, hoverText: "생성하려면 E를 누르세요" },
    },
    function () {
      sceneMessageBus.emit("spawn", {
        position: {
          x: 1 + Math.random() * 8,
          y: Math.random() * 8,
          z: 1 + Math.random() * 8,
        },
      });
    }
  );

  return meshEntity;
}

// 초기화
createCube(8, 1, 8);

// 데이터 타입 정의
type NewBoxPosition = {
  position: { x: number; y: number; z: number };
};

// 생성 메시지를 받으면 새 큐브 생성
sceneMessageBus.on("spawn", (info: NewBoxPosition) => {
  createCube(info.position.x, info.position.y, info.position.z);
});
```

## 멀티플레이어 장면을 로컬에서 테스트하기

장면 미리 보기를 실행해 두 개 이상의 서로 다른 explorer 창에서 열면, 열려 있는 각 창은 별도의 플레이어로 인식되며 모의 통신 서버가 이 플레이어들을 동기화 상태로 유지합니다.

한 창에서 장면과 상호작용한 다음 다른 창으로 전환하면 그 상호작용의 효과가 그곳에도 표시되는 것을 볼 수 있습니다.

Creator Hub에서 다음을 켜세요 **다중 인스턴스 미리 보기** 다음에서 **데스크톱 클라이언트** 하위 메뉴의 **재생 옵션**, 그런 다음 다음을 클릭하세요 **재생** 버튼을 한 번 더 눌러 두 번째 디센트럴랜드 explorer 창을 엽니다. 두 창 모두 서로 다른 주소로 연결해야 합니다. 장면이 다시 로드되어도 동일한 세션이 유지됩니다.

![](https://3980763956-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)

대안으로, 다음을 브라우저 URL에 입력하여 두 번째 디센트럴랜드 explorer 창을 열 수 있습니다:

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

## 싱글 플레이어 씬

씬이 [Decentraland World](/creator/content-creator-ko/sdk7/publishing/publishing-options.md#decentraland-worlds)에 배포되면 싱글 플레이어 씬으로 만들 수 있습니다. 플레이어들은 서로를 볼 수 없고, 채팅할 수도 없으며 서로의 행동의 효과를 볼 수도 없습니다.

이를 위해 씬의 `scene.json` 파일을 구성하여 **fixedAdapter** 를 `offline:offline`로 설정하세요. 그러면 씬에는 Communication Service가 전혀 없게 되며, 그 월드에 들어오는 각 사용자는 항상 혼자 있게 됩니다.

**예시:**

```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-ko/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.
