---
title: Bot 플레이어 생성 가이드
slug: world-sdk-guide-kr/bot
docTags: 
createdAt: 2024-09-02T08:34:13.135Z
---

봇 플레이어는 멀티플레이어 월드를 시작할 충분한 사람이 없거나, 플레이어가 월드 중에 나갈 때 채우기 위해 사용됩니다.

봇 플레이어의 행동 방식은 각 콘텐츠에 맞게 구현되어야 합니다.

이 가이드는 봇 플레이어를 만드는 일반적인 방법을 설명합니다.

:::hint{type="info"}
**📘&#x20;**&#xBD07; 플레이어 생성 가이드는 멀티플레이어 가이드를 기반으로 합니다. \[[멀티플레이 제작하기](docId\:S6aOeRnolpAc9cyQi_Onr)]
:::

## 1단계: 봇 플레이어 만들기

1-1. 멀티플레이어 스키마에 `IsBot`이라는 불리언 값을 추가합니다.

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-H8VrE4wHUDwRHCAiehWap-20240904-102422.png)



1-2. 아래의 함수를 정의하여 서버 스크립트 index.ts에서 봇 플레이어를 생성하고 원하는 지점에서 호출합니다.

:::CodeblockTabs
index.ts

```typescript
// `CreateBot()` 메서드는 주어진 `userId`로 봇 플레이어를 생성하는 데 사용됩니다.
CreateBot(userId: string) {

    // 제공된 `userId`를 사용하여 봇 플레이어의 세션 ID를 생성합니다.
    const sessionId = "Bot_" + userId;

  	// 동일한 세션 ID를 가진 봇 플레이어가 이미 존재하는지 확인합니다. 그렇다면 중복 생성 없이 반환합니다.
    if (this.state.players.has(sessionId)) {
        return;
    }

    // 봇 플레이어를 위한 새로운 `Player` 객체를 생성합니다.
    const player: Player = new Player();
    player.sessionId = sessionId;
    if (userId) {
        player.zepetoUserId = userId;
    }
    player.isBot = true;

  	// 세션 ID를 키로 사용하여 상태의 플레이어 맵에 봇 플레이어를 추가합니다.
    this.state.players.set(player.sessionId, player);
    this._botMap.set(sessionId, player);
}
```
:::



:::hint{type="success"}
**👍 팁**

- 특정 사용자의 userId는 봇 플레이어 캐릭터가 생성되기 위해 미리 저장됩니다.
- 서버의 OnJoin에 연결된 클라이언트의 userId를 확인하여 특정 사용자의 UserId를 확인할 수 있습니다. 아래 스크립트를 서버 스크립트에 작성한 후 관련 월드에서 연결하세요.
:::

:::CodeblockTabs
index.ts

```typescript
onJoin(client: SandboxPlayer) {
	console.log(client.userId);
}
```
:::



## 단계 2: 클라이언트에서 봇 플레이어 만들기

2-1. 서버에서 특정 시점에 봇 플레이어를 생성하면 클라이언트는 이를 OnJoinPlayer()에서 새로운 플레이어로 인식합니다.

- 프로젝트 만들기 > 생성 > ZEPETO > TypeScript로 이름을 BotPlayerManager로 변경합니다.
- OnAddedPlayer()에서 각 플레이어를 생성하는 로직을 추가하고, 봇 플레이어를 구분하고 그들의 ZEPETO 캐릭터를 생성하는 로직을 추가합니다.

:::CodeblockTabs
BotPlayerManager.ts

```typescript
import { ZepetoCharacter, ZepetoPlayers } from 'ZEPETO.Character.Controller';
import { Room } from 'ZEPETO.Multiplay';
import { ZepetoScriptBehaviour } from 'ZEPETO.Script'
import { ZepetoWorldMultiplay } from 'ZEPETO.World';

export default class BotPlayerManager extends ZepetoScriptBehaviour {

    public zepetoWorldMultiplay: ZepetoWorldMultiplay;

 	// 현재 방과 봇 플레이어 데이터를 저장할 개인 변수들입니다.
    private _room: Room;
    private _botMapData: Map<string, ZepetoCharacter> = new Map<string, ZepetoCharacter>();

    Start() {
		
	 	// `ZepetoWorldMultiplay` 컴포넌트에서 `RoomJoined` 이벤트를 수신합니다.
        this.zepetoWorldMultiplay.RoomJoined += (room: Room) => {
        	this._room = room;
        }

	 	// 새로 추가된 플레이어를 처리하기 위해 `ZepetoPlayers.instance`에서 `OnAddedPlayer` 이벤트를 수신합니다.
        ZepetoPlayers.instance.OnAddedPlayer.AddListener((userId: string) => {

		 	// `userId`를 사용하여 방 상태에서 현재 플레이어 데이터를 가져옵니다.
            const currentPlayer = this._room.State.players.get_Item(userId);

		 	// 플레이어가 봇인지 확인하고, 그렇다면 그들을 봇 플레이어로 설정합니다.
            if (currentPlayer.isBot) {
                this.SetBotPlayer(currentPlayer.sessionId);
            }
        });
    }
}
```
:::



2-2. SetBotPlayer 함수를 작성하여 봇 플레이어에 태그와 동기화 구성 요소를 추가하고 이를 제어하는 스크립트를 생성합니다.

- 봇 플레이어 데이터를 관리하기 위해 Map 형식으로 \_botMapData를 설정하여 저장합니다.

:::CodeblockTabs
BotPlayerManager.ts

```typescript
// `SetBotPlayer()` 메서드는 플레이어를 봇으로 설정하는 데 사용됩니다.
SetBotPlayer(userId: string) {

    // `userId`를 사용하여 봇 플레이어와 관련된 ZEPETO 캐릭터를 가져옵니다.
    const bot = ZepetoPlayers.instance.GetPlayer(userId).character;

    // 식별을 위해 캐릭터의 이름을 `userId`로 설정합니다.
    bot.gameObject.name = userId;

    // `userId`를 키로 사용하여 `_botMapData` 맵에 봇 플레이어의 데이터를 저장합니다.
		this._botMapData.set(userId, bot);
}
```
:::



:::hint{type="success"}
**👍 팁**
SetBotPlayer()에 추가 스크립트나 설정을 추가하여 봇 플레이어의 행동을 제어할 수 있습니다.
:::



## 3단계: 클라이언트에 봇 플레이어 버튼 생성

시작하기 위해 특정 수의 플레이어가 필요한 월드의 경우, 때때로 플레이어가 충분하지 않아 월드가 시작되기까지 오랜 시간을 기다려야 합니다.

이 경우, Bot 플레이어를 추가하여 월드를 시작할 수 있습니다.

3-1. index.ts에서 서버가 클라이언트로부터 메시지를 받을 때 CreateBot()을 실행하도록 함수를 등록합니다.

```typescript
async OnCreate() {

	 	// 주어진 userId로 봇 플레이어를 생성하는 "CreateBot" 메시지를 처리합니다.
    this.onMessage("CreateBot", (client, message) => {
        this.CreateBot(message);
    });
}
```



3-2. 클라이언트 스크립트 BotPlayerManager.ts에서 서버에 "CreateBot" 메시지를 보내는 함수를 작성합니다.

- 함수를 실행하는 방법은 버튼을 눌러 메시지를 보내는 것입니다.
- 생성할 Bot 플레이어의 사용자 ID를 문자열로 메시지를 통해 보냅니다.

:::CodeblockTabs
BotPlayerManager.ts

```typescript
public buttonCreateBot: Button;
public botPlayerId: string;

Start() {	

	 	// "Create Bot" 버튼에 클릭 리스너를 추가하여 봇 플레이어를 생성하는 메시지를 보냅니다.
	 	this.buttonCreateBot.onClick.AddListener(() => {
            this._room.Send("CreateBot", this.botPlayerId);
    });
}
```
:::



3-3. 이제 서버와 런타임을 실행하면 버튼을 누를 때 봇 플레이어가 생성되는 것을 볼 수 있습니다.

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-7RPcD4Sy06GPGzi3Z-LZQ-20240904-102422.png)



## 4단계: 봇 플레이어 추가로 월드 시작하기

월드 시작에 필요한 플레이어가 부족할 때, 봇 플레이어를 추가하여 월드를 시작할 수 있습니다.

4-1. 서버 스크립트에서 OnJoin 중에 다음 코드를 추가하여 플레이어 수를 확인하고 최소 네 명의 플레이어가 있을 때 월드를 시작합니다.

- CreateBot()에서 플레이어 수를 확인하는 함수를 추가합니다.
- StartWorld() 함수에서 플레이 수를 카운트하는 변수를 추가합니다.

:::CodeblockTabs
index.ts

```typescript
 async onJoin(client: SandboxPlayer) {

 	// 플레이어가 입장한 후 방의 플레이어 수를 확인합니다.
	this.CheckPlayerNumber();
}

// `CheckPlayerNumber()` 메서드는 방의 플레이어 수를 확인하고 최소 네 명의 플레이어가 있을 경우 월드를 시작합니다.
CheckPlayerNumber() {

	// 방의 현재 플레이어 수를 콘솔에 출력합니다.
	console.log(`플레이어 수, ${this.state.players.size}`);

	// 방에 최소 네 명의 플레이어가 있을 경우 월드를 시작합니다.
	if (this.state.players.size >= 4) {
 	this.StartWorld();
	}
}

// `CreateBot()` 메서드는 주어진 `userId`로 봇 플레이어를 생성하는 데 사용됩니다.
CreateBot(userId: string) {

	// 제공된 `userId`를 사용하여 봇 플레이어의 세션 ID를 생성합니다.
	const sessionId = "Bot_" + userId;

	// 동일한 세션 ID를 가진 봇 플레이어가 이미 존재하는지 확인합니다. 존재할 경우 중복 생성 없이 반환합니다.
 	if (this.state.players.has(sessionId)) {
	return;
	}

	// 봇 플레이어를 위한 새로운 `Player` 객체를 생성합니다.
	const player: Player = new Player();
 	player.sessionId = sessionId;
	if (userId) {
		player.zepetoUserId = userId;
	}
	player.isBot = true;

	// 세션 ID를 키로 사용하여 상태의 플레이어 맵에 봇 플레이어를 추가합니다.
	this.state.players.set(player.sessionId, player);
	this._botMap.set(sessionId, player);

	// 봇 플레이어를 추가한 후 방의 플레이어 수를 확인합니다.
	this.CheckPlayerNumber();
}


private playTime: number = 0;

// `StartWorld()` 메서드는 플레이 시간을 증가시키고 모든 클라이언트에 "StartWorld" 메시지를 방송합니다.
StartWorld() {
		this.playTime += 1;

    // 월드의 시작과 현재 플레이 시간을 나타내는 메시지를 출력합니다.
    console.log("StartWorld!");
    this.broadcast("StartWorld", this.playTime);
} 
```
:::



:::hint{type="success"}
- 서버에서 실제 플레이어가 방에 들어오면 OnJoin이 실행됩니다. 따라서 CreateBot을 통해 봇 플레이어가 생성되고 플레이어가 OnJoin을 통해 들어올 때 checkPlayerNumber()는 인원 수를 추가합니다.
:::



4-2. 클라이언트 스크립트인 BotPlayerManager.ts에서 서버로부터 StartWorld 메시지를 수신할 때 실행되는 StartWorld()를 작성합니다.

:::CodeblockTabs
BotPlayerManager.ts

```typescript
Start() {

	// `ZepetoWorldMultiplay` 컴포넌트에서 `RoomJoined` 이벤트를 수신합니다.
	this.zepetoWorldMultiplay.RoomJoined += (room: Room) => {

 	// "StartWorld" 메시지 유형에 대한 메시지 핸들러를 추가합니다.    
	this._room.AddMessageHandler("StartWorld", (playTime: number) => {
    	this.StartWorld(playTime);
  });   
}

// 제공된 `playTime`으로 월드가 시작될 때 `StartWorld()` 메서드가 호출됩니다.
StartWorld(playTime: number) {

	// `playTime`과 함께 월드 시작 메시지를 출력합니다.
  console.log(`Start World : ${playTime}`);
}
```
:::



4-3. 실행 중에 봇 플레이어를 포함하여 4명 이상의 플레이어가 있을 경우, 서버 콘솔과 클라이언트 콘솔에 'World Start'라는 로그가 표시됩니다.

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-Lctng3ajZYOFKBpMbP_UJ-20240904-102422.png)



## 5단계: 봇 플레이어 위치 동기화

아래는 추가된 봇 플레이어를 로컬 플레이어 위치로 이동시키고 이동 위치를 동기화하는 샘플 코드입니다.

5-1. 먼저, 서버의 index.ts에서 클라이언트로부터 메시지 `MoveBot`을 수신할 때 봇 플레이어를 이동시키는 코드를 작성합니다.

```typescript
async OnCreate() {

 	// "MoveBot" 메시지를 처리하여 봇 플레이어를 지정된 위치로 이동시킵니다.
	this.onMessage("MoveBot", (client, message) => {
        this.MoveBot(client, message);
 	});
}  

// `MoveBot()` 메서드는 수신된 메시지를 기반으로 봇 플레이어를 지정된 위치로 이동시킵니다.
MoveBot(client: SandboxPlayer, message: string) {
    // 클라이언트로부터 수신된 JSON 메시지를 파싱하여 위치 정보를 추출합니다.
    const position = JSON.parse(message);

    // 사용자의 세션 ID와 파싱된 위치 데이터를 포함하는 새로운 메시지 객체를 생성합니다.
    const newMessage = {
        user: client.sessionId,
        positionX: position.x,
        positionY: position.y,
        positionZ: position.z
    }

    // 새로운 메시지 데이터를 JSON 문자열로 모든 클라이언트에 "MoveBotToPosition" 메시지를 브로드캐스트합니다.
    this.broadcast("MoveBotToPosition", JSON.stringify(newMessage));
}
```



5-2. 클라이언트 스크립트인 BotPlayerManager.ts에서 buttonCallBot이 눌릴 때 로컬 플레이어 위치를 서버로 전송하는 SendBotPosition()을 작성합니다.

- 그런 다음 서버에서 MoveBotToPosition 메시지를 수신할 때 모든 봇 플레이어를 메시지에 포함된 위치 정보로 이동시키는 코드를 작성하십시오.

:::CodeblockTabs
BotPlayerManager.ts

```typescript
public buttonCallBot: Button;

Start(){    

	// `ZepetoWorldMultiplay` 컴포넌트에서 `RoomJoined` 이벤트를 수신합니다.
    this.zepetoWorldMultiplay.RoomJoined += (room: Room) => {
    		this._room = room;

        // "StartWorld" 메시지 유형에 대한 메시지 핸들러를 추가합니다.
        this._room.AddMessageHandler("StartWorld", (playTime: number) => {
            this.StartWorld(playTime);
        });
       
	// "Call Bot" 버튼에 클릭 리스너를 추가하여 봇 플레이어 위치를 전송하는 메시지를 보냅니다.
	this.buttonCallBot.onClick.AddListener(() => {
				this.SendBotPosition();
	});
}  

// 이 메서드는 로컬 플레이어의 캐릭터 위치를 서버에 전송하여 봇 이동 동기화를 수행합니다.
SendBotPosition() {

    // 로컬 플레이어의 캐릭터 위치를 가져옵니다.
    const localPlayerPosition = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.transform.position;

    // 로컬 플레이어의 캐릭터의 x, y, z 좌표를 포함하는 위치 객체를 생성합니다.
    const position = {
        x: localPlayerPosition.x,
        y: localPlayerPosition.y,
        z: localPlayerPosition.z
    }

    // 위치 객체를 JSON 문자열로 변환하고 "MoveBot" 메시지 유형으로 서버에 전송합니다.
    this._room.Send("MoveBot", JSON.stringify(position));
    }   

MoveBotToPosition(message) {

    // 클라이언트에서 수신한 JSON 메시지를 구문 분석하여 위치 정보를 추출합니다.
    const jsonMessage = JSON.parse(message);
    const position = new Vector3(jsonMessage.positionX, jsonMessage.positionY, jsonMessage.positionZ);

    // `_botMapData` 맵의 각 봇 캐릭터를 지정된 위치로 이동시키고 x 및 z 축에 작은 무작위 오프셋을 추가합니다.
    this._botMapData.forEach((character: ZepetoCharacter) => {
        // `MoveToPosition()` 메서드를 사용하여 캐릭터를 지정된 위치로 이동시킵니다.
        // 여기서, 봇의 자연스러운 움직임을 위해 목표 위치에 작은 무작위 오프셋을 추가합니다.
        character.MoveToPosition(position + new Vector3(Random.Range(0.5, 1), 0, Random.Range(0.5, 1)));
    });
}
```
:::



5-3. 이제 런타임에 봇 플레이어를 생성하고 buttonCallBot 버튼을 누르면 생성된 봇 플레이어가 로컬 플레이어 캐릭터 위치로 이동하는 것을 볼 수 있습니다.

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-P_Alb5zIa4CoJJONXEvHQ-20240904-102428.png)



### BotPlayerManager.ts 전체 코드

:::CodeblockTabs
BotPlayerManager.ts

```typescript
import { Random, Vector3 } from 'UnityEngine';
import { Button } from 'UnityEngine.UI';
import { ZepetoCharacter, ZepetoPlayers } from 'ZEPETO.Character.Controller';
import { Room } from 'ZEPETO.Multiplay';
import { ZepetoScriptBehaviour } from 'ZEPETO.Script'
import { ZepetoWorldMultiplay } from 'ZEPETO.World';

export default class BotPlayerManager extends ZepetoScriptBehaviour {

    // Public properties to reference necessary components and settings from the Inspector.
    public zepetoWorldMultiplay: ZepetoWorldMultiplay;
    public buttonCreateBot: Button;
    public buttonCallBot: Button;
    public botPlayerId: string;

    // Private variables to store the current room and bot player data.
    private _room: Room;
    private _botMapData: Map<string, ZepetoCharacter> = new Map<string, ZepetoCharacter>();

    Start() {

        // Listen for the `RoomJoined` event from the `ZepetoWorldMultiplay` component.
        this.zepetoWorldMultiplay.RoomJoined += (room: Room) => {
            this._room = room;

            // Add a message handler for the "StartWorld" message type.
            this._room.AddMessageHandler("StartWorld", (playTime: number) => {
                this.StartWorld(playTime);
            });
        }

        // Listen for the `OnAddedPlayer` event from `ZepetoPlayers.instance` to handle newly added players.
        ZepetoPlayers.instance.OnAddedPlayer.AddListener((userId: string) => {

            // Get the current player data from the room state using the `userId`.
            const currentPlayer = this._room.State.players.get_Item(userId);

            // Check if the player is a bot, and if so, set them as a bot player.
            if (currentPlayer.isBot) {
                this.SetBotPlayer(currentPlayer.sessionId);
            }
        });

        // Add a click listener to the "Create Bot" button to send a message to create a bot player.
        this.buttonCreateBot.onClick.AddListener(() => {
            this._room.Send("CreateBot", this.botPlayerId);
        });

        this.zepetoWorldMultiplay.RoomJoined += (room: Room) => {
            this._room = room;

            this._room.AddMessageHandler("MoveBotToPosition", (message: string) => {
                this.MoveBotToPosition(message);
            });
        }

        // Add a click listener to the "Call Bot" button to send a message to send a bot player position.
        this.buttonCallBot.onClick.AddListener(() => {
            this.SendBotPosition();
        });
    }

    // The `SetBotPlayer()` method is used to set a player as a bot.
    SetBotPlayer(userId: string) {

        // Get the ZEPETO character associated with the bot player using their `userId`.
        const bot = ZepetoPlayers.instance.GetPlayer(userId).character;

        // Set the name of the character to the `userId` for identification.
        bot.gameObject.name = userId;

        // Store the bot player's data in the `_botMapData` map using their `userId` as the key.
        this._botMapData.set(userId, bot);
    }

    // The `StartWorld()` method is called when the world starts with the provided `playTime`.
    StartWorld(playTime: number) {

        // Print the world start message along with the `playTime`.
        console.log(`Start World : ${playTime}`);
    }

    // This method sends the position of the local player's character to the server for bot movement synchronization.
    SendBotPosition() {

        // Get the position of the local player's character.
        const localPlayerPosition = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.transform.position;

        // Create a position object containing the x, y, and z coordinates of the local player's character.
        const position = {
            x: localPlayerPosition.x,
            y: localPlayerPosition.y,
            z: localPlayerPosition.z
        }

        // Convert the position object to a JSON string and send it to the server with the "MoveBot" message type.
        this._room.Send("MoveBot", JSON.stringify(position));
    }

    // This method is called when the server receives the "MoveBot" message from a client and moves the bot characters to the specified position.
    MoveBotToPosition(message) {

        // Parse the JSON message received from the client to extract the position information.
        const jsonMessage = JSON.parse(message);
        const position = new Vector3(jsonMessage.positionX, jsonMessage.positionY, jsonMessage.positionZ);

        // Move each bot character in the `_botMapData` map to the specified position with a small random offset on the x and z axes.
        this._botMapData.forEach((character: ZepetoCharacter) => {
            // The `MoveToPosition()` method is used to move the character to the specified position.
            // Here, a small random offset is added to the target position to create a natural-looking movement for the bots.
            character.MoveToPosition(position + new Vector3(Random.Range(0.5, 1), 0, Random.Range(0.5, 1)));
        });
    }
}
```
:::



### index.ts 서버 전체 코드

```text
import { Sandbox, SandboxOptions, SandboxPlayer } from "ZEPETO.Multiplay";
import { DataStorage } from "ZEPETO.Multiplay.DataStorage";
import { Player, Transform, Vector3 } from "ZEPETO.Multiplay.Schema";

export default class extends Sandbox {

    storageMap: Map<string, DataStorage> = new Map<string, DataStorage>();

    // 봇 플레이어의 맵 데이터를 _botMap으로 저장합니다.
    private _botMap: Map<string, Player> = new Map<string, Player>();
    private playTime: number = 0;

    constructor() {
        super();
    }

    onCreate(options: SandboxOptions) {

        // Room 객체가 생성될 때 호출됩니다.
        // Room 객체의 상태 또는 데이터 초기화를 처리합니다.

        this.onMessage("onChangedTransform", (client, message) => {

            this.state.players.get(client.sessionId);

            const player = this.state.players.get(client.sessionId);

            const transform = new Transform();
            transform.position = new Vector3();
            transform.position.x = message.position.x;
            transform.position.y = message.position.y;
            transform.position.z = message.position.z;

            transform.rotation = new Vector3();
            transform.rotation.x = message.rotation.x;
            transform.rotation.y = message.rotation.y;
            transform.rotation.z = message.rotation.z;

            if (player) {
                player.transform = transform;
            }
        });

        this.onMessage("onChangedState", (client, message) => {
            const player = this.state.players.get(client.sessionId);
            if (player) {
                player.state = message.state;
                player.subState = message.subState;
            }
        });

        // 주어진 userId로 봇 플레이어를 생성하는 "CreateBot" 메시지를 처리합니다.
        this.onMessage("CreateBot", (client, message) => {
            this.CreateBot(message);
        });

        // 지정된 위치로 봇 플레이어를 이동시키는 "MoveBot" 메시지를 처리합니다.
        this.onMessage("MoveBot", (client, message) => {
            this.MoveBot(client, message);
        });
    }

    // `CreateBot()` 메서드는 주어진 `userId`로 봇 플레이어를 생성하는 데 사용됩니다.
    CreateBot(userId: string) {

        // 제공된 `userId`를 사용하여 봇 플레이어의 세션 ID를 생성합니다.
        const sessionId = "Bot_" + userId;

        // 동일한 세션 ID를 가진 봇 플레이어가 이미 존재하는지 확인합니다. 존재하면 중복 생성을 방지합니다.
        if (this.state.players.has(sessionId)) {
            return;
        }

        // 봇 플레이어를 위한 새로운 `Player` 객체를 생성합니다.
        const player: Player = new Player();
        player.sessionId = sessionId;
        if (userId) {
            player.zepetoUserId = userId;
        }
        player.isBot = true;

        // 세션 ID를 키로 사용하여 상태의 플레이어 맵에 봇 플레이어를 추가합니다.
        this.state.players.set(player.sessionId, player);
        this._botMap.set(sessionId, player);

        // 봇 플레이어를 추가한 후 방의 플레이어 수를 확인합니다.
        this.CheckPlayerNumber();
    }

    // `CheckPlayerNumber()` 메서드는 방의 플레이어 수를 확인하고, 최소 4명의 플레이어가 있을 경우 월드를 시작합니다.
    CheckPlayerNumber() {

        // 방의 현재 플레이어 수를 콘솔에 출력합니다.
        console.log(`player Number, ${this.state.players.size}`);

        // 방에 최소 4명의 플레이어가 있으면 월드를 시작합니다.
        if (this.state.players.size >= 4) {
            this.StartWorld();
        }
    }

    // `StartWorld()` 메서드는 플레이 시간을 증가시키고 "StartWorld" 메시지를 모든 클라이언트에 방송합니다.
    StartWorld() {
        this.playTime += 1;

        // 월드의 시작과 현재 플레이 시간을 나타내는 메시지를 출력합니다.
        console.log("Start World!");
        this.broadcast("StartWorld", this.playTime);
    }

    // `MoveBot()` 메서드는 수신된 메시지를 기반으로 봇 플레이어를 지정된 위치로 이동시킵니다.
    MoveBot(client: SandboxPlayer, message: string) {
        // 클라이언트로부터 수신된 JSON 메시지를 파싱하여 위치 정보를 추출합니다.
        const position = JSON.parse(message);

        // 사용자의 세션 ID와 파싱된 위치 데이터를 포함하는 새로운 메시지 객체를 생성합니다.
        const newMessage = {
            user: client.sessionId,
            positionX: position.x,
            positionY: position.y,
            positionZ: position.z
        }

        // 새로운 메시지 데이터를 JSON 문자열로 모든 클라이언트에 "MoveBotToPosition" 메시지를 방송합니다.
        this.broadcast("MoveBotToPosition", JSON.stringify(newMessage));
    }

    async onJoin(client: SandboxPlayer) {

        // schemas.json에 정의된 플레이어 객체를 생성하고 초기 값을 설정합니다.
        console.log(`[OnJoin] sessionId : ${client.sessionId}, HashCode : ${client.hashCode}, userId : ${client.userId}`)

        const player = new Player();
        player.sessionId = client.sessionId;

        if (client.hashCode) {
            player.zepetoHash = client.hashCode;
        }
        if (client.userId) {
            player.zepetoUserId = client.userId;
        }

        // [DataStorage] 들어온 플레이어의 DataStorage 로드
        const storage: DataStorage = client.loadDataStorage();

        this.storageMap.set(client.sessionId, storage);

        let visit_cnt = await storage.get("VisitCount") as number;
        if (visit_cnt == null) visit_cnt = 0;

        console.log(`[OnJoin] ${client.sessionId}의 방문 수 : ${visit_cnt}`)

        // [DataStorage] 플레이어의 방문 수를 업데이트하고 저장합니다.
        await storage.set("VisitCount", ++visit_cnt);

        // 세션Id를 사용하여 플레이어 객체를 관리합니다. 이는 클라이언트 객체의 고유 키 값입니다.
        // 클라이언트는 플레이어 객체에 추가된 정보를 확인할 수 있습니다.
        this.state.players.set(client.sessionId, player);

        // 플레이어가 입장한 후 방의 플레이어 수를 확인합니다.
        this.CheckPlayerNumber();
    }

    onTick(deltaTime: number): void {
        // 서버에서 설정된 시간마다 반복적으로 호출되며, 특정 간격 이벤트를 deltaTime을 사용하여 관리할 수 있습니다.
    }

    async onLeave(client: SandboxPlayer, consented?: boolean) {

        // allowReconnection을 설정하면 회로를 위해 연결을 유지할 수 있지만, 기본 가이드에서는 즉시 정리합니다.
        // 클라이언트는 삭제된 플레이어 객체에 대한 정보를 확인할 수 있습니다.
        this.state.players.delete(client.sessionId);
    }
}
```

