> 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).

# 서버리스 멀티플레이어

플레이어 간 씬 상태를 동기화하세요.

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

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

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

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

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

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

다음에서 [크리에이터 허브](/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)

코드로 엔티티를 동기화됨으로 표시하려면 다음을 사용하세요 `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() `함수 내부,` 함수나 그 이후에 실행되는 함수에서 호출하세요 `함수 내부,` (예: 콜백 또는 시스템). 파일의 최상위에서 호출하면 에러가 발생합니다. 그 시점에는 플레이어의 프로필이 아직 초기화되지 않았기 때문입니다.
{% 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** 고유해야 합니다. 이는 다음에서 할당되는 로컬 entityId와는 관련이 없습니다 `engine.addEntity()`이며, 자동으로 생성되어 같은 씬을 실행하는 플레이어마다 달라질 수 있습니다. 엔티티의 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/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);
}
```

부모와 자식 모두 `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]);

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

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

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

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

  // 자식에서 부모를 제거
  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", {});
  }
);
```

각 메시지는 두 번째 인자로 페이로드를 포함할 수 있습니다. 페이로드의 타입은 `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 };
};

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

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

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

한 창에서 장면과 상호작용한 다음 다른 창으로 전환하여 그 상호작용의 효과가 그곳에도 보이는지 확인하세요.

Creator Hub에서 미리보기 버튼을 두 번째로 클릭하면 두 번째 Decentraland 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)

대안으로, 다음을 브라우저 URL에 입력하여 두 번째 Decentraland 탐색기 창을 열 수 있습니다:

> `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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
